MCP Transports Explained

A transport is how an MCP client and server physically exchange messages, and it is the single decision that most affects how your server is installed, secured, and scaled. There are two you should build on — stdio and Streamable HTTP — and one you should be migrating away from.

The short answer

MCP separates what a server exposes (tools, resources, prompts) from how the messages get there. The protocol is JSON-RPC either way; the transport decides where the server runs and who can reach it.

TransportServer runsUse whenStatus
stdioAs a child process of the clientThe work happens on the user’s own machine — filesystem, shell, local databaseCurrent
Streamable HTTPAs a network serviceOne server is shared by many users, agents, or teamsCurrent
HTTP+SSEAs a network service, dual-endpointExisting deployments onlyDeprecated

The deciding question is not how complex your server is. It is whose machine the work happens on. A server that reads local files cannot be remote, and a server that fronts a shared production database should not be spawned separately on every laptop.

stdio: the local transport

With stdio, the client launches your server as a subprocess and talks to it over standard input and output. There is no port, no URL, and no network exposure at all. This is what the config blocks in Claude Desktop, Cursor, and Windsurf describe when they specify a command and args:

config.json
{
  "mcpServers": {
    "filesystem": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-filesystem", "/Users/me/projects"]
    }
  }
}

Framing is newline-delimited JSON-RPC: one message per line, in both directions. That framing did not change in the 2026-07-28 revision, which makes stdio the most stable surface in the protocol.

The security model is inherited rather than configured. The server runs as the user who launched the client, with that user’s permissions — which is exactly why a stdio server deserves the same scrutiny as any program you would run directly. Our security guide covers what to check before granting that access.

The one rule that catches everyone: on stdio, stdout belongs to the protocol. Anything else written there — a console.log, a dependency’s startup banner, a progress bar — lands in the middle of the JSON-RPC stream and corrupts it. Diagnostics go to stderr, which the client captures as logs. This is also where the deprecated Logging capability went: write to stderr or emit OpenTelemetry instead.

Streamable HTTP: the remote transport

Streamable HTTP is the current transport for servers that run somewhere else. One endpoint accepts JSON-RPC requests, and each request gets its own response — either a plain JSON body or a stream scoped to that single request.

http
POST /mcp HTTP/1.1
Host: mcp.example.com
Content-Type: application/json
Accept: application/json, text/event-stream

{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"search","arguments":{"q":"postgres"}}}

The Accept header is the negotiation. The client declares it can handle either shape, and the server chooses per request: a fast tool answers with application/json, while a long-running one streams progress events that belong to that one call and end with it.

What matters operationally is what is absent. There is no standalone stream held open between requests, so idle timeouts stop being a protocol concern. There is no session identifier, so nothing needs to be remembered between requests. And because two requests from the same client need not reach the same process, you can scale horizontally without session affinity.

HTTP+SSE and why it was deprecated

The older remote transport split one conversation across two endpoints. The client opened a long-lived GET /sse stream, the server pushed back a session-scoped POST URL, and every subsequent request went to that URL while its response came back down the original stream.

Three consequences followed, and each one became a production problem:

  • The stream had to outlive every request. One socket stayed open for the whole conversation, and had to survive every proxy and idle timeout in between — many of which default to sixty seconds, shorter than plenty of legitimate tool calls.
  • The POST and its stream had to reach the same process. That forced session affinity on the load balancer, which meant deploys dropped in-flight conversations and scaling stopped being transparent.
  • The session identifier was real state. Something had to map that id to an open socket on a specific instance, with all the eviction and recovery questions that implies.

The 2026-07-28 revision marked HTTP+SSE discouraged for exactly this reason. It is deprecated rather than removed, and MCP’s feature lifecycle policy gives deprecated features at least twelve months — but no new server should be built on it. If you are running one today, the migration walkthrough covers running both transports side by side until legacy traffic reaches zero.

Choosing between them

Work down this list and stop at the first one that applies:

  • Does the server need the user’s own machine? Local files, shell access, a database on localhost, the user’s SSH keys or git credentials — that is stdio, and nothing else will do.
  • Do multiple people or agents share one instance? A team-wide connector, an internal API gateway, anything with centrally-held credentials — Streamable HTTP.
  • Do you need to update it without redistribution? A remote server is deployed once; a stdio server is re-installed by every user, on their own schedule, forever.
  • Is a browser the client? Then it is necessarily remote, and CORS becomes part of your problem.

When both would work, stdio is the smaller commitment: no hosting, no authentication surface, no uptime obligation. A remote server is a production service, with everything that implies — see Deploying Remote MCP Servers and Securing & Authenticating Remote MCP Servers.

Failure modes by transport

Most “my MCP server won’t connect” reports resolve to one of these, and which transport you are on narrows it immediately.

SymptomTransportUsual cause
spawn npx ENOENTstdioThe GUI client was launched with a minimal PATH and cannot find the binary. Use an absolute path in command.
Random JSON parse errorsstdioSomething wrote non-protocol output to stdout. Move all logging to stderr.
Connects, then drops after ~60sHTTP+SSEA proxy idle timeout closed the long-lived stream. A reason to migrate rather than tune.
Works locally, stalls in productionStreamable HTTPA proxy or compression layer is buffering the response body. Disable response buffering on the MCP route.
Requests fail after scaling upHTTP+SSEThe POST reached an instance that does not hold the stream. Sticky sessions, or migrate.

For the full diagnostic tree, see MCP Troubleshooting. To inspect a live server’s handshake and tool list directly, the protocol inspector speaks both remote transports.

Supporting both during migration

A server already deployed on HTTP+SSE should keep answering it while adding the modern endpoint. The rule that keeps this maintainable is that only the transport layer knows which era a caller belongs to — both paths hand off to the same tool implementations, and no tool handler ever branches on transport.

Detection belongs per connection or origin, not per call. Clients on the current revision send io.modelcontextprotocol/protocolVersion in every request’s _meta; legacy clients complete an initialize handshake and never send that field. Cache the answer for the life of the process rather than re-probing.

One check worth running against production, since it fails loudly when a proxy is buffering:

Terminal
curl -N -X POST https://mcp.example.com/mcp \
  -H 'Content-Type: application/json' \
  -H 'Accept: text/event-stream' \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'

With -N disabling curl’s own buffering, events should arrive incrementally. If they all land at once when the call completes, something between you and the server is buffering, and your code is not the problem.

Frequently Asked Questions

If the server needs access to the machine the user is sitting at — their filesystem, their shell, their local database, their SSH keys — use stdio. It runs as a child process of the client, inherits that user’s permissions, and never opens a network port. If the server is a shared service that multiple people or agents connect to, use Streamable HTTP. The deciding question is whose machine the work happens on, not how complex the server is.

Next steps: new to MCP entirely? Start with What is an MCP? Writing your first server? See How to Build an MCP Server. Moving one off HTTP+SSE? Follow the migration walkthrough. Or browse all MCP guides and the directory.