cd /news/ai-agents/mcp-s-file-transfer-draft-keeps-byte… · home › topics › ai-agents › article
[ARTICLE · art-168325] src=dev.to ↗ pub= topic=ai-agents verified=true sentiment=↑ positive

MCP's file transfer draft keeps bytes out of your model's context: 5 mechanics and 3 open security questions

The MCP maintainers published the first draft of the io.modelcontextprotocol/files extension, which moves file contents out of the model's context by passing only metadata (id, name, mime type, size, optional SHA-256 digest) in tool arguments while bytes travel over plain HTTPS PUT/POST to a server-specified upload target. Servers advertise file arguments via an x-mcp-file schema annotation, and the flow returns an upload_required result with a URL, headers, expiry, credentials mode and requestState before the client re-calls the tool. The draft targets protocol revision 2026-07-28 and later, with maintainers expecting it to stabilize before year's end, though open security questions remain.

by read6 min views2 publishedNov 12, 2026

Yesterday the MCP maintainers published the first draft of an extension for moving files between clients and tools. If you build MCP servers, this is the piece you have been hand-rolling: base64 blobs in tool arguments, bespoke presigned-URL conventions, CSVs pasted into prompts. The extension, identified as io.modelcontextprotocol/files, wants to standardize all of it. It works with protocol revision 2026-07-28 and later, and the maintainers expect it stable before the end of the year.

I read the full announcement and the draft's mechanics. Here is what actually changes for a server author, and where the design still has open holes.

Every scenario in the draft (save a PDF to cloud storage, pull a big CSV into a sandbox, upload a ZIP deck to a third-party service) shares one property. The model needs to know things about the file, but it never needs to read every byte.

So the extension moves file contents out of band. Tool arguments carry metadata only: an id, a name, a mime type, a size, optionally a SHA-256 digest. The actual bytes travel over plain HTTPS PUT or POST, from the client directly to an upload target the server specifies. On the way back, downloads ride an HTTPS GET against a URL in the result's _meta.

The consequence for your context window is the interesting part. The model makes the tool call and gets the result. Neither the file contents nor the upload URL ever enter its context. We measured 41k tokens of MCP overhead before the first prompt on a typical server set. Any pattern that keeps bulk data out of the model's view is worth adopting early.

A server advertises a file argument by adding x-mcp-file to the argument's input schema, the same pattern as x-mcp-header:

"file": {
  "type": "object",
  "x-mcp-file": { "accept": ["application/pdf"], "maxSize": 157286400 },
  "properties": {
    "id": { "type": "string", "minLength": 1, "maxLength": 128 },
    "name": { "type": "string" },
    "mimeType": { "type": "string" },
    "size": { "type": "integer", "minimum": 0 },
    "data": { "type": "string" }
  },
  "required": ["id", "mimeType", "size"]
}

The client, not the model, fills this argument in. Small files can still go inline as base64 in data, capped by a maxInlineBytes value the server advertises in its server/discover response. Anything bigger goes through the out-of-band flow below.

There is a graceful degradation path: a client that does not support the extension ignores x-mcp-file and treats the argument as an ordinary object. If the client never declared extension support in its tools/list request, the server should omit the tool or offer an alternative that takes a URL.

Before anything is sent, the host gets the user's approval, or agent policy decides when no human is present. Then the client calls the tool with metadata only:

{
  "method": "tools/call",
  "params": {
    "name": "save_to_folder",
    "arguments": {
      "folderId": "fld_8c1a",
      "file": { "id": "file-1", "name": "Q3 report.pdf",
                "mimeType": "application/pdf", "size": 41204133 }
    }
  }
}

This ordering is deliberate. The server can check permissions and storage quota before any bytes move. You do not upload 40 MB only to learn the destination folder is read-only. If checks pass, the server returns the extension's new upload_required result type, containing one upload target per file: a URL, headers, an expiry, a credentials mode, and a requestState value.

After the client PUTs the file and gets a 2xx, it calls the same tool again with the same arguments plus requestState returned unchanged.

That value represents the unfinished job. The server packages the details it needs to continue, protects the value against tampering, and hands it back. The client does not need to understand it. This fits the stateless direction MCP took in the July 28 revision, where persistent sessions went away. Any server instance that can verify the value and reach the uploaded file can finish the job. For tools where a second run would create a duplicate, the server marks the requestState as used before running, and all instances share that record.

This is the Multi Round-Trip Requests pattern, which shipped recently, applied to file transfer. If you write servers against the 2026-07-28 revision, the shape will look familiar.

Downloads are simpler because they do not need a retry. The tool completes normally, and the download target travels in the result's _meta under the extension's id:

"result": {
  "resultType": "complete",
  "content": [{ "type": "text", "text": "Exported q3-orders.csv." }],
  "_meta": {
    "io.modelcontextprotocol/files": {
      "downloads": [{
        "name": "q3-orders.csv", "mimeType": "text/csv",
        "size": 209715200,
        "url": "https://download.example.com/d/01JD3V9P4Q?sig=...",
        "credentials": "omit",
        "expiresAt": "2026-11-04T18:30:00Z"
      }]
    }
  }
}

The client only sees download targets if it declared download support in the tool call. After fetching, it checks byte count against size, and against digest if one was supplied. A mismatch means discard the file.

One line in the announcement deserves emphasis: a matching digest confirms the bytes match the hash, not that the file is safe. A malicious server can hand you a harmful file and its correct digest. Downloaded files stay untrusted input.

A transfer target may be a third-party storage service, not the MCP server itself. So every target carries a credentials mode. omit means the client attaches no MCP token, cookies, or environment credentials; the target authorizes itself, typically via a presigned URL. include means the client attaches its MCP credential, but only to a path under the client's own MCP address or the server's origin, which helps gateways and sandboxed clients.

The draft also requires clients to refuse redirects on transfers and reject targets that supply an Authorization header. Both close trivial exfiltration paths.

The maintainers are explicit that this is a draft that may change shape, with no reference implementation yet. Three things are genuinely undecided:

_meta is the current choice, and it is an open design question. If you have burned your fingers on result-shape drift, comment on the draft now rather than after it stabilizes.credentials: "omit", a malicious server can still direct an upload to any public endpoint it controls. The host approves the transfer before it knows the target, so approval never covers the specific destination. The draft recommends hosts restrict omit targets to an allowlist of permitted origins. Whether that restriction should be mandatory is an open question. Nothing breaks today. The extension is experimental, optional, and unimplemented in the reference SDKs. But the direction matters: MCP is standardizing the last big chunk of bespoke server glue, and it is doing so in a way that keeps both the bytes and the URLs out of model context. If you run MCP servers for agents, read the draft, and if your tools shuffle files, your feedback window is now, before the shape hardens near year end. The maintainers take feedback in the extension repository's issues and the working group's Discord discussion.

For broader context on how the 2026-07-28 stateless revision changed server authoring, see MCP C# SDK 2.0 is stable: HTTP is stateless by default.

── more in #ai-agents 4 stories · sorted by recency
── more on @model context protocol 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
→ Live at https://your-agent.zahid.host ✓
Get free account → Pricing
from €0/mo · no card required
LIVE [news/mcp-s-file-transfer-…] indexed:0 read:6min 2026-11-12 · —