MCP Transports: stdio vs Streamable HTTP, and What Happened to SSE

The current MCP specification has two standard transports, stdio and Streamable HTTP. The old HTTP with SSE transport is deprecated, yet SSE still appears inside Streamable HTTP. What each one is, when to use which, what the requests look like, and how to move off the old one.

7 min read

MCP has two standard transports today. stdio is for a server running on the same machine as the client, which starts it as a child process and exchanges messages over its standard input and output. Streamable HTTP is for a server anyone can reach at a URL: every message is an HTTP POST to one endpoint, and each reply is either a single JSON object or a short Server-Sent Events (SSE) stream for that one request. “SSE” as a transport of its own means the older HTTP with SSE transport from the 2024-11-05 revision, which used two endpoints and one long-lived stream. It has been deprecated since 2025-03-26, it is eligible for removal, and nothing new should be built on it. So in Streamable HTTP vs SSE, the answer is Streamable HTTP, which still uses SSE as a response format when it needs to.

This page is about the current revision, 2026-07-28, and the practical choice. The history of every revision is in MCP specification changes; if MCP itself is new, start with what MCP is.

What a transport is, and is not

In the transports overview (opens in a new tab) a transport is a binding: it defines how messages are framed and delivered, how request metadata travels, and how cancellation works. It does not change what the messages mean. Both transports carry the same JSON-RPC messages, UTF-8 encoded, and in 2026-07-28 every request carries its own protocol version and client capabilities in _meta. Your tools do not know or care which transport called them. That is why most SDKs let you switch with one argument.

stdio

  • The client launches the server as a subprocess. The server reads JSON-RPC messages from standard input and writes them to standard output, one per line, with no embedded newlines.
  • Standard output is for protocol messages only. Logs go to standard error, which the client may capture, forward or ignore.
  • To cancel a request, the client sends notifications/cancelled. To shut down, it closes the server’s input and waits; a server should exit when its input closes.
  • If the process dies, the client should restart it. Because the protocol is now stateless, lost requests are simply retried against the new process.

Use stdio for tools that belong to one person on one machine: a server that reads local files, wraps a command-line tool, or talks to a database on your laptop. There is no port and no sign-in, because only the client that started the process can talk to it. The cost is distribution: every user installs the server and its runtime, and any credential it needs sits in that machine’s configuration.

Streamable HTTP

The Streamable HTTP binding (opens in a new tab) gives the server a single endpoint, such as https://example.com/mcp, that accepts POST. The rules that matter most:

  • Every client message is its own POST, with an Accept header listing both application/json and text/event-stream.
  • The server chooses per request: a single JSON object, or an SSE stream scoped to that request that carries progress or log notifications and ends with the response. Clients must handle both.
  • Every request POST carries MCP-Protocol-Version, plus Mcp-Method and, for tool calls, resource reads and prompt gets, Mcp-Name. They mirror the body so gateways can route without parsing it, and a mismatch is rejected with 400.
  • Servers must validate the Origin header and answer 403 to an invalid one, should bind to 127.0.0.1 when running locally, and should authenticate every connection.
  • Since 2026-07-28 there is no GET stream, no Mcp-Session-Id, and no resuming a broken stream. Long-lived change notifications come on the response stream of a subscriptions/listen request instead.

Use Streamable HTTP for anything shared: a product’s own MCP server, a team server, anything a chat assistant in a browser has to reach. It is also the transport that carries MCP’s OAuth sign-in, so it is how a person grants an assistant scoped access without handing over a key. How MCP sign-in works covers that from the user’s side.

What the requests look like

These are real exchanges with a small Python server built on version 2.2.0 of the official SDK and started with uv run mcp run server.py --transport streamable-http. First the discovery request a modern client sends, then a tool call.

Terminal (response trimmed)
curl -si http://127.0.0.1:8000/mcp \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -H "MCP-Protocol-Version: 2026-07-28" \
  -H "Mcp-Method: tools/call" \
  -H "Mcp-Name: add_task" \
  -d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{
        "name":"add_task","arguments":{"title":"Tag the release"},
        "_meta":{"io.modelcontextprotocol/protocolVersion":"2026-07-28",
                 "io.modelcontextprotocol/clientCapabilities":{}}}}'

HTTP/1.1 200 OK
content-type: application/json

{"jsonrpc":"2.0","id":2,"result":{"content":[{"text":"Added T-2: Tag the release","type":"text"}],
 "isError":false,"resultType":"complete","structuredContent":{"result":"Added T-2: Tag the release"}}}

The same server answered server/discover with supportedVersions: ["2026-07-28"], its capabilities and its name, and it answered a GET to /mcp from a 2026-07-28 client with 405 Method Not Allowed, as the specification asks. The MCP Inspector connecting in its default legacy mode still worked, through initialize, because the SDK serves both eras from one endpoint. The MCP Inspector shows all of this in its Network tab if you would rather not use curl.

