Skip to content

One-Time Transfer Links

One-time transfer links let vault files move between the server and a browser or another service without passing the bytes through the LLM context window. The LLM calls a tool to mint a short-lived capability URL. Clients such as curl, a browser, or any HTTP client then use that URL directly to download or upload the file.

HTTP/SSE transport only

Transfer links require a running HTTP or SSE server with MARKDOWN_VAULT_MCP_BASE_URL configured. They are not available on stdio transport.

Use transfer links when you need to move a file between the vault and something outside the conversation:

  • Downloading a large PDF, image, or binary attachment to a local machine without base64-encoding it through the LLM.
  • Uploading a file produced by an external tool (a script, a browser export, a compiled artifact) into the vault without reading it into context first.
  • Handing off bytes to another service (a data pipeline, a browser-based viewer, or a CI job) that can speak HTTP but does not have MCP client support.

In every case the file bytes travel directly over HTTP; the LLM receives only the metadata (url, path, expires_at, expires_in_seconds).

Download walkthrough

Ask the LLM (or call the tool directly):

create_download_link(path="reports/q1.pdf", ttl_seconds=600)

The tool returns:

{
  "url": "https://mcp.example.com/transfer/AbCdEfGhIjKlMnOpQrStUvWxYz01234567890ab",
  "path": "reports/q1.pdf",
  "expires_at": "2026-06-05T14:10:00+00:00",
  "expires_in_seconds": 600
}

The URL is a one-time capability URL. No authentication header is required to fetch it.

Step 2: fetch the file

On any machine that can reach the server:

curl "https://mcp.example.com/transfer/AbCdEfGhIjKlMnOpQrStUvWxYz01234567890ab" \
     -o q1.pdf

Or open the URL in a browser; the server sets Content-Disposition: attachment so the browser downloads rather than renders.

After a successful download the link is grace-settled: its remaining lifetime shrinks to MARKDOWN_VAULT_MCP_TRANSFER_GRACE_TTL_S (default 60 seconds) so a stalled transfer can retry. Once that window passes, a further request returns HTTP 404.

Token lifetime

If you do not specify ttl_seconds, the server uses MARKDOWN_VAULT_MCP_TRANSFER_TTL_DEFAULT_S (default 3600 seconds / 1 hour). The maximum is MARKDOWN_VAULT_MCP_TRANSFER_TTL_MAX_S (default 86400 seconds / 24 hours). Shorter TTLs reduce the exposure window if the URL is accidentally shared.

Upload walkthrough

create_upload_link(path="assets/new-diagram.png")

The tool returns:

{
  "url": "https://mcp.example.com/transfer/ZyXwVuTsRqPoNmLkJiHgFeDcBa98765432109zy",
  "path": "assets/new-diagram.png",
  "expires_at": "2026-06-05T15:00:00+00:00",
  "expires_in_seconds": 3600
}

The destination path in the vault is fixed at link-creation time; the uploader cannot change it.

Step 2: upload the file

Send the raw file bytes as the request body using POST:

curl -X POST \
     --data-binary @new-diagram.png \
     "https://mcp.example.com/transfer/ZyXwVuTsRqPoNmLkJiHgFeDcBa98765432109zy"

PUT is also accepted as an alias for POST. Both behave identically.

After a successful upload the file is available in the vault, and the link is grace-settled in the same way as a download. The FTS index is updated and the git-commit callback fires (when git integration is configured).

Raw body, not multipart

Send the file bytes directly as the request body. Avoid multipart/form-data; the endpoint reads raw bytes. curl's --data-binary flag sends raw bytes and is correct; --form sends multipart and will be rejected.

Security model

Capability-URL authorization

The /transfer/{token} route is mounted outside the server's auth middleware. The token itself is the authorization: a URL-safe random string generated by secrets.token_urlsafe(32) (43 characters, 256 bits of entropy). Anyone who holds the URL can perform the transfer, so treat the URL with the same care as a short-lived password.

One-time use (grace-settled)

A valid token grants exactly one operation on one vault path. On success the link is grace-settled rather than burned outright: its remaining lifetime shrinks to MARKDOWN_VAULT_MCP_TRANSFER_GRACE_TTL_S (default 60 seconds). A transfer that was served but stalled part-way can still reclaim the link instead of being stranded by a spent one.

A transient failure (network drop, size limit exceeded, server error) releases the reservation with the full remaining TTL, so the transfer can be retried until expiry. If a handler crashes mid-transfer, its in-flight reservation frees itself after MARKDOWN_VAULT_MCP_TRANSFER_LEASE_S (default 60 seconds) and the token becomes claimable again.

Short TTL

Tokens expire after a configurable TTL, and that TTL is the security-relevant bound. The server default is 1 hour; the configurable ceiling is 24 hours. An expired token is rejected the moment it is used.

Per-upload size cap

The upload route reads the request body up to MARKDOWN_VAULT_MCP_TRANSFER_MAX_UPLOAD_BYTES (default 100 MiB). A body that exceeds this limit is rejected with HTTP 413 and the token is returned to available so a smaller upload can retry.

Fixed destination (upload)

The upload destination path is validated for path traversal and allowed extension at link-creation time. The uploader cannot override the destination. The write path re-validates on write as a defense-in-depth measure.

Limitations

  • No HTTP range requests. Each GET reads the entire file into memory and streams it. Partial downloads (Range: header) are not supported.
  • Raw body only. The upload endpoint does not parse multipart/form-data. Send raw bytes.
  • HTTP/SSE transport only. The route and both tools are registered only on an HTTP or SSE transport, because the /transfer/{token} route needs an HTTP server. On stdio they are absent from the tool listing rather than present and failing.
  • BASE_URL required. The same registration also requires MARKDOWN_VAULT_MCP_BASE_URL, which supplies the public origin for the capability URL. Without it the tools do not appear.

Persistence

The token store is KV-backed (MARKDOWN_VAULT_MCP_KV_STORE_URL, on-disk state by default), so links minted before a restart remain valid afterwards for the rest of their TTL.

Configuration reference

Variable Default Description
MARKDOWN_VAULT_MCP_BASE_URL (none) Public base URL used to construct the capability URL. Required for transfer tools
MARKDOWN_VAULT_MCP_TRANSFER_TTL_DEFAULT_S 3600 Default token lifetime in seconds when ttl_seconds is omitted
MARKDOWN_VAULT_MCP_TRANSFER_TTL_MAX_S 86400 Maximum permitted TTL; requested values above this are clamped
MARKDOWN_VAULT_MCP_TRANSFER_GRACE_TTL_S 60 Grace window after a successful transfer. The token's remaining lifetime shrinks to this, so a stalled or retried transfer can reclaim the link
MARKDOWN_VAULT_MCP_TRANSFER_LEASE_S 60 Reclaim window for an in-flight reservation. A crashed handler's token becomes claimable again once this lease lapses
MARKDOWN_VAULT_MCP_TRANSFER_MAX_UPLOAD_BYTES 104857600 (100 MiB) Per-upload size cap; exceeded bodies are rejected with HTTP 413

See Configuration for the full details.