DEV Community

Cover image for 7 Services, 0 Upload Solutions: Why MCP File Transfer Fails
Ken Imoto
Ken Imoto

Posted on

7 Services, 0 Upload Solutions: Why MCP File Transfer Fails

I spent a day attaching a single receipt to a freee expense through an MCP client. It never worked. So I went wider: seven services my team actually uses in production, one upload test each.

Result: zero of seven support file upload in the standard MCP way. Four refuse it outright. Three "work" only because the MCP server is reading files off its own local disk — which falls over the moment you containerize the server, and which is not really MCP at all.

This is not a server bug list. The hole is in the protocol.

7 services tested, 0 full solutions for MCP file upload, June 2026

The protocol has 3 content types. File is not one of them.

MCP tool results return one of three content types:

  • TextContent (JSON strings, structured data)
  • ImageContent (base64-encoded PNG / JPEG only)
  • EmbeddedResource (URI reference to a host-owned resource)

There is no FileContent. There never was. An Anthropic engineer answered a public Discussion #1197 thread on this directly:

"I don't think you're overlooking anything, your use-case is currently finicky in the current state of the protocol."

Translated from politeness: this is a real gap, you found it, we know.

SEP-1306 proposed adding a binary elicitation mode in August 2025, where the server hands the client an upload URL and the client POSTs multipart/form-data to it. A later proposal, SEP-2356, adds Tool.inputFiles and requestedFiles so tools can declaratively say "I expect files here" and clients can render a native file picker.

Both are proposals. Neither is in the 2026-07-28 release candidate. The release candidate ships Apps, Tasks, and elicitation form/URL modes — all of which are "do file work outside the core protocol" workarounds. The core itself stays text and base64-image only.

MCP tool result types: TextContent, ImageContent, EmbeddedResource ship — FileContent does not exist in the spec

What each service actually does when you try

freee (accounting) — hard no

freee's REST API takes multipart/form-data receipt attachments. MCP's JSON-RPC layer cannot send that. The MCP server sits between your client and freee, and the protocol it speaks upstream simply has no slot for the bytes. Expense workflows that need receipt attachment do not complete end-to-end. They stop at "describe the receipt in text."

Jira / Confluence — hard no, and worse in containers

The Atlassian official remote MCP server's community response says it plainly: "file uploads or image attachments via the MCP Remote Agent are not supported."

mcp-atlassian Issue #618 is darker. The attachment tool expects a local filesystem path on the MCP server itself:

{
  "attachment_results": {
    "failed": [{
      "filename": "grafana.png",
      "error": "File not found: /home/user/jira-mcp/grafana.png"
    }]
  }
}
Enter fullscreen mode Exit fullscreen mode

Your client has the screenshot. The MCP server is in a Docker container that cannot see your filesystem. Issue tracked, no fix path because the fix is upstream in the protocol.

Notion — on the roadmap, no date

Notion's docs state: "Image and file uploads are not currently supported in Notion MCP, but this is on our roadmap." Roadmap items without dates do what they always do.

GitHub — no, and developers have noticed

github-mcp-server Issue #738 asks for image upload so Claude Code can attach UI screenshots to PRs. The thread is closed-pending-spec. Translation: the GitHub team is also waiting for SEP-1306.

Gmail, Google Drive, Slack — "works" in a way that doesn't count

Third-party Gmail and Drive MCP servers attach files by reading the path off the server's own disk. CData's Slack server has an UploadFile tool that does the same. None of these are transferring bytes through MCP. They are transferring a path string, then the server opens the file locally.

Two problems with that:

  1. The client and server need to share a filesystem. Containerize the server and the model is now describing files it cannot reach.
  2. It's a side channel, not the protocol. If the spec eventually does add FileContent or binary elicitation, every "works today" path will be the wrong shape.

Why the core avoids binary on purpose

Worth steelmanning: the spec authors did not forget. The avoidance is deliberate, for three reasons that are all defensible in isolation and add up to a real product gap.

JSON-RPC is text-shaped. MCP picked JSON-RPC because it's introspectable and trivially debuggable. Binary in JSON means base64, which means a 33% size tax on every byte and a flood of tokens into the model's context window. A 1 MB receipt becomes 1.33 MB of text and probably ~340k tokens of nothing the model can do anything semantic with.

File paths are a known injection surface. Every "let the model send a filename" feature in the last 30 years has had a command injection or path traversal bug filed against it within a year. The MCP team is being conservative on purpose.

The context window is the bottleneck. If file upload were a primitive, every tool would want it, and conversations would routinely embed 5–10 MB of base64. The economics break before the security does.

These are real. They also mean the protocol is making the file problem someone else's. Right now that someone is every MCP server author, and the solutions don't compose.

The workaround that actually ships

Until SEP-1306 or SEP-2356 lands, the only thing that genuinely works end-to-end is the reference-not-bytes pattern:

  1. The MCP client (or the user) uploads the file to object storage — S3, GCS, a presigned-URL endpoint your server owns.
  2. The MCP tool call passes the resulting URL or storage key as a TextContent string.
  3. The MCP server fetches the bytes from storage, hands them to the upstream API (freee, GitHub, Jira), and returns the new attachment ID as text.

Bytes never touch JSON-RPC. The model never sees a 340k-token base64 blob. The server is a thin proxy, and it works identically whether it runs on your laptop or in a container.

This is also, not coincidentally, the shape SEP-1306 will eventually formalize. Building this way now means the migration when (if) the spec lands is roughly: swap your hand-rolled upload endpoint for the spec'd one, keep everything else. It's also where I should have started, instead of spending the first day trying to convince the protocol to carry bytes it was never going to carry.

Five-second checklist before you commit to MCP for a file workflow

  • Does your upstream API need multipart/form-data? If yes, MCP is not your transport — it's your control plane. Wire object storage underneath.
  • Is your MCP server containerized? If yes, do not trust any "works locally" file path tool. It will silently fail in prod.
  • Is the file >1 MB? Don't even consider base64-embedding it in ImageContent. The token bill alone is a no.
  • Do you need an audit trail of which bytes were uploaded? Log the storage key, not the tool call — JSON-RPC didn't carry the bytes anyway.

What to take away

MCP is a great control plane. It is not a file transport. The 2026-07-28 release candidate confirms this for another release cycle. If file work is on the critical path of your agent — expenses, ticket attachments, PR screenshots, Notion uploads — you need object storage and a thin server-side fetcher, today. Treat the protocol's eventual FileContent as a possible future cleanup, not a roadmap dependency.

The seven-service test reads as a vendor problem until you sit with the spec. Then it reads as a spec problem with seven downstream casualties. Worth knowing which one you're solving.

If you want the systematic version — every MCP server I tested, the exact failure modes, OWASP MCP Top 10, and the production reference-not-bytes pattern with code — I wrote a Japanese book on it: MCP Security in Practice (Impress).

References

Top comments (0)