MCP stdio vs HTTP Transport: When to Pick Each for Claude Code
A practical comparison of MCP stdio and HTTP transports for Claude Code skills — local-only reach, auth model, debuggability, and the failure modes that bite in production.
MCP stdio vs HTTP Transport: When to Pick Each for Claude Code
Every MCP server you wire into Claude Code makes the same first decision: stdio or HTTP. The protocol itself is identical above the transport layer, so the choice rarely surfaces as a feature comparison. It surfaces as a deployment constraint \u2014 who runs the server, where it lives, and how Claude Code reaches it. Picking wrong means rewriting the server six weeks later when the second user shows up or when the laptop sleeps mid-tool-call.
This piece walks the two transports against the failure modes that actually appear: process lifecycle, auth, multi-client reach, and the debuggability gap that nobody mentions until they hit it.
The one-line decision tree
Pick stdio when the MCP server is a single-user local tool whose blast radius is the operator's own machine. Pick HTTP when more than one client needs it, when it must survive across machines, or when it needs to be addressable from a remote agent.
Everything below is the longer version of that sentence.
What stdio actually is
A stdio MCP server is a subprocess. Claude Code spawns it, talks JSON-RPC over the child's stdin and stdout, and kills it when the session ends. The configuration looks like this:
{
"mcpServers": {
"pixel-plugin": {
"command": "uvx",
"args": ["pixel-mcp-server@latest"],
"env": {
"ASEPRITE_PATH": "/Applications/Aseprite.app/Contents/MacOS/aseprite"
}
}
}
}
No port, no URL, no TLS. The transport is two file descriptors. Authentication is "you launched the process, so you're authorized" \u2014 the same trust model as running make. The server inherits the parent process's environment, including secrets you didn't realize you were sharing.
This is unbeatable for local-only tools. A pixel-art automation server that drives Aseprite has no business listening on a port. A skill that reads from the user's own filesystem doesn't need an auth header \u2014 the OS already enforces the boundary. Spawning is fast (under 200ms for a Python server with a warm import cache), and the lifecycle is bounded by the Claude Code session.
The cost is reach. A stdio server is visible only to the process that spawned it. Two Claude Code instances on the same laptop each get their own copy. A scheduled cron job on a separate machine cannot talk to it. If the server needs to share state with anything outside the spawning shell, stdio stops fitting.
What HTTP gives you
HTTP transport in MCP is plain HTTP/1.1 with Server-Sent Events for streaming responses, or the newer Streamable HTTP variant that multiplexes both directions over one connection. Configuration shifts to a URL:
{
"mcpServers": {
"memory-store": {
"type": "http",
"url": "https://internal-tools.example.com/mcp",
"headers": {
"Authorization": "Bearer ${MCP_TOKEN}"
}
}
}
}
Now the server is a long-running daemon. It can serve many clients, persist state in a database, talk to other services on its own network, and survive the operator closing their laptop. Auth becomes a real concern \u2014 bearer tokens, mTLS, or OAuth, picked deliberately rather than inherited from the shell. The MCP spec leaves auth implementation to the server; the official guidance recommends OAuth 2.1 for remote servers, but plenty of internal deployments ship with static bearer tokens behind a VPN.
The tradeoff inverts the stdio one. You gain reach and lose blast-radius containment. A misconfigured HTTP MCP server with a leaky token grants tool execution to anyone who finds the URL. The MCP host (Claude Code) cannot tell whether the server on the other end is benign \u2014 it trusts whatever tool definitions arrive.
Auth: where the real divergence lives
Stdio auth is implicit: process ownership. There is no token, no rotation, no revocation. Killing the parent kills the server. Compromising the server requires already having code execution on the operator's machine, at which point auth is moot.
HTTP auth is explicit and load-bearing. Three patterns dominate in production:
- Static bearer token in a private network. Token lives in the Claude Code settings file, server validates on every request, network reachability is gated by VPN or Tailscale. Easy to ship, easy to leak, fine for solo operators.
- OAuth 2.1 with PKCE. Required for any MCP server exposed on the public internet that holds user-scoped data. The MCP spec defines the auth flow here. Heavier to implement, but it's the only honest choice for multi-tenant servers.
- mTLS between known services. Useful when both ends are infrastructure you control and you want to avoid token-handling code entirely. Certificate rotation becomes the operational burden.
Stdio dodges all three. That dodge is the actual reason stdio servers ship faster \u2014 there is no auth surface to design.
Debuggability \u2014 the gap nobody mentions
The transport choice changes how you debug a misbehaving tool by 10\u00d7.
A stdio server's logs are a write to stderr away. The child process inherits the parent's terminal, so print(..., file=sys.stderr) lands where the operator can see it during development. JSON-RPC malformed-message errors surface inside the same shell. Restarting means killing one process.
An HTTP server's logs live wherever you deployed it. Adding a print requires a deploy. JSON-RPC parsing errors only show up if you've wired structured logging to a sink you actually read. Restarting affects every connected client. The MCP Inspector tool from Anthropic (github.com/modelcontextprotocol/inspector) helps for both transports, but the iteration loop on HTTP is dominated by deploy latency, not by inspect-and-fix.
A useful mitigation: design the server's business logic as a pure module, and wrap it in both transports. Run stdio locally for iteration, run HTTP in production. The MCP Python SDK supports this with one decorator switch \u2014 the same FastMCP server can be served by mcp.run("stdio") or mcp.run("streamable-http"). Build for stdio first, port to HTTP once the tool definitions stabilize.
Numeric reality check
For a Python MCP server with three tools, the cold-start gap is meaningful: roughly 150\u2013400ms to spawn stdio versus a single sub-millisecond round-trip on a warm HTTP keepalive. That sounds like an HTTP win, but the HTTP path adds a TLS handshake on the first call (50\u2013200ms depending on resumption) and a DNS lookup. After the first call, HTTP wins on per-call latency by ~50ms because there's no process spawn \u2014 but stdio servers are spawned once per session, not once per call, so the per-call latency converges to roughly equal once the process exists.
The latency conversation is usually a red herring. The real cost of stdio is one process per Claude Code instance. The real cost of HTTP is one deployment to maintain. Pick the one whose cost you'd rather pay.
When stdio is genuinely wrong
Three scenarios force HTTP regardless of preference:
- Shared state across sessions. A memory server that lets multiple Claude Code instances read the same vector store cannot live in stdio \u2014 each spawn would be its own isolated process. HTTP-backed shared state is the only model that works.
- Remote agents calling in. If a daemon on a server somewhere needs to invoke MCP tools, the stdio model has no path. HTTP with bearer auth fits cleanly.
- Tools that own non-local resources. A server that wraps an Odoo API or talks to a managed Postgres needs to live close to those services, not on the operator's laptop. HTTP wins by default.
If none of those apply, stdio is the lower-friction choice. The MCP ecosystem skews stdio-first for a reason \u2014 most useful tools are local automation, and the auth-free model means contributors can publish servers without solving deployment.
A recommended split
Ship stdio for anything an individual operator runs alone. Pixel art automation, filesystem indexing, local LLM helpers, dev-loop tooling \u2014 all stdio. The blast radius matches the audience.
Ship HTTP for anything backing a shared service: memory stores, agent registries, internal API wrappers, anything that needs to outlive a single Claude Code session. Pair it with OAuth 2.1 if it's reachable from the public internet, with a static bearer behind a VPN if it isn't.
When in doubt, build stdio first. Porting to HTTP later is mostly a transport swap if the business logic stays separate from the transport handler \u2014 about an afternoon of work for a well-structured server. Building HTTP-first and trying to backport to stdio is harder because HTTP-first code tends to accumulate auth, request-ID tracking, and session state that stdio doesn't need.
References: