MCP Transports: stdio vs Streamable HTTP
There are two transports now, not three, and the one that vanished is the one most tutorials still document. What each is exposed to, why they fail in opposite directions, and how statelessness changed the operational story.
If you learned MCP transports from a tutorial written in 2025, you learned three of them: stdio, HTTP+SSE, and Streamable HTTP. As of protocol version 2026-07-28 there are two, and the one that disappeared is the one most third-party guides still document.
Here is the current shape, from the specification itself.
There are two standard transports
The spec is precise about what a transport is and is not. Protocol semantics are identical everywhere; a transport is a binding that defines how messages are framed and delivered, how request metadata is carried, and how cancellation and termination are signaled. It does not define what the messages mean.
The two standard bindings:
- stdio — newline-delimited JSON-RPC over the standard streams of a client-launched subprocess.
- Streamable HTTP — each message is an HTTP POST to a single MCP endpoint; replies arrive either as a JSON object or as a request-scoped SSE stream.
That is the entire list. Custom transports are permitted, and the spec has sensible advice about them, but nothing else is standard.
What happened to HTTP+SSE
It stopped being a separate transport.
The 2024-vintage design used two endpoints: a long-lived GET that held an SSE stream open for server-to-client messages, and a separate POST endpoint for client-to-server ones. It worked, and it was miserable to operate. A dropped connection lost the session. Load balancers had to pin a client to one instance. Serverless platforms, whose whole premise is not holding connections open, could not host it sensibly.
Streamable HTTP folded the two endpoints into one and inverted the relationship. Now SSE is a reply mode — a single POST can be answered with a plain JSON body, or with an SSE stream scoped to that one request, whichever suits the response. There is no standing connection to lose.
If you see a config with "type": "sse" in it, you are looking at either a legacy server or a stale tutorial. It may still work — implementations carry backwards-compatibility paths — but it is not what you should be writing.
Choosing between them
The decision is almost never close, because the two transports answer different questions.
Use stdio when the server needs your machine. Filesystem access, a local git repository, a database on localhost, a browser to drive. The server is a subprocess your client launched; there is no network, no latency, and no authorization flow — credentials come from the environment, and the spec explicitly says stdio implementations SHOULD NOT follow the OAuth specification.
Use Streamable HTTP when the server needs somebody else's. Anything hosted by a vendor: Stripe, Linear, Sentry, Notion. One deployment serves many clients, updates ship without anyone reinstalling anything, and authorization is OAuth.
A useful framing from the spec's own architecture notes: local stdio servers typically serve a single MCP client,
whereas remote Streamable HTTP servers will typically serve many.
That is a deployment fact with consequences for everything else — state, auth, and blast radius.
Note also that local
and remote
describe where the server runs, not what kind of server it is. The same program can often be either.
The security properties are completely different
This is the part worth internalising, because the transports fail in opposite directions.
stdio is a code execution boundary. The client launches a subprocess with your privileges. The specification's security guidance treats a malicious config entry as the attack, and its examples are unsubtle — a startup command that pipes ~/.ssh/id_rsa to a remote host, or runs sudo rm -rf. Its requirement on clients offering one-click install is that they MUST show the exact command without truncation and get explicit approval first.
So the questions for a local server are: do I trust this package, and have I read the command? Server authors are told to prefer stdio precisely because it limits access to just the launching client — an HTTP server bound to localhost is reachable by every other process on the machine, and by web pages via DNS rebinding.
Streamable HTTP is an authorization boundary. Nothing runs locally, but the server sees whatever your token permits, and now you have OAuth's entire surface to get right: token audience validation, redirect URI matching, issuer checking. The named attacks — confused deputy, token passthrough, mix-up — all live here. MCP security covers them.
Neither is safer.
They are exposed to different things, and the mitigation for one does nothing for the other.
Statelessness changed the operational story
Under 2026-07-28, MCP is a stateless protocol with no connection-scoped session. Every request carries its protocol version and the client's capabilities in _meta fields, so a server can process each request on its own without remembering the last one.
For Streamable HTTP this removes most of what used to make remote MCP servers awkward to run. Any instance can serve any request. Horizontal scaling needs no session affinity. A reconnect costs nothing, because there was nothing to re-establish.
It also relocated a security problem rather than eliminating it. Servers that genuinely need state across requests now mint an explicit handle — a cart ID, a workflow ID — and receive it back as an ordinary tool argument. The spec calls the resulting attack state handle hijacking, and its requirement is unambiguous: servers MUST NOT treat possession of a state handle as authentication. Bind the handle to the authenticated user server-side, key it by a user ID derived from the verified token rather than one the client supplied, and reject it when anyone else presents it.
Cancellation differs too, in a way that matters if you are implementing a client: on stdio you send a notifications/cancelled notification; on Streamable HTTP you close the request's response stream.
If you are writing a server
Support stdio first. It is trivial to implement, it is what every client can consume, and it is what people will try before they trust you with a hosted endpoint.
Add Streamable HTTP when you have something to host — a service with its own accounts, where the alternative is asking every user to run your code locally and hold their own credentials.
Two things that cause the most support tickets, both avoidable:
Never write to stdout on stdio. Anything that isn't a JSON-RPC message corrupts the stream. Log to stderr. The spec's own deprecation note for the logging primitive says exactly this: new implementations should log to stderr on stdio, or use OpenTelemetry.
Bind localhost HTTP servers carefully. If you expose HTTP locally rather than stdio, require an authorization token or use a Unix domain socket. An unauthenticated localhost port is reachable by every process on the machine.
How to build an MCP server covers the rest, and the MCP directory records which transport each published server actually uses — which, more often than the tutorials suggest, is a URL rather than a command.