PapermarkDocs

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, branding, 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. 63 tools are shared verbatim; the rest depend on whether the server can read your disk:

  • Stdio (Claude Desktop, Claude Code) exposes 66 tools: the 63 shared ones plus upload_document, upload_brand_image, and upload_dataroom_branding_image, which read a local file_path directly because the server runs on your machine. add_document_version also accepts a file_path here.
  • HTTP (claude.ai Connectors, ChatGPT Apps) exposes 65 tools: the 63 shared ones plus request_document_upload and an upload_document that takes an upload_id or source_url. A remote server can't read your filesystem, so a local document is uploaded in two steps via a presigned URL (see Uploading an attached file below). The two branding image upload tools do not exist over HTTP; set branding images from a public source_url instead.

Everything else is identical across transports.

Documents (6–7)

ToolWhat it doesRequired scope
list_documentsPaginated list of team documents. Args: limit, cursor, query?.documents.read
get_documentFetch 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_documentsSubstring search by name. Args: query, limit.documents.read
request_document_uploadHTTP 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_documentRegister 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_documentRename a document or move it between folders. Args: document_id, name?, folder_id?.documents.write
delete_documentPermanently delete a document and its links and view history. Args: document_id.documents.write

Document versions (4)

ToolWhat it doesRequired scope
list_document_versionsList every version of a document. Args: document_id.documents.read
get_document_versionFetch one version by ID. Args: document_id, version_id.documents.read
add_document_versionAdd 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_versionMake 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.

ToolWhat it doesRequired scope
list_foldersList folders, optionally under one parent. Args: parent_id?, limit, cursor.documents.read
get_folderFetch one folder by ID. Args: folder_id.documents.read
create_folderCreate a folder. Args: name, parent_id?, icon?, color?.documents.write
update_folderRename a folder or change its icon/color. Args: folder_id, name?, icon?, color?.documents.write
move_folderReparent a folder. Args: folder_id, parent_id.documents.write
delete_folderDelete a folder. Pass cascade: true to also delete everything nested inside it. Args: folder_id, cascade?.documents.write
ToolWhat it doesRequired scope
list_linksList 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_linkFetch one share link by ID. Args: link_id.links.read
create_linkCreate 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), or allow_list/deny_list. 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_linkChange 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, welcome_message.links.write
delete_linkRevoke a share link (soft delete; view history is kept). Args: link_id.links.write
list_link_viewsRaw view events for one link. Args: link_id, limit, cursor.analytics.read + links.read
get_link_permissionsPer-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_permissionsReplace 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)

ToolWhat it doesRequired scope
list_dataroomsPaginated list of team datarooms. Args: limit, cursor.datarooms.read
search_dataroomsSubstring search by name. Args: query, limit.datarooms.read
get_dataroomFetch one dataroom by ID. Args: dataroom_id.datarooms.read
create_dataroomCreate an empty dataroom. Args: name, description?.datarooms.write
update_dataroomRename a dataroom or change its settings. Args: dataroom_id + any field to change.datarooms.write
delete_dataroomDelete a dataroom and its links and folders. Documents in the team library are kept. Args: dataroom_id.datarooms.write
list_dataroom_documentsDocuments attached to a dataroom. Args: dataroom_id, limit, cursor.datarooms.read
attach_dataroom_documentAttach an existing team-library document to a dataroom. Args: dataroom_id, document_id, folder_id?.datarooms.write
detach_dataroom_documentRemove 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_documentMove 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)

ToolWhat it doesRequired scope
list_dataroom_foldersList folders inside a dataroom. Args: dataroom_id, parent_id?, limit, cursor.datarooms.read
get_dataroom_folderFetch one dataroom folder by ID. Args: dataroom_id, folder_id.datarooms.read
create_dataroom_folderCreate a folder inside a dataroom. Args: dataroom_id, name, parent_id?, icon?, color?.datarooms.write
update_dataroom_folderRename a dataroom folder or change its icon/color. Args: dataroom_id, folder_id + any field.datarooms.write
move_dataroom_folderReparent a folder within the dataroom. Args: dataroom_id, folder_id, parent_id.datarooms.write
delete_dataroom_folderDelete 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.

ToolWhat it doesRequired scope
list_dataroom_groupsList viewer groups in a dataroom with member/link counts. Args: dataroom_id, limit, cursor.datarooms.read
get_dataroom_groupFetch one group with its domain rules and allow_all flag. Args: dataroom_id, group_id.datarooms.read
create_dataroom_groupCreate a group. Args: dataroom_id, name, allow_all?, domains? (email domains treated as members).datarooms.write
update_dataroom_groupRename 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_groupDelete a group, its memberships, its permissions, and every share link pointing at it. Irreversible. Args: dataroom_id, group_id.datarooms.write
list_dataroom_group_membersList explicit email members with their membership ids. Args: dataroom_id, group_id, limit, cursor.datarooms.read
add_dataroom_group_membersAdd members by email (up to 500, idempotent, no invitation emails). Args: dataroom_id, group_id, emails[].datarooms.write
remove_dataroom_group_memberRemove 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_permissionsThe 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_permissionsUpsert 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

