MCP Transports: stdio, Streamable HTTP, and Legacy SSE
Compare MCP stdio and current Streamable HTTP, and distinguish the 2024 HTTP+SSE and 2025 session-based wire models.
On this page
Version verifiedChecked August 25, 2026 against MCP 2026-07-28. Review method.
The short version
- The current specification defines two standard bindings: stdio for a client-launched process and Streamable HTTP for a remote endpoint.
- Modern Streamable HTTP uses one POST endpoint and has no GET endpoint, MCP session ID, or resumable SSE stream.
- SSE can carry one modern HTTP response; that does not make modern Streamable HTTP the deprecated HTTP+SSE transport.
- Choose a transport by process boundary, then add explicit legacy negotiation only when your compatibility requirements demand it.
An MCP transport answers a narrow question: how does a JSON-RPC 2.0 message travel between a client and a server S001? It does not change what tools/call means, who controls a resource, or whether an operation is allowed. The 2026-07-28 transport overview calls a transport a binding and keeps protocol semantics consistent across bindings S057.
For the roles on either side of that binding, see the MCP server architecture overview. For why native function calling has no equivalent cross-application transport contract, see MCP vs function calling.
The detailed logical-role versus physical-connection guide explains why one client-to-server relationship does not imply one permanent TCP connection or one server process.
The current specification has two standard bindings: stdio and Streamable HTTP. Confusion comes from the fact that MCP has used three different HTTP shapes since launch, and all three use or can use Server-Sent Events. Treat the version as part of the transport name.
The two standard transports solve different process boundaries
| Question | stdio | Streamable HTTP (2026-07-28) |
|---|---|---|
| Who starts the server? | The MCP client launches a subprocess | An operator deploys an independent HTTP service |
| Where does it usually run? | Same machine and user context as the host | Local network, cloud, edge, or another machine |
| Message framing | One newline-delimited JSON-RPC message per line | One JSON-RPC message per HTTP POST |
| Response delivery | Shared stdout byte stream | JSON body or request-scoped SSE stream |
| Authentication | Usually environment/configuration outside MCP HTTP auth | MCP HTTP authorization or another negotiated scheme |
| Cancellation | notifications/cancelled with request ID | Close that request’s SSE response stream |
| Protocol session | None | None |
The choice is normally architectural, not a performance contest. If the host owns the process lifecycle and needs a private local integration, stdio is the natural binding. If many clients need a URL, independent deployment, load balancing, or normal HTTP controls, use Streamable HTTP.
Custom transports are allowed, but they create an interoperability obligation. A custom binding still has to preserve MCP’s request, response, notification, metadata, cancellation, and termination rules. Most products benefit more from supporting the two standard bindings correctly than from inventing a third.
stdio is a strict JSON-RPC subprocess channel
In the current stdio binding, the client launches the server process, writes requests and notifications to its standard input, and reads responses and notifications from its standard output S057. Each UTF-8 JSON-RPC message occupies exactly one line and cannot contain an embedded newline.
Host / MCP client Local MCP server
|--- stdin: request ---------->|
|<-- stdout: response ----------|
|<-- stderr: diagnostic log ----|
The separation between stdout and stderr is a protocol requirement with a practical consequence: printing a friendly startup banner to stdout corrupts the MCP stream. Application logs, debug output, stack traces, and progress intended for a human terminal go to stderr. Only valid MCP messages go to stdout.
All requests share one bidirectional channel, so JSON-RPC IDs correlate responses with requests. Request-scoped notifications such as progress also share it. Notifications from subscriptions/listen include io.modelcontextprotocol/subscriptionId so a client can demultiplex multiple active subscriptions.
Closing the server’s input is the portable graceful-shutdown signal. A server should exit when stdin reaches end-of-file. If it crashes, the client should restart it; in-flight work is lost, and subscriptions must be opened again. Because current MCP is stateless, a restarted process does not need to reconstruct hidden protocol-session data.
stdio is local, but it is not automatically low risk. The subprocess inherits some combination of the user’s filesystem access, environment variables, network reach, and working directory. Use absolute executable paths where appropriate, constrain environment variables, avoid placing secrets in command arguments, and do not assume a downloaded package is safe merely because installation was easy.
Modern Streamable HTTP is one POST endpoint
The current Streamable HTTP binding uses a single MCP endpoint, conventionally /mcp, that accepts POST S057. Every client message is a new HTTP request. For a JSON-RPC request, the server returns either:
application/jsonwith one response object; ortext/event-streamwith notifications related to that request followed by the final response.
Client https://api.example/mcp
|-- POST tools/call ------------------------------>|
| Accept: application/json, text/event-stream |
| MCP-Protocol-Version: 2026-07-28 |
| Mcp-Method: tools/call |
| Mcp-Name: search |
|<-- JSON response OR request-scoped SSE stream ---|
The version appears both in the MCP-Protocol-Version header and in the request body’s _meta; those values must agree. Mcp-Method mirrors the JSON-RPC method on all requests. Mcp-Name also mirrors the tool name, prompt name, or resource URI for tools/call, prompts/get, and resources/read. A server that processes the body must reject missing or inconsistent mirrored headers. This lets gateways route, meter, or authorize using headers without creating two sources of truth.
The endpoint has no modern GET or DELETE behavior. There is no Mcp-Session-Id. The server does not send independent JSON-RPC requests on an SSE response. When it needs client-provided input, it returns an MRTR InputRequiredResult, and the client retries the original method. Long-lived change events use a POSTed subscriptions/listen request whose response remains open.
Modern streams are not resumable with Last-Event-ID. If a request-scoped stream breaks, the in-flight request is lost and any retry uses a new request ID. Durable work belongs in an explicit mechanism such as the Tasks extension, not in hidden transport replay.
SSE is a response format, not one timeless MCP transport
“Does MCP use SSE?” has two valid answers: modern Streamable HTTP may use SSE for a response, while the transport historically named HTTP+SSE is deprecated. The same technology appears in different protocol designs.
In modern MCP, an SSE stream is scoped to the POST request that created it. It can carry progress or log notifications related to that request, then its final result. A subscriptions/listen response may stay open for opted-in change notifications. Servers should disable reverse-proxy buffering and may send SSE comment lines as keep-alives, but Last-Event-ID replay is not supported.
Calling all three designs “SSE transport” erases the exact differences that determine whether a client can connect. When documenting an endpoint, publish the protocol revision and say one of: 2024-11-05 HTTP+SSE, 2025-era Streamable HTTP, or 2026-07-28 Streamable HTTP.
MCP has passed through three HTTP eras
| Protocol era | Endpoint shape | Session behavior | Server-to-client path | Current status |
|---|---|---|---|---|
2024-11-05 | GET SSE endpoint plus a separate message POST endpoint | Connection-oriented lifecycle after initialize | Server messages arrive on the dedicated SSE stream | HTTP+SSE deprecated |
2025-03-26 to 2025-11-25 | Single MCP endpoint supporting POST and optional GET/DELETE | Server could mint Mcp-Session-Id; clients could terminate it with DELETE | Request responses plus standalone GET stream; Last-Event-ID could resume | Legacy compatibility behavior |
2026-07-28 | One POST endpoint | No protocol session | JSON or request-scoped SSE; subscriptions/listen for opted-in changes | Current modern core |
The original 2024-11-05 transport specification documents the two-endpoint design S027. Streamable HTTP arrived in the 2025-03-26 revision S038, and the 2025-11-25 specification is the last revision in that handshake-and-session era S048. The 2026-07-28 changelog removed sessions, GET, resumability, and independent server requests S055. HTTP+SSE’s deprecated status is governed by the project’s feature lifecycle policy S065.
These are protocol versions, not merely SDK generations. An SDK may support more than one era at once. Conversely, seeing a class named StreamableHTTP does not prove that its wire behavior is current. Verify which MCP versions the deployed client and server actually negotiate.
Compatibility should be deliberate and observable
A modern-only implementation can send self-contained requests and reject unsupported versions. A product that must connect to older peers needs an explicit dual-era path. The versioning specification describes negotiation and fallback S056.
For stdio, a dual-era client should probe with server/discover. A successful discovery result identifies a modern server. A recognized modern version error means “select another advertised modern version,” not “fall back.” An implementation-defined error, unknown-method behavior, or reasonable timeout can trigger the legacy initialize path.
For HTTP, a client may attempt a modern request first. Before falling back, it must inspect a 400 response body because modern servers use structured errors for unsupported versions, missing capabilities, and header mismatches. An empty or unrecognized response can indicate a handshake-era server. A separate fallback from Streamable HTTP to the oldest HTTP+SSE endpoint shape should occur only after the modern POST fails with the conditions defined by the binding.
Make the selected era visible in logs and diagnostics. Useful facts include the requested version, server-supported versions, endpoint URL, status code, content type, and whether a fallback occurred. “Connection failed” is not enough when the likely defect is an era mismatch.
Secure the boundary that each transport creates
For Streamable HTTP, the current specification requires servers to validate the Origin header when present and return 403 for an invalid origin. A local HTTP server should bind to 127.0.0.1, not 0.0.0.0, unless network exposure is intentional. Remote endpoints should authenticate requests, authorize them independently, use TLS, apply request limits, and validate that mirrored headers match the body.
For stdio, process launch is the boundary. Treat the command, arguments, package source, executable path, environment, current directory, and inherited permissions as security configuration. Keep diagnostics off stdout and avoid exposing secrets through process listings.
Neither transport changes the larger trust model described in how MCP works: servers and their output remain untrusted, while the host owns consent and policy. Choose the binding that fits the deployment boundary, then implement the exact version rather than a transport name remembered from an older tutorial.