On this page
MCP Streamable HTTP: How It Works, vs stdio and SSE (Nov 2026)

MCP Streamable HTTP is the standard transport for MCP servers you reach by URL. Your AI client sends each message as an HTTP POST to one endpoint, often /mcp. The server answers with plain JSON or a short SSE stream. It replaced the old HTTP+SSE transport. Local servers mostly use stdio instead.

Everything below is quoted from the current MCP specification, revision 2026-07-28. That revision changed Streamable HTTP more than any since it arrived. If you learned MCP in 2025, read what changed in 2026-07-28 first. New to MCP itself? Start with what MCP is.

MCP server transport types: stdio, Streamable HTTP and SSE

A transport is how MCP messages travel between the AI client and the server. The spec's transports page calls it a "binding": it "defines how messages are framed and delivered" but "does not define what the messages mean". As it puts it, "Protocol semantics are identical on every transport." A tool works the same over stdio or HTTP. Only the delivery changes.

The spec names two standard transports:

  1. stdio: "newline-delimited messages over the standard streams of a client-launched subprocess."
  2. Streamable HTTP: "each message is an HTTP POST to a single MCP endpoint; replies arrive as a JSON object or a request-scoped SSE stream."

A third one, HTTP+SSE from the 2024-11-05 spec, is deprecated. Clients and servers "MAY implement additional custom transport mechanisms" as well.

stdio Streamable HTTP HTTP+SSE (old)
Status in the spec Standard Standard Deprecated since 2025-03-26
Who starts the server Your AI client, as a subprocess You or a host; it keeps running Same as Streamable HTTP
How messages travel One JSON message per line on stdin and stdout One HTTP POST per message, all to one URL POSTs to one URL, replies on a separate long-lived SSE stream
Opens a network port No Yes Yes
Who can connect Only the client that started it Anyone who can reach the URL Anyone who can reach the URL
Sign-in Keys in environment variables OAuth or a token on each request OAuth or a token
In a client config "command" and "args" "url", often with "type": "http" "url" with "type": "sse", often ending in /sse
Use it for Tools on your own machine Remote and shared servers Old servers that haven't moved yet

stdio vs Streamable HTTP: which to use

  • Use stdio when the job is on your machine: files, a local database, a browser you watch. The spec's security best practices tell servers meant to run locally to "Use the stdio transport to limit access to just the MCP client." There's no port, so nothing else can connect.
  • Use Streamable HTTP when the server runs somewhere else, or when several people or apps share one server. The spec says "the server operates as an independent process that can handle multiple client connections."
  • A local server that only speaks HTTP can still be fine. The same best-practices page says to "Require an authorization token" or "Use unix domain sockets or other Interprocess Communication (IPC) mechanisms with restricted access". Our local MCP server guide shows how to check what's listening and lock it down.

Sign-in follows the same split. The authorization spec is written for HTTP. stdio servers read their keys from the environment. OAuth for HTTP servers is covered in MCP authentication.

How Streamable HTTP works

The spec's Streamable HTTP page sums it up in three lines:

  • "The server exposes a single HTTP endpoint (the MCP endpoint) that accepts POST." Its example is "a URL like https://example.com/mcp".
  • "The client sends every JSON-RPC request or notification as its own HTTP POST."
  • "The server answers each request with either a single JSON object or a Server-Sent Events (SSE) stream scoped to that request".

The rules for each POST, in plain words:

  1. The client's Accept header lists both application/json and text/event-stream.
  2. The body is one JSON-RPC request or notification. The client "MUST NOT send JSON-RPC responses."
  3. If the server accepts a notification, it returns 202 Accepted with no body.
  4. For a request, the server picks Content-Type: application/json (one JSON object) or Content-Type: text/event-stream (an SSE stream). "The client MUST support both."

When the server picks SSE, it can send progress or log notifications first, then the result. "The final JSON-RPC response SHOULD terminate the stream." The server can't send its own requests on that stream.

The headers on every request

Every POST carries an MCP-Protocol-Version header. Two more are "REQUIRED for compliance": Mcp-Method on all requests, and Mcp-Name on tools/call, resources/read and prompts/get. Here is the spec's own tools/call example:

POST /mcp HTTP/1.1
Content-Type: application/json
MCP-Protocol-Version: 2026-07-28
Mcp-Method: tools/call
Mcp-Name: get_weather

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "get_weather",
    "arguments": {
      "location": "Seattle, WA"
    },
    "_meta": {
      "io.modelcontextprotocol/protocolVersion": "2026-07-28",
      "io.modelcontextprotocol/clientInfo": {
        "name": "ExampleClient",
        "version": "1.0.0"
      },
      "io.modelcontextprotocol/clientCapabilities": {}
    }
  }
}

