🎯 Launch offer: first 3 clients get 40% off in exchange for a public testimonial — email hello@mcpdone.com with your tier + project.
← All posts

MCP transports: when stdio wins, when HTTP wins

· mcptransportarchitectureclaude-codesecurity

Every MCP server has to decide how it talks to a client. The protocol supports two transports, and the choice is more consequential than most people treat it. It changes your auth model, your attack surface, who can use the server at the same time, and the kinds of bugs you ship.

The four servers we run in production are all stdio, on purpose. The decision was deliberate, not default. Here’s the reasoning, the tradeoffs each way, and the decision matrix we use when scoping a new server for clients.

The two transports, in one paragraph each

stdio — the client (Claude Desktop, Claude Code, etc.) spawns your server as a child process and talks to it over stdin/stdout using newline-delimited JSON-RPC. There is no network listener. There is no port to open. There is no Host header to inspect. The server lives and dies with the client.

HTTP / SSE / streamable-HTTP — your server is an ASGI app (Starlette + uvicorn is the dominant stack) listening on a port. Clients connect to it over HTTP. The server is a long-running process — it survives independent of any individual client and can serve many of them simultaneously. SSE (server-sent events) is the older streaming style; “streamable HTTP” is the newer, simpler one. Both put you on the same network-server architecture and roughly the same threat model.

Same protocol, two materially different deployment shapes.

When stdio wins

All four of our shipped servers fit this profile. They each hold credentials for one thing (Gmail, the local SQLite file, the Twitter API, the search APIs), they each serve one user, and they each die when Claude Desktop / Claude Code restarts. stdio is the right answer every time.

When HTTP / SSE wins

The honest catch with HTTP: you have inherited a real auth problem. Path-based authorization, IP allowlists, and “it’s only on localhost” all have a long history of being insufficient — BadHost is one recent example, but the category is older and broader. If you’re picking HTTP, you’re committing to authentication you would not have needed otherwise.

The decision matrix we actually use

When scoping a new MCP server for a client, four questions decide the transport:

  1. How many simultaneous users? One → stdio. Many → HTTP.
  2. Does the server hold credentials, and how privileged are they? Highly privileged (email send, finance, write access to production) → bias hard toward stdio. Low-privilege read-only (an internal knowledge base) → either works.
  3. Does state need to outlive a single client session? No → stdio is fine. Yes → HTTP.
  4. Is the operator running this for themselves, or operating it on behalf of a team? Self → stdio. Team service → HTTP with real auth.

If you answer stdio on 3 of 4, just ship stdio. Almost every “but what if we want HTTP someday” justification we’ve heard in scoping calls is YAGNI in disguise — and you can migrate cleanly when the third user shows up.

Start stdio, migrate when justified

A useful pattern: build the server with the protocol layer abstracted, so the same handler functions run under stdio today and HTTP tomorrow if the use case grows. With FastMCP this is essentially free — the @mcp.tool() functions don’t care about transport. The transport choice becomes a one-line config when (and only when) you actually need it.

The trap is the opposite path: building HTTP-first because it feels more “real,” then carrying an auth surface and a hosting bill for a tool that will only ever serve one person.

What this means in practice

For most teams scoping their first MCP server: default to stdio. The simplicity gains are large, the attack surface is small, and you can migrate later. Reach for HTTP only when one of the four questions above genuinely lands on the HTTP side.

And if you do choose HTTP — patch your Starlette, put real auth in front of the listener, scope every stored credential to the minimum, and read the BadHost writeup for a concrete example of what the auth surface costs you when one layer of it fails.

Want something similar for your team? See the Build tier — custom MCP servers, shipped in 5 days, fixed price.