On this page
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:
- stdio: "newline-delimited messages over the standard streams of a client-launched subprocess."
- 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
stdiotransport 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:
- The client's
Acceptheader lists bothapplication/jsonandtext/event-stream. - The body is one JSON-RPC request or notification. The client "MUST NOT send JSON-RPC responses."
- If the server accepts a notification, it returns
202 Acceptedwith no body. - For a request, the server picks
Content-Type: application/json(one JSON object) orContent-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/cancelledinstead.) - The server needs input (sampling, elicitation or roots): it doesn't
send its own request. It returns an
InputRequiredResultwithinputRequests, and the client retries the original request withinputResponses. The spec calls this Multi Round-Trip Requests (MRTR). - Change notifications (for example
notifications/tools/list_changed): the client sends asubscriptions/listenrequest. "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:
- It tries a modern POST. If that works, the server speaks Streamable HTTP at the current revision.
- 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. - An empty or unknown body means an older server. The client falls back
to the 2025
initializehandshake. If the server doesn't speak Streamable HTTP at all (400,404or405with no modern error), the client sends a GET "expecting that this will open an SSE stream and return anendpointevent 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: nowhen 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, letMCP-Protocol-Version,Mcp-Method,Mcp-NameandMcp-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:
- "Servers MUST validate the
Originheader on all incoming connections to prevent DNS rebinding attacks." A badOrigingets "HTTP 403 Forbidden". - "When running locally, servers SHOULD bind only to localhost (127.0.0.1) rather than all network interfaces (0.0.0.0)."
- "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
0 of 5 done
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.