A real client also sends the Accept header from rule 1. Note the _meta block: each request carries its own protocol version and client capabilities, because there's no handshake any more.

The headers copy what's already in the body "so that intermediaries (load balancers, gateways, observability tooling) can route and inspect requests without parsing the body." The body stays the source of truth. If a header is missing or doesn't match, the server rejects the request with 400 Bad Request and error -32020 (HeaderMismatch). That check is a safety feature: it stops "a load balancer routing on the header value while the MCP server executes based on the body value."

A tool can also ask for one of its arguments to be copied into a header. It marks the argument with x-mcp-header in its inputSchema, and the client sends it as Mcp-Param-{name}. Servers don't have to use this, but clients "MUST support this feature."

Cancelling, asking for input, and listening for changes

  • Cancel: the client closes the response stream. "Closing the SSE response stream MUST be treated by the server as cancellation of that request." (On stdio, the client sends notifications/cancelled instead.)
  • The server needs input (sampling, elicitation or roots): it doesn't send its own request. It returns an InputRequiredResult with inputRequests, and the client retries the original request with inputResponses. The spec calls this Multi Round-Trip Requests (MRTR).
  • Change notifications (for example notifications/tools/list_changed): the client sends a subscriptions/listen request. "The server's response is itself an SSE stream that stays open".

What changed in 2026-07-28: no sessions, no GET stream

The spec flags it at the top of the page: "Revision 2026-07-28 changed the behavior of Streamable HTTP." The two big changes are "Removal of the GET stream endpoint" and "Removal of protocol-level sessions."

2025-03-26 to 2025-11-25 2026-07-28
Start-up An initialize handshake None; each request carries its version and capabilities in _meta
Sessions The server could assign one with the Mcp-Session-Id header, ended with HTTP DELETE None
Server-to-client messages The client could open a standalone SSE stream with HTTP GET No GET; change notifications come on a subscriptions/listen stream
Server requests (sampling, elicitation) Sent as JSON-RPC requests on SSE streams Returned inside an InputRequiredResult
A dropped stream Could resume with Last-Event-ID "Resumable SSE streams via Last-Event-ID are not supported."

The same revision deprecates the protocol's logging feature (SEP-2577): new implementations SHOULD NOT adopt it, and existing ones should move to stderr or OpenTelemetry. See MCP logging vs audit logs.

A server that only speaks 2026-07-28 answers old-style traffic like this: GET or DELETE to the MCP endpoint gets 405 Method Not Allowed. It ignores an Mcp-Session-Id header, and should "not mint or echo session IDs". It ignores Last-Event-ID too.

What that means in practice: no request depends on an earlier one, so any copy of the server can answer it. A server that needs state across calls (a shopping cart, a long workflow) hands back an ID from one tool and takes it as an argument on the next. The security best practices add a warning: servers "MUST NOT treat possession of a state handle as authentication." Tie each handle to the signed-in user.

MCP Streamable HTTP vs SSE

"SSE" means two different things in MCP, and that's most of the confusion.

1. HTTP+SSE, the old transport. In the 2024-11-05 spec, the server "MUST provide two endpoints": "An SSE endpoint, for clients to establish a connection and receive messages from the server" and "A regular HTTP POST endpoint for clients to send messages to the server". When a client connects, the server sends an endpoint event with the URL to POST to. Every reply comes back on that one long-lived stream.

2. SSE as a reply format inside Streamable HTTP. A Streamable HTTP server can answer one POST with a short SSE stream: progress, then the result, then the stream closes.

So the real difference is one endpoint versus two, and a stream per request versus one stream for everything.

HTTP+SSE (2024-11-05) Streamable HTTP (2026-07-28)
Endpoints Two: an SSE stream and a POST URL One, POST only
Replies SSE message events on one long-lived stream JSON, or an SSE stream for that one request
How a call looks to a proxy Part of a stream that stays open An ordinary POST and its response
Status Deprecated The current standard

The deprecation is formal. The spec says HTTP+SSE "has been deprecated since protocol version 2025-03-26". "New implementations SHOULD NOT adopt it; existing implementations SHOULD migrate to Streamable HTTP. It is eligible for removal in a future revision". Claude Code's MCP docs say the same: "The SSE (Server-Sent Events) transport is deprecated. Use HTTP servers instead, where available."