What the old SSE transport was

The 2024-11-05 transport (opens in a new tab) called HTTP with SSE needed two endpoints. The client opened a GET to the SSE endpoint and kept it open for the whole connection. The server’s first event, endpoint, told the client where to POST its messages, and every server message, including every response, came back as a message event on that one long-lived stream. Requests went one way and answers came back another, over a connection that had to stay up for as long as the client was connected.

Streamable HTTP replaced it in 2025-03-26. The 2026-07-28 revision formally classifies HTTP with SSE as deprecated, and its deprecated features list (opens in a new tab) sets the earliest removal at three months after the proposal that reclassified it reaches Final. New implementations should not adopt it and existing ones should migrate.

Which one to use

  • A tool for yourself, on your machine: stdio.
  • A server other people or other machines will use: Streamable HTTP, with authentication from the start.
  • Replies that are quick and need no progress updates: let the server answer with plain JSON. It is simpler to proxy, log and cache.
  • Long-running tools that report progress: an SSE response stream for that request. Behind nginx or a similar proxy, send X-Accel-Buffering: no so events are not held back.
  • A remote server your stdio-only client needs: a small local bridge that speaks stdio to the client and HTTP to the server.
  • The old SSE transport: only to keep existing clients working while you migrate.

Migrating a server off SSE

  1. Add one MCP endpoint, conventionally /mcp, that accepts POST and answers each request with JSON or a request-scoped SSE stream. In most SDKs this is a change of transport name, not of tools: in the Python SDK, mcp.run(transport="streamable-http"), whose running guide (opens in a new tab) says plainly not to build anything new on SSE.
  2. Keep the old SSE and message endpoints running beside it while clients move. The specification allows this, and notes that merging the old POST endpoint into the new one is possible but may add complexity you do not need.
  3. Decide your era. If you support only 2026-07-28, answer GET or DELETE on the endpoint with 405, ignore any Mcp-Session-Id header and do not mint one, and ignore Last-Event-ID. If you support both eras, legacy clients keep their sessions.
  4. Move anything that relied on the server sending requests over the stream, such as elicitation or sampling, to the new pattern where a result asks the client for input and the client retries.
  5. Check Origin validation, localhost binding for local servers, and authentication, then change the URL in your documentation from …/sse to …/mcp.

Migrating a client

A client that must reach both kinds of server tries a POST first. If it gets 400, 404 or 405 without a recognised modern error in the body, it falls back to a GET and waits for an endpoint event, which means it has found an old SSE server. Claude Code already does this: according to its MCP documentation (opens in a new tab), claude mcp add --transport http tries HTTP first and switches to SSE when the server does not accept it, from v2.1.265, and --transport sse remains for connecting over SSE directly.

How fenbs does it

fenbs, a task board where AI assistants are members with roles, runs a Streamable HTTP server at https://fenbs.ai/api/mcp. Every POST gets a single JSON reply, because no fenbs tool needs to stream. A GET asking for an event stream gets 405, which tells the client to carry on without one, and a call with no credential gets 401 with a pointer to the sign-in metadata, so the client can open the browser. A client that cannot open a browser sends a token issued by hand under Settings, with a name, scopes and an optional expiry, as an Authorization: Bearer header instead. A client that speaks only stdio runs the fenbs-mcp bridge (npx -y fenbs-mcp), which relays stdin and stdout to this endpoint with that token in the FENBS_TOKEN environment variable. Setup for each assistant is in the MCP docs.

Related

Watching the traffic yourself: the MCP Inspector. A server in Python, on either transport: building an MCP server in Python. Serving a TypeScript server over HTTP: how to build an MCP server. Adding sign-in: how to add authentication to an MCP server.

Questions people ask.

Is the MCP SSE transport deprecated?

Yes. The HTTP with SSE transport from the 2024-11-05 revision has been deprecated since 2025-03-26, when Streamable HTTP replaced it, and the 2026-07-28 revision lists it as eligible for removal. New servers and clients should use Streamable HTTP.

Does Streamable HTTP still use Server-Sent Events?

It can. A Streamable HTTP server may answer any single request with an SSE stream that carries progress or log notifications before the final response. What changed is that the stream belongs to one request, not to the whole connection, and the server may answer with plain JSON instead.

Can a stdio MCP server be used remotely?

Not directly. The client has to start the process itself, so both run on the same machine. To offer the server to others, serve it over Streamable HTTP; to let a stdio-only client reach a remote server, run a local bridge that forwards to it.

Is WebSocket an MCP transport?

Not a standard one. The specification defines stdio and Streamable HTTP and allows custom transports that keep the same messages. Some clients support a WebSocket connection of their own, but it is not part of the specification.

Start with one thing.

There is nothing to set up first. Write one line and you’ve started.