Branding (9)

A team brand is a named set of viewer settings (logo, banner, colors, welcome message, dataroom layout, link preview); one brand is the team default. A dataroom shows exactly one source, chosen by its mode: team_default, team_brand (pinned brand_id), or custom (its own settings). The model is explained once in the guide Brand a data room.

Seven tools are shared by both transports. The two upload_*_image tools are stdio only; every write tool also accepts images as { "source_url": "https://..." }, which works on both transports.

ToolWhat it doesRequired scope
list_brandsList the team's brands, oldest first; the one with is_default: true applies wherever nothing else is pinned. Call it before any tool that takes a brand_id. Args: limit, cursor.branding.read
get_brandFetch one brand: colors, logo and banner, access-screen text, dataroom layout, link preview. Args: brand_id.branding.read
create_brandCreate a named brand. The team's first brand becomes the default; is_default: true makes a later one the default. Names are unique per team and the brand count is capped by plan. Images: logo, banner, link_preview_image, link_preview_favicon as { source_url }; banner also takes { hidden: true }. Args: name, is_default?, plus any branding field.branding.write
update_brandChange a brand. PATCH semantics: omitted fields stay, null clears. is_default: true moves the team default, which restyles every link and dataroom that follows it. Args: brand_id, name?, is_default?, plus any branding field.branding.write
delete_brandPermanently delete a brand and its images. Pinned links and datarooms fall back to the team default; deleting the default promotes the oldest remaining brand. Args: brand_id.branding.write
upload_brand_imageStdio only. Upload a local image into one of a brand's slots, replacing the current image; the content type comes from the file extension. Args: brand_id, slot (logo | banner | link_preview_image | link_preview_favicon), file_path (.png, .jpg, .jpeg, .ico).branding.write
get_dataroom_brandingA dataroom's mode, pinned brand_id, stored custom branding (kept while the room is pinned to a team brand, deleted by team_default), and effective, what visitors see now. Args: dataroom_id (internal or dr_… id).datarooms.read
update_dataroom_brandingChoose the dataroom's branding source and edit its custom branding. mode: "team_default" follows the team default and deletes the saved custom branding; mode: "team_brand" pins brand_id and keeps the custom branding stored; mode: "custom" uses the room's own branding (seeded from the brand shown now, the first time) and applies any branding fields sent with it. Fields with the wrong mode are a 422; frozen datarooms reject every change. Args: dataroom_id, mode, brand_id?, plus branding fields in custom mode.datarooms.write
upload_dataroom_branding_imageStdio only. Upload a local image into one of the dataroom's custom branding slots. The room must already be in custom mode or the call fails with 409. Args: dataroom_id, slot, file_path.datarooms.write

Branding fields on the write tools: brand_color, accent_color, accent_button_color, apply_accent_color_to_dataroom_view, hide_logo, welcome_message, cta_label, cta_url, card_layout, show_folder_tree, viewer_layout_preset, viewer_header_style, hide_folder_icons_in_main, default_language, custom_link_preview_enabled, link_preview_title, link_preview_description, and the four image slots. Several are plan-gated; a blocked write returns 403 plan_restriction with details.fields naming the offending ones (see Plan gating).

Visitors (3)

ToolWhat it doesRequired scope
list_visitorsPersistent visitor entities (one per email). Args: limit, cursor.visitors.read
get_visitorFetch one visitor by ID. Args: visitor_id.visitors.read
list_visitor_viewsAll view events from one visitor. Args: visitor_id, limit, cursor.visitors.read + analytics.read

Analytics (4)

ToolWhat it doesRequired scope
get_document_analyticsAggregate stats for a document: total views, unique viewers, average read time. Args: document_id, optional since/until.analytics.read
get_link_analyticsSame shape, scoped to one document share link. Dataroom links return 404 — use get_dataroom_analytics for those. Args: link_id, optional since/until.analytics.read
get_dataroom_analyticsAggregate across every document in a dataroom. Args: dataroom_id, optional since/until.analytics.read
get_view_analyticsPer-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:

  1. request_document_upload with the file's file_name, content_type, and content_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: ...'"
    }
  2. PUT the raw bytes to upload_url from the code-execution sandbox. The simplest path is the returned curl_command — run it after replacing <local-file-path> with the attached file's path. The signed headers (Content-Type and Content-Disposition) are already baked into it; these aren't optional, dropping either is a 403, which is why a bare curl -T won't do. To PUT it yourself instead, send required_headers verbatim — e.g. in Python:

    import requests
    requests.put(upload_url, data=open(path, "rb").read(), headers=required_headers)
  3. Redeem the upload_id:

    • New document — upload_document with the upload_id (and name, plus optional folder_id / create_link) to register the uploaded file as a document.
    • New version of an existing document — add_document_version with document_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.

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 object field (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_*, 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.

On this page