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 43 tools.
upload_documentreads a localfile_pathdirectly, because the server runs on your machine. - HTTP (claude.ai Connectors, ChatGPT Apps) exposes 44 tools —
the same 43 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_documenttakes 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. 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. Args: file_name, content_type, content_length. See Uploading an attached file. | documents.write |
upload_document | Register a document from exactly one source. Stdio: file_path (local) or source_url. HTTP: upload_id (from request_document_upload) or source_url (public HTTPS URL). Optional: name, folder_id, create_link. | 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 |
Uploading an attached file (HTTP transport)
Over the HTTP 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) -
upload_documentwith theupload_id(andname, plus optionalfolder_id/create_link) to register the uploaded file as a 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 call upload_document with source_url
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 reads 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.
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 | Upload a new version from a public HTTPS URL. Args: document_id, source_url. | 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 (6)
| Tool | What it does | Required scope |
|---|---|---|
list_links | List share links, optionally filtered by document_id or dataroom_id. Args: document_id?, dataroom_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. Args: one of document_id/dataroom_id, optional name, password, expires_at, email_protected, allow_download, enable_confidential_view, enable_watermark, watermark_config. | links.write (+ documents.read or datarooms.read on the target) |
update_link | Change a link's name, expiry, password, access controls, or watermark. Args: link_id + any field to change. Pass watermark_config: null to clear an existing config. | 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 |
Datarooms (8)
| 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 |
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 |
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 |
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.