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.
When to use transfer links¶
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¶
Step 1 — mint a download link¶
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¶
Step 1 — mint an upload link¶
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_URLrequired. Both tools raise an error ifMARKDOWN_VAULT_MCP_BASE_URLis 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.