Skip to content
mnzes

What is the difference between stdio and HTTP in MCP?

ByDiógenes MenezesLearning AI in public

12 min read

MCP defines two transports. Under stdio, the client launches the server as a subprocess and the two exchange newline-delimited JSON-RPC over stdin and stdout. Under Streamable HTTP, the server exposes a single endpoint and every message becomes a POST whose reply is either a JSON object or an SSE stream scoped to that request. Protocol semantics are the same on both1.

That last sentence governs everything that follows. The specification calls a transport a binding: it defines how a message is framed and delivered, how request metadata travels, and how cancellation and termination are signalled. It does not define what the messages mean1.

Transport is the bottom layer under the three roles described in MCP’s architecture. Choosing between the two is almost always the same decision as local versus remote server seen from another angle, and the operational consequences show up in MCP in production.

stdio · local machineStreamable HTTP · networkclientserver(child process)stdinone line = one messagestdout returns, stderr is logclientserver(its own process)POST /mcpreply: one JSON object or anSSE stream tied to that request
Figure 1Protocol semantics are identical on both. What changes is how a message is framed and who owns the life cycle of the process on the other end.

stdio, one line per message

The client launches the server as a subprocess. The server reads JSON-RPC from stdin and writes JSON-RPC to stdout, one message per line, and messages must not contain embedded newlines2.

Three rules on that page account for nearly every stdio server bug you will meet.

The server must not write anything to stdout that is not a valid MCP message. One forgotten debug print() corrupts the framing and the client stops understanding the channel. It is the number one cause of “my server won’t connect”, and it shows up in no log at all, because logging is exactly what broke it.

The server may write UTF-8 strings to stderr for any logging purpose, and the client should not assume that output on stderr indicates an error condition2. That is where your print() belongs.

Every message shares a single channel: there are no per-request streams. That has a consequence for cancellation. With no stream to close, the client cancels by sending a notifications/cancelled notification carrying the request ID, and the server must not send any further messages for that request afterwards2.

Shutdown has a defined sequence: the client closes the child’s input stream, waits for the server to exit, and if it does not exit within a reasonable time, forcibly terminates the process — on POSIX escalating from SIGTERM to SIGKILL, on Windows through TerminateProcess or Job Objects2. The server side has a matching obligation: exit promptly when stdin closes or reads return end-of-file. That is the portable graceful-shutdown signal, and a server that ignores it is a server that only ever dies on SIGKILL.

If the process exits on its own, the client should restart it. Here a direct effect of the protocol going stateless shows up: with no session, in-flight requests are simply lost and the client can retry them against the fresh process. What has to be re-established are any active subscriptions/listen streams2.

One point that usually goes unnoticed: stdio’s framing does not depend on the standard streams. One JSON-RPC message per line over a reliable bidirectional byte stream works the same over a Unix socket or TCP, and the specification recommends that custom transports on such channels reuse that framing rather than invent another1.

Streamable HTTP, one POST per message

The server must expose a single HTTP path, the MCP endpoint, that accepts POST. Every JSON-RPC message the client sends must be a new POST to that endpoint, and the client must include an Accept header listing both application/json and text/event-stream3.

The body is a request or a notification, never a response. If it is a notification and the server accepts it, the reply is 202 Accepted with no body. If it is a request, the server returns either Content-Type: application/json with a single object or Content-Type: text/event-stream with a stream, and the client must support both3.

The SSE stream is what most confuses anyone arriving from the older revision. It is scoped to the request: it carries notifications relating to that call, such as notifications/progress, and ends with the final response. The server must not send independent JSON-RPC requests on it. Long-lived streams do exist, but through an explicit path: the client sends subscriptions/listen, and the response to that request is the stream that stays open delivering the notifications it opted into3.

Two operational recommendations save real pain: include X-Accel-Buffering: no on SSE responses so reverse proxies stop buffering events, and periodically emit an SSE comment line as a keep-alive on long-lived streams so intermediaries do not drop the connection during quiet periods3.

Cancellation here is the mirror of stdio: closing the response stream must be treated by the server as cancellation of that request. Because each request has its own stream, the disconnect is unambiguous3.

The headers the server enforces

Revision 2026-07-28 mirrors body fields into HTTP headers so load balancers and gateways can route without opening the JSON. This is required, not optional.

Every POST must carry MCP-Protocol-Version, and its value must match io.modelcontextprotocol/protocolVersion in the body’s _meta. On a mismatch the server must reject the request with 400 Bad Request and a HeaderMismatch error, code -32020. Also required are Mcp-Method, carrying the method name, and Mcp-Name, carrying params.name or params.uri, on tools/call, resources/read and prompts/get3.