One catch: some tunnels and proxies don't pass SSE well, and Streamable HTTP still uses SSE for streamed replies. Cloudflare's quick tunnels, for one, "do not support Server-Sent Events (SSE)"; see tunnels in our local server guide.

How clients find out which transport a server speaks

You give the client one URL. It works out the rest:

  1. It tries a modern POST. If that works, the server speaks Streamable HTTP at the current revision.
  2. On a 400, it reads the body. A modern server sends a known JSON-RPC error, such as an unsupported-version error that lists the versions it does support. The client then retries with one of those.
  3. An empty or unknown body means an older server. The client falls back to the 2025 initialize handshake. If the server doesn't speak Streamable HTTP at all (400, 404 or 405 with no modern error), the client sends a GET "expecting that this will open an SSE stream and return an endpoint event as the first event". If that event arrives, it's the old HTTP+SSE transport.

The big clients do this for you:

  • Claude Code: "Claude Code tries the HTTP transport first and switches to SSE when the server doesn't accept it. The automatic switch requires Claude Code v2.1.265 or later."
  • VS Code: "VS Code first tries the HTTP Stream transport and falls back to SSE if HTTP is not supported" (configuration reference).

How to connect to an MCP Streamable HTTP server in Claude Code, VS Code and Cursor

Claude Code

From the Claude Code MCP docs:

claude mcp add --transport http notion https://mcp.notion.com/mcp

For an old SSE-only server, their example is:

claude mcp add --transport sse asana https://mcp.asana.com/sse

In .mcp.json, "add "type": "http", "type": "sse", or "type": "ws" to match the endpoint." Scopes, headers and sign-in are in our Claude Code MCP setup guide.

VS Code

In mcp.json, a remote server takes "type": "http" (or "sse" for old servers) and a URL:

{
  "servers": {
    "myServer": {
      "type": "http",
      "url": "https://example.com/mcp"
    }
  }
}

Cursor

Its MCP docs say "Cursor supports three transport methods": stdio, SSE and Streamable HTTP. A remote server in mcp.json needs only a URL, plus headers if it wants a key:

{
  "mcpServers": {
    "server-name": {
      "url": "http://localhost:3000/mcp",
      "headers": {
        "API_KEY": "value"
      }
    }
  }
}

To watch the raw requests, headers and status codes, point the MCP Inspector at the same URL.

Run your own MCP server over Streamable HTTP

  • Python. It's one argument to mcp.run(): transport="streamable-http" in the official SDK, transport="http" in FastMCP. Defaults, host checks and logging are in our Python MCP SDK guide. Before you ship, check your SDK's release notes for which spec revision its HTTP transport follows.
  • Behind nginx or another reverse proxy. Buffering breaks streamed replies. The spec says servers SHOULD send X-Accel-Buffering: no when they open an SSE stream: "This instructs reverse proxies (such as nginx) to disable response buffering". If your proxy or firewall only passes known headers, let MCP-Protocol-Version, Mcp-Method, Mcp-Name and Mcp-Param-* through.
  • Long-lived streams. For subscriptions/listen, the spec encourages a keep-alive: "periodically emit an SSE comment line (a line beginning with a colon, e.g. :\r\n)". For slow tool calls, see MCP tool call timeouts.
  • Many servers behind one URL, with one place for sign-in and logs: that's an MCP gateway.

Streamable HTTP security: Origin, auth and binding

The spec lists three rules for every Streamable HTTP server:

  1. "Servers MUST validate the Origin header on all incoming connections to prevent DNS rebinding attacks." A bad Origin gets "HTTP 403 Forbidden".
  2. "When running locally, servers SHOULD bind only to localhost (127.0.0.1) rather than all network interfaces (0.0.0.0)."
  3. "Servers SHOULD implement proper authentication for all connections."

Its warning: "Without these protections, attackers could use DNS rebinding to interact with local MCP servers from remote websites." In plain words, a web page open in your browser could call the tools on a server running on your laptop. Our local server guide explains how DNS rebinding works and how to add a token. For remote servers, OAuth and its token rules are in MCP authentication. The full build checklist is in how to create an MCP server securely.

Transport safety is one of the four checks behind our grades. A server's HTTP option should use TLS, and plaintext HTTP to a remote server fails the check; see how TrustedMCP scores servers.

Streamable HTTP errors and what they mean

These come from the spec's Streamable HTTP page unless the row says otherwise.

