Tools
The MCP tools Papermark exposes and what each one does.
The server exposes the full v1 API surface as tools: the same documents, links, datarooms, folders, visitors, and analytics you get over HTTP. Argument schemas are identical to the REST API's request shapes, so see the linked reference endpoint for full field detail.
The tool set differs slightly by transport, all in how a local file gets uploaded:
- Stdio (Claude Desktop, Claude Code) exposes 57 tools.
upload_documentandadd_document_versionread a localfile_pathdirectly, because the server runs on your machine. - HTTP (claude.ai Connectors, ChatGPT Apps) exposes 58 tools —
the same 57 plus
request_document_upload. A remote server can't read your filesystem, so local-file upload happens in two steps via a presigned URL (see Uploading an attached file below). Over HTTP,upload_documentandadd_document_versiontake anupload_idor asource_urlinstead of afile_path.
Everything else is identical across transports.
Documents (6–7)
| Tool | What it does | Required scope |
|---|---|---|
list_documents | Paginated list of team documents. Args: limit, cursor, query?. | documents.read |
get_document | Fetch one document by ID. For link/notion documents the result includes the external target url (null for file-based documents). Args: document_id. | documents.read |
search_documents | Substring search by name. Args: query, limit. | documents.read |
request_document_upload | HTTP transport only. Step 1 of uploading an attached file: returns a short-lived presigned upload_url, a single-use upload_id, the required_headers, expires_in, and a ready-to-run curl_command. PUT the file bytes to upload_url, then finalize with upload_document (new document) or add_document_version (new version of an existing document). Args: file_name, content_type, content_length. See Uploading an attached file. | documents.write |
upload_document | Register a new document from exactly one source. File: stdio file_path (local) or source_url; HTTP upload_id (from request_document_upload) or source_url (public HTTPS URL). Non-file: type: "link" | "notion" + external_url to point at an external page or a public Notion page (stored as-is, not downloaded). Optional: name, folder_id, create_link. To replace the file on a document that already exists, use add_document_version instead. | documents.write |
update_document | Rename a document or move it between folders. Args: document_id, name?, folder_id?. | documents.write |
delete_document | Permanently delete a document and its links and view history. Args: document_id. | documents.write |
Document versions (4)
| Tool | What it does | Required scope |
|---|---|---|
list_document_versions | List every version of a document. Args: document_id. | documents.read |
get_document_version | Fetch one version by ID. Args: document_id, version_id. | documents.read |
add_document_version | Add a new version of a document, keeping the document, its share links, its dataroom placement, and its version history. Takes the same file sources as upload_document — provide exactly one: upload_id (a completed presigned upload; stdio also accepts a local file_path) for a private file with no public URL, source_url (public HTTPS URL the server fetches), or external_url to repoint a link/notion document at a new URL (type preserved, stored as-is). external_url must be a public HTTPS URL — and for a notion document a publicly-accessible Notion page — validated the same way as upload_document. Args: document_id, upload_id?, source_url?, external_url? (stdio: file_path?, content_type?). | documents.write |
promote_document_version | Make a version the primary one shown to viewers. Args: document_id, version_id. | documents.write |
Folders (6)
Team-library folders, for organizing standalone documents outside any dataroom.
| Tool | What it does | Required scope |
|---|---|---|
list_folders | List folders, optionally under one parent. Args: parent_id?, limit, cursor. | documents.read |
get_folder | Fetch one folder by ID. Args: folder_id. | documents.read |
create_folder | Create a folder. Args: name, parent_id?, icon?, color?. | documents.write |
update_folder | Rename a folder or change its icon/color. Args: folder_id, name?, icon?, color?. | documents.write |
move_folder | Reparent a folder. Args: folder_id, parent_id. | documents.write |
delete_folder | Delete a folder. Pass cascade: true to also delete everything nested inside it. Args: folder_id, cascade?. | documents.write |
Links (8)
| Tool | What it does | Required scope |
|---|---|---|
list_links | List share links, optionally filtered by document_id, dataroom_id, or group_id (dataroom group). Args: document_id?, dataroom_id?, group_id?, limit, cursor. | links.read |
get_link | Fetch one share link by ID. Args: link_id. | links.read |
create_link | Create a share link with access controls. Set exactly one of document_id/dataroom_id. Seed every setting from a saved preset with preset_id — preset values become the defaults, any field you also pass overrides them, and the preset's social-card metadata (OG tags) is applied too (a preset is the only way to set link metadata via the API). Scope to a dataroom group with audience_type: "group" + group_id (dataroom links only; always email-protected). Gate access with password, email_protected, email_authenticated (one-time email code), allow_list/deny_list, or an NDA (enable_agreement + agreement_id). Protect content with enable_watermark (+ watermark_config), enable_screenshot_protection, or enable_confidential_view. Collect viewer info with custom_fields (per-field config; count capped by plan). Host on a verified custom domain with domain + slug (sent together). Other args: name, expires_at, allow_download, welcome_message, enable_notification, show_banner. | links.write (+ documents.read or datarooms.read on the target) |
update_link | Change any of a link's settings; the URL stays the same. Args: link_id + any create_link field except the target and preset_id — including welcome_message, enable_notification, show_banner, and custom_fields (full replace: the array you send becomes the link's complete field set; [] clears them). Switch audience with audience_type + group_id (or back to general). Move on/off a custom domain with domain + slug (pass domain: "papermark.com" to reset to the default). Clear a field by passing null where allowed — expires_at, password, watermark_config, agreement_id, welcome_message. | links.write |
delete_link | Revoke a share link (soft delete; view history is kept). Args: link_id. | links.write |
list_link_views | Raw view events for one link. Args: link_id, limit, cursor. | analytics.read + links.read |
get_link_permissions | Per-item file permission overrides on a dataroom link. Empty when the link has no overrides (viewers see the full dataroom). Args: link_id. | links.read |
set_link_permissions | Replace a dataroom link's per-item permissions (full replace: the payload is the complete desired state; an empty array clears everything and hides every item). Ignored on group-audience links, where the group's permissions win. Args: link_id, permissions[] of {item_id, item_type, can_view, can_download}. | links.write |
Datarooms (10)
| Tool | What it does | Required scope |
|---|---|---|
list_datarooms | Paginated list of team datarooms. Args: limit, cursor. | datarooms.read |
search_datarooms | Substring search by name. Args: query, limit. | datarooms.read |
get_dataroom | Fetch one dataroom by ID. Args: dataroom_id. | datarooms.read |
create_dataroom | Create an empty dataroom. Args: name, description?. | datarooms.write |
update_dataroom | Rename a dataroom or change its settings. Args: dataroom_id + any field to change. | datarooms.write |
delete_dataroom | Delete a dataroom and its links and folders. Documents in the team library are kept. Args: dataroom_id. | datarooms.write |
list_dataroom_documents | Documents attached to a dataroom. Args: dataroom_id, limit, cursor. | datarooms.read |
attach_dataroom_document | Attach an existing team-library document to a dataroom. Args: dataroom_id, document_id, folder_id?. | datarooms.write |
detach_dataroom_document | Remove one document's attachment to a dataroom, by its dataroom-document id (the id from attach/list, not document_id). The document stays in the library and attached to other datarooms. Args: dataroom_id, dataroom_document_id. | datarooms.write |
move_dataroom_document | Move a dataroom document into another folder of the same dataroom; folder_id: null moves it to the root. Args: dataroom_id, dataroom_document_id, folder_id. | datarooms.write |
Dataroom folders (6)
| Tool | What it does | Required scope |
|---|---|---|
list_dataroom_folders | List folders inside a dataroom. Args: dataroom_id, parent_id?, limit, cursor. | datarooms.read |
get_dataroom_folder | Fetch one dataroom folder by ID. Args: dataroom_id, folder_id. | datarooms.read |
create_dataroom_folder | Create a folder inside a dataroom. Args: dataroom_id, name, parent_id?, icon?, color?. | datarooms.write |
update_dataroom_folder | Rename a dataroom folder or change its icon/color. Args: dataroom_id, folder_id + any field. | datarooms.write |
move_dataroom_folder | Reparent a folder within the dataroom. Args: dataroom_id, folder_id, parent_id. | datarooms.write |
delete_dataroom_folder | Delete a dataroom folder. Pass cascade: true to also detach everything nested inside it. Args: dataroom_id, folder_id, cascade?. | datarooms.write |
Dataroom groups (10)
A group is a named audience inside a dataroom: members (by email or
domain) plus per-item view/download permissions. Share a group by
creating a link with audience_type: "group". A new group sees
nothing until permissions are set.
| Tool | What it does | Required scope |
|---|---|---|
list_dataroom_groups | List viewer groups in a dataroom with member/link counts. Args: dataroom_id, limit, cursor. | datarooms.read |
get_dataroom_group | Fetch one group with its domain rules and allow_all flag. Args: dataroom_id, group_id. | datarooms.read |
create_dataroom_group | Create a group. Args: dataroom_id, name, allow_all?, domains? (email domains treated as members). | datarooms.write |
update_dataroom_group | Rename a group or change its domains/allow_all audience rules. A passed domains array replaces the current one. Args: dataroom_id, group_id + any field. | datarooms.write |
delete_dataroom_group | Delete a group, its memberships, its permissions, and every share link pointing at it. Irreversible. Args: dataroom_id, group_id. | datarooms.write |
list_dataroom_group_members | List explicit email members with their membership ids. Args: dataroom_id, group_id, limit, cursor. | datarooms.read |
add_dataroom_group_members | Add members by email (up to 500, idempotent, no invitation emails). Args: dataroom_id, group_id, emails[]. | datarooms.write |
remove_dataroom_group_member | Remove a member by membership id (from list_dataroom_group_members, not the email). Args: dataroom_id, group_id, member_id. | datarooms.write |
get_dataroom_group_permissions | The group's per-item access controls. Items without an entry are invisible to the group. Args: dataroom_id, group_id, limit, cursor. | datarooms.read |
set_dataroom_group_permissions | Upsert per-item permissions (delta: omitted items keep their state; ancestor folders of visible items are auto-shown). item_id is a dataroom-document or dataroom-folder id. Args: dataroom_id, group_id, permissions[] of {item_id, item_type, can_view, can_download}. | datarooms.write |
Visitors (3)
| Tool | What it does | Required scope |
|---|---|---|
list_visitors | Persistent visitor entities (one per email). Args: limit, cursor. | visitors.read |
get_visitor | Fetch one visitor by ID. Args: visitor_id. | visitors.read |
list_visitor_views | All view events from one visitor. Args: visitor_id, limit, cursor. | visitors.read + analytics.read |
Analytics (4)
| Tool | What it does | Required scope |
|---|---|---|
get_document_analytics | Aggregate stats for a document: total views, unique viewers, average read time. Args: document_id, optional since/until. | analytics.read |
get_link_analytics | Same shape, scoped to one share link. Args: link_id, optional since/until. | analytics.read |
get_dataroom_analytics | Aggregate across every document in a dataroom. Args: dataroom_id, optional since/until. | analytics.read |
get_view_analytics | Per-view detail: page-by-page durations, location, device. Args: view_id. | analytics.read |
Uploading an attached file (HTTP transport)
request_document_upload uploads a local file over the HTTP transport,
followed by either upload_document (to create a new document) or
add_document_version (to add a new version to a document that already
exists). Over that transport the server can't reach your filesystem, so
a file you attach in a chat on an HTTP client (e.g. claude.ai) is
uploaded the same way large datasets are handled in MCP generally: a
presigned URL used as a side channel, so the file's bytes never
pass through the model's context window (no base64 blow-up, no token
cost).
The client does this in three steps, the middle one inside its code execution sandbox:
-
request_document_uploadwith the file'sfile_name,content_type, andcontent_length(bytes). Returns:{ "upload_url": "https://s3.../report.pdf?X-Amz-Signature=...", "upload_id": "upload_3xA5v7r8K9mN2pQ4", "required_headers": { "Content-Type": "application/pdf", "Content-Disposition": "attachment; filename=\"report.pdf\"; ..." }, "expires_at": "2026-06-13T13:20:15.092Z", "expires_in": 3600, "curl_command": "curl -X PUT -T <local-file-path> '<upload_url>' -H 'Content-Type: ...' -H 'Content-Disposition: ...'" } -
PUT the raw bytes to
upload_urlfrom the code-execution sandbox. The simplest path is the returnedcurl_command— run it after replacing<local-file-path>with the attached file's path. The signed headers (Content-TypeandContent-Disposition) are already baked into it; these aren't optional, dropping either is a403, which is why a barecurl -Twon't do. To PUT it yourself instead, sendrequired_headersverbatim — e.g. in Python:import requests requests.put(upload_url, data=open(path, "rb").read(), headers=required_headers) -
Redeem the
upload_id:- New document —
upload_documentwith theupload_id(andname, plus optionalfolder_id/create_link) to register the uploaded file as a document. - New version of an existing document —
add_document_versionwithdocument_id+upload_id. The document id, its share links, its dataroom placement, and its version history are preserved. The new version becomes primary in the same transaction; existing links serve it once background conversion finishes, which is usually seconds. Use this to push updated files into a dataroom rather than uploading a second document.
- New document —
The upload_id is single-use and upload_url expires after
expires_in seconds (at expires_at, ~1 hour); if the PUT fails,
request a fresh pair. If the file is already hosted at a public HTTPS
URL, skip steps 1–2 and pass source_url to upload_document /
add_document_version directly.
This applies only to HTTP clients with code execution that can
reach the presigned URL (claude.ai, or Claude Desktop when connected
to the HTTP endpoint rather than stdio, with the analysis/code tool
enabled). Clients without code execution can still upload via
source_url. On the stdio transport none of this is needed —
there upload_document and add_document_version read the local
file_path directly, which is the default for Claude Desktop and
Claude Code.
Allow the storage domain (code-execution egress)
Code-execution sandboxes are network-isolated by default —
outbound requests are blocked unless the destination host is on an
allowlist. The presigned upload_url points at the storage backend
(for Papermark cloud, AWS S3), so if that host isn't allowed the
PUT in step 2 fails — a DNS error, a timeout, or a
"blocked by allowlist" message — even though the request_document_upload
tool call itself succeeded. This is a client-side sandbox setting, not
something Papermark controls.
The host to allow is the hostname of the upload_url returned by
request_document_upload — read it straight from the response. For
Papermark cloud that's an *.amazonaws.com S3 host (e.g.
your-bucket.s3.us-east-1.amazonaws.com); a self-hosted or
custom-domain deployment uses whatever host its upload_url shows.
claude.ai / Claude Desktop
- Pro / Max (personal): Settings → Capabilities → Code execution and file creation → Allow network egress. Personal plans default to All domains, so the upload works with no setup. If you've narrowed egress, switch to package managers and specific domains and add the storage host.
- Team / Enterprise: the org owner controls this in Organization
settings → Capabilities. The default is package managers only,
which blocks the storage
PUT. An owner must choose package managers and specific domains and add the storage host (e.g.s3.amazonaws.com), or select All domains. Members can't change this themselves — ask an owner.
ChatGPT and other clients
There's no per-user egress allowlist for ChatGPT's code interpreter —
its sandbox network is managed by OpenAI. If the sandbox can't reach
the storage host, fall back to uploading from a public source_url
(upload_document with source_url), or use the stdio transport.
Check your client's sandbox-network docs for any equivalent setting.
If a PUT to upload_url fails with a network or allowlist error,
this egress allowlist is the first thing to check — most often on
Team / Enterprise Claude plans, where egress defaults to package
managers only.
Result shapes
Tool results mirror the REST API responses field-for-field; the server invents nothing. Two conventions are worth calling out:
- Every resource carries a read-only
objectfield (document,link,dataroom, …) so the model can tell resource types apart. delete_*tools return the API's deleted-object acknowledgment,{ "id": "…", "object": "…", "deleted": true }, not a custom shape.
Writes are scope-gated
The agent can only call tools whose required scopes are on the
token. A read-only token (documents.read, links.read, …) gives
you a read-only agent. The write tools (create_*, update_*,
delete_*, upload_document, attach_dataroom_document) return a
403 forbidden error envelope, which the MCP client surfaces back to
the model so it can apologize and ask the user.
If you want a stricter sandbox, point the server at a non-production
environment via PAPERMARK_API_URL (and mint a separate token there
with only the scopes the agent needs).
Tool naming
Tool names use snake_case. That's the MCP convention, not a
Papermark choice. They're the same in stdio and HTTP transports.