There is also an extension that mirrors tool parameters into headers: an x-mcp-header property in an argument’s schema makes the client send Mcp-Param-{Name} with that value. It exists for routing by region or tenant without body inspection. The spec ships the obvious warning alongside it: do not mark sensitive parameters this way, because header values are visible to every intermediary on the network3.

On security, three requirements. The server must validate the Origin header on all incoming connections and answer 403 Forbidden when it is present and invalid; it should bind only to 127.0.0.1 when running locally rather than 0.0.0.0; and it should implement proper authentication on all connections. Without these, an attacker can use DNS rebinding to interact with your local MCP server from a remote website3.

What changed on 28 July 2026

Revision 2026-07-28 reshaped Streamable HTTP and breaks older clients. The three removals that matter3:

  1. The GET stream endpoint is gone. There is no standalone stream opened by GET to receive server-initiated messages.
  2. Protocol sessions are gone. The Mcp-Session-Id header, and the DELETE that terminated a session, are not part of this revision.
  3. Last-Event-ID resumability is unsupported. If the connection drops mid-call, the request is lost.

A server implementing only this revision that receives older traffic should answer 405 Method Not Allowed to GET and DELETE on the endpoint, ignore Mcp-Session-Id without minting or echoing an identifier, and ignore Last-Event-ID3.

SSE did not die, the transport with that name is on its way out

This is where almost every write-up about MCP gets it wrong, and the claim needs a date attached.

As of 30 July 2026, the deprecated features registry lists the HTTP+SSE transport from revision 2024-11-05 — the two-endpoint one, with a GET that opened the stream and a separate POST for messages — as deprecated since revision 2025-03-26, reclassified under the formal feature lifecycle policy by SEP-2596, with earliest removal three months after that policy reaches Final4. New implementations should not adopt it, and existing ones should migrate.

SSE as a technology remains at the centre of the current transport: whenever a request needs to emit progress before its result, or when the client opens a subscriptions/listen, the response is an SSE stream3. Saying “MCP dropped SSE” is wrong. Saying “the two-endpoint HTTP+SSE transport is deprecated” is right.

The same registry lists the rest of what is on its way out as of 30 July 2026: roots, sampling, logging and dynamic client registration, all deprecated in 2026-07-28, eligible for removal in the first revision released on or after 28 July 20274.

Which one to pick

one usermany userson the user’smachineon aserverstdioStreamable HTTPbind to 127.0.0.1validate OriginStreamable HTTPwith authenticationStreamable HTTPper-request authno state on the instanceTwo-endpoint HTTP+SSE (2024-11-05): deprecated since 2025-03-26
Figure 2The decision has one real branch: the server runs on the user's machine or it does not. Outside the top-left cell, everything is Streamable HTTP with auth.

Server on the user’s machine, serving that one person: stdio. No network, no authentication, no open port, and the client owns the process life cycle. That is what happens when you add a server to your editor’s configuration file.

Server running locally but reached over HTTP, which is a legitimate setup in some shops: Streamable HTTP, bound to 127.0.0.1 and validating Origin. Without those two, any page open in the user’s browser can reach the server.

Server on another machine, with one user or many: Streamable HTTP with authentication. Because there is no protocol session, every request carries its own credential and any replica can answer any request. That is what makes horizontal scaling trivial, and what forces the server to keep no state on the instance.

Where each one breaks

stdio has no isolation. The server runs with the user’s permissions, on the user’s machine, with access to the same filesystem and the same environment variables. There is no sandbox in the protocol. Installing a third-party stdio server is running third-party code.

stdio does not scale past one person. One process per client, on each person’s machine. Ten people using the same server means ten installations to update.

HTTP turned a dropped connection into lost work. With no Last-Event-ID, a tool POST that has been running for forty seconds and loses its connection loses everything. The client has to reissue with a fresh id, and the server needs the operation to be idempotent or reversible — which most servers are not.

Local HTTP without Origin is an open door. It is the one failure mode on this list the specification names outright, with the vector spelled out: DNS rebinding from a remote website.

Cancellation has two semantics and code usually has one. On stdio the client sends a notification; on HTTP it closes the stream. A server written assuming one of the two keeps working after a cancellation on the other. A 2025 measurement study of MCP-enabled agents went as far as recommending task abort mechanisms among its most relevant optimizations5.

What it costs

The transport itself costs little next to context, and the proportion is worth keeping in mind. That same 2025 study measured MCP-enabled interactions and showed that token inflation comes from the combination of system prompt, tool definitions and history, not from the channel the messages travel through5.