You see It usually means What to do
202 Accepted, empty body A notification was accepted Nothing; it's not an error
400 with error -32020 (HeaderMismatch) A required header is missing or doesn't match the body Check that your proxy passes the Mcp-* headers; update the client library
400 with an unsupported-version error The server doesn't speak the version the client asked for; the error lists the ones it does Update the client or the server so they share a version
400 with no MCP error in the body Probably a 2025-era server that expects initialize Use a client that falls back, or update the server
401 Unauthorized You need to sign in, or the token expired (from the authorization spec) See MCP authentication
403 Forbidden The server rejected the Origin header Connect from your MCP client, not a web page; check the server's allowed origins
404 with error -32601 A modern server that doesn't have that method Client and server support different features; update one
404 with no JSON-RPC body A wrong path, or an old HTTP+SSE server Check the URL (often /mcp); for old servers use "type": "sse"
405 Method Not Allowed on GET or DELETE A 2026-07-28 server got an old-style GET stream or session delete Update the client; the server is fine
421 The official Python SDK's host check (from its docs, not the spec) Add your hostname to transport_security=; see the Python SDK guide

Connection errors such as "-32000: connection closed" are covered in MCP server not working.

Move from SSE to Streamable HTTP in 5 steps

1. Check which transport you run now

A URL ending in /sse, "type": "sse" in a config, or --transport sse in a command usually means the old transport. A GET that opens a stream and sends an endpoint event confirms it.

2. Add the new endpoint next to the old ones

Upgrade your SDK to a release that serves Streamable HTTP, then serve the new endpoint (often /mcp). To keep old clients working meanwhile, the spec says servers can "Continue to host both the SSE and POST endpoints of the old transport, alongside the new 'MCP endpoint'".

3. Keep the three security rules

Check the Origin header, bind to 127.0.0.1 when running locally, and require sign-in. A new endpoint is a new door, so test it the same way you tested the old one.

4. Fix the proxy

Turn off response buffering for the MCP path, let the MCP-Protocol-Version and Mcp-* headers through, and set idle timeouts long enough for your slowest tool call.

5. Move clients over, then retire /sse

Change client configs to "type": "http" or --transport http, and test with the MCP Inspector. When your logs show no more GETs to the old SSE endpoint, remove it.

Need an MCP server that runs over HTTP for your team, with sign-in done right? Tell us what you need to connect.

Frequently asked questions

What is MCP Streamable HTTP?

It's the standard way to run an MCP server over HTTP. The server has one URL, the client sends each message as a POST to it, and the server answers with JSON or a short SSE stream. It arrived in the 2025-03-26 spec "as a replacement for the HTTP+SSE transport" and was reshaped in 2026-07-28.

Is SSE deprecated in MCP?

Yes, the old HTTP+SSE transport is. The spec says it "has been deprecated since protocol version 2025-03-26" and "is eligible for removal in a future revision". New servers should use Streamable HTTP. Claude Code, VS Code and Cursor still connect to SSE servers for now.

What's the difference between MCP Streamable HTTP and SSE?

HTTP+SSE used two endpoints: a long-lived SSE stream for every reply, and a separate URL for POSTs. Streamable HTTP uses one endpoint. Each message is its own POST, and each reply is JSON or a stream for that one request.

Does Streamable HTTP still use SSE?

Yes, as a reply format. A server can answer a POST with an SSE stream that carries progress and then the result. The subscriptions/listen request also returns a stream that stays open for change notifications.

Does MCP Streamable HTTP have sessions?

Not in the current spec. 2026-07-28 removed protocol-level sessions and the Mcp-Session-Id header. Each request carries its own version and capabilities. Servers that need state pass an ID back and forth as a tool argument.

Should a local MCP server use stdio or Streamable HTTP?

stdio, when the server offers it. The spec's security guidance says to "Use the stdio transport to limit access to just the MCP client." If it only speaks HTTP, bind it to 127.0.0.1 and require a token; see our local MCP server guide.

What URL does a Streamable HTTP MCP server use?

Whatever path the server chooses. The spec's example is https://example.com/mcp, and many servers use /mcp, including the Python SDK's default. Old HTTP+SSE servers often end in /sse.

Does Cursor support MCP Streamable HTTP?

Yes. Cursor's docs say it "supports three transport methods": stdio, SSE and Streamable HTTP. Add the server's URL to mcp.json, with headers if it needs a key.

Can MCP run over WebSockets?

Not as a standard transport. The spec only defines stdio and Streamable HTTP, but allows custom transports. Claude Code accepts "type": "ws" in .mcp.json, so a WebSocket server works there. Don't expect it to work in every client.