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; that URL is then used directly by curl, a browser, or any HTTP client 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 token is consumed. A second 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 token is consumed and the file is available in the vault. 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. Do not use multipart/form-data — the endpoint reads raw bytes. curl's --data-binary flag sends raw bytes and is correct; --form sends multipart and is not.

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 — treat the URL with the same care as a short-lived password.

One-time use (burn-on-success)

A token is consumed on the first successful transfer. A failed request — network drop, size limit exceeded, server error — does not burn the token, so retry is permitted until the TTL expires. This is the "burn-on-success" pattern: the token survives transient failures but disappears once the bytes have moved.

Short TTL

Tokens expire after a configurable TTL. The server default is 1 hour; the configurable ceiling is 24 hours. An expired token is rejected the moment it is used, and stale entries are purged from memory the next time a link is minted — no background thread is needed.

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 tools raise an error on stdio transport because there is no HTTP server to service the request.
  • BASE_URL required. Both tools raise an error if MARKDOWN_VAULT_MCP_BASE_URL is not configured.
  • In-memory token store. Tokens are not persisted to disk. A server restart invalidates all outstanding tokens.

Configuration reference

Variable Default Description
MARKDOWN_VAULT_MCP_BASE_URL 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_MAX_UPLOAD_BYTES 104857600 (100 MiB) Per-upload size cap; exceeded bodies are rejected with HTTP 413

See Configuration for the full details.