What the transport charges you is operations. On stdio, one process per server per user, with memory and a life cycle to manage, and updates that depend on each person’s machine. On HTTP, a service to host, authenticate, monitor and rate limit, plus the fixed cost of TLS and one network round trip per message instead of a write to a pipe.

The deciding sum is rarely about latency. It is about who administers the process.

Where to start

  1. Start with stdio. With no network and no authentication, the development loop is shorter and errors surface in the right place.
  2. Route all logging to stderr before the first line of logic. In Python, print() without file=sys.stderr is the classic bug.
  3. Implement shutdown on closed stdin. A server that only dies on SIGKILL leaves orphaned processes on the user’s machine.
  4. When you move to HTTP, implement all three required headers at once. MCP-Protocol-Version, Mcp-Method and Mcp-Name, validated against the body.
  5. Validate Origin and bind to 127.0.0.1 even in development. The habit is what stops you shipping 0.0.0.0 to production.
  6. Assume any request may be reissued. With no stream resumption and no session, idempotency stopped being a refinement and became a requirement.

Step 6 is the biggest departure from what tutorials written before July 2026 teach.

Footnotes

  1. The transports overview page of revision 2026-07-28 is the source for the definition of a transport as a binding, for the list of the two standard transports, and for the recommendation that custom transports reuse stdio’s framing. Verified on 30 July 2026. 2 3

  2. The stdio transport page of the same revision is the source for line framing, the stdout and stderr rules, cancellation via notifications/cancelled, the shutdown sequence and the behaviour on unexpected termination. 2 3 4 5

  3. The Streamable HTTP page of the same revision is the source for the single endpoint, the Accept and response rules, the per-request stream model, the required headers and the HeaderMismatch error, the Origin and local-bind requirements, and the three removals in this revision. 2 3 4 5 6 7 8 9 10 11

  4. The deprecated features registry of revision 2026-07-28 is the source for the status of the 2024-11-05 HTTP+SSE transport and for the earliest removal dates of roots, sampling, logging and dynamic client registration. 2

  5. Ding et al. (2025) measured MCP-enabled agent interactions and attributed token inflation to the system prompt, tool definitions and history, recommending parallel tool calls and task abort mechanisms. 2

Frequently asked questions

Which transports does MCP define?
Two standard ones as of revision 2026-07-28: stdio, where the client launches the server as a subprocess and they exchange newline-delimited messages over the standard streams, and Streamable HTTP, where each message is a POST to a single endpoint. Protocol semantics are identical; only the framing differs.
What is Streamable HTTP in MCP?
It is MCP's network transport. The server exposes a single endpoint accepting POST, every JSON-RPC request becomes its own POST, and the reply arrives as a JSON object or as an SSE stream scoped to that request. It replaced the two-endpoint HTTP+SSE transport in revision 2025-03-26.
Is SSE deprecated in MCP?
The two-endpoint HTTP+SSE transport from revision 2024-11-05 has been deprecated since 2025-03-26 and appears in the deprecated features registry as of 30 July 2026. SSE itself is still in use: Streamable HTTP answers with an SSE stream whenever a request needs events before its result.
Which MCP transport should I use?
If the server runs on the user's machine and serves that one person, stdio. In every other case, Streamable HTTP with authentication. Running HTTP on localhost is legitimate too, but it requires validating the Origin header and binding to 127.0.0.1, or any open browser tab can reach the server.
Does the Mcp-Session-Id header still exist?
Not in revision 2026-07-28, which removed protocol sessions along with the GET stream endpoint and Last-Event-ID resumability. A server implementing only that revision should answer 405 to GET and DELETE on the endpoint, ignore Mcp-Session-Id without echoing anything, and ignore Last-Event-ID.
Why does my stdio server break when I print something?
Because stdout is the protocol channel. The specification forbids a server from writing anything to stdout that is not a valid MCP message, and one stray print line corrupts the framing. Logging from a stdio server goes to stderr, which the client may capture, forward or ignore.

References

  1. Agentic AI Foundation. Model Context Protocol specification, revision 2026-07-28 — Transports (2026)
  2. Agentic AI Foundation. Model Context Protocol specification, revision 2026-07-28 — stdio transport (2026)
  3. Agentic AI Foundation. Model Context Protocol specification, revision 2026-07-28 — Streamable HTTP transport (2026)
  4. Agentic AI Foundation. Model Context Protocol — Deprecated features registry, revision 2026-07-28 (2026)
  5. Ding, Z., Zhu, M., Liu, Y.. Network and Systems Performance Characterization of MCP-Enabled LLM Agents (2025)arXiv:2511.07426