I asked myself why did MCP switch from SSE to Streamable HTTP. Here's why.
Recently, I encountered something that I found I had never dealt with before. Setting up an MCP server that wasn't a one click set up or paste-able JSON into a config file.
I had to actually choose whether the server supported Streamable HTTP or SSE(+HTTP). So I found myself trying to understand what separated both protocols and why MCP switched to Streamable HTTP
HTTP+SSE protocol
Before the push for Streamable HTTP as the remote transport standard, MCP used a combination of HTTP with SSE.
It worked with these two endpoints:
GET /<example-sse>:
A client opens up a channel with a GET request using the EventSource browser API which handled all the necessary details to keep the connection open so the server could stream back tool call results/notifications/updates etc. in the same session.
POST /<example-client-message>:
Think of this channel as the client/agents request for tools/list or tools/call. A client could send multiple separate POST requests and expect to get back each response via the same GET channel. For this reason, the server will always responded with 202 Accepted to any POST.
Streamable HTTP protocol.
Streamable HTTP fundamentally works similar, however, in practice promotes higher safety guardrails and is more intentional.
There is a single endpoint that mainly serves POST requests:
POST /mcp:
This works essentially exactly like HTTP+SSE with the client initiating this HTTP request with a JSON-RPC message(request, notification, response).
We won't cover client notifications, responses but since this is bi-directional they're available, and the server always responds with 202 Accepted. You can learn more from the MCP transport specs.
Mainly, we see clients issuing requests, in which case the server will send a single application/json response or initiate an SSE stream with the Content-Type:text/event-stream header.
The server can handle this event-stream in a host of ways such as sending requests, notifications, terminating the connection, and of course SSE events, but will always eventually send a final response to the request.
GET /mcp:
Optionally, the server also supports the GET HTTP method. You may think this is looking very similar to HTTP+SSE, but a key difference is this GET request is not sent with the EventSource API, and communication is unrelated to any ongoing client requests.
Instead, this GET channel is typically used for server initiated communication such as resuming an event-stream started by a previous client POST request, but was interrupted. Now the server just starts redelivering missed SSE events.
A little fun side quest I found was that MCP was actually late to this party.
Lots of LLM chat completion APIs always used a format of Streamable HTTP to stream tokens to clients with the POST request and Accepting a
text/event-streamformat.Google even updated DevTools by popular demand in order to show an 'EventStream' tab to accommodate for SSE event-streams that weren't sent only by the EventSource API.
What's new in DevTools (Chrome 123)
Why did they switch?
- One of the biggest reasons for the protocol switch was a huge authentication concern. Because HTTP+SSE used the
EventSourcebrowser API — whose constructor didn't allow for custom headers — developers were required to unsafely put authentication headers in query params.
GET /sse?token=eyJhb… <- this can be logged in proxies, load balancers, etc.
- EventSource browser API to establish the GET /sse connection bc constructors do not allow passing headers so defaulted to Accept: text/event-stream https://developer.mozilla.org/en-US/docs/Web/API/EventSource/EventSource
- SSE with 1 server meant managing the complexity and overhead of being Stateful in order to map GET responses to respective POST requests.
- Scaling SSE with 2 servers meant load balancing 2 servers was more complicated to have to route each POST request to the right server where the GET connection was active to stream back the tool responses
- SSE had issues with connections dropping. In Streamable HTTP this is addressed. When the server initiates an event-stream it will immediately send an empty SSE event with an
Last-Event-IDso the client has the sufficient info to reconnect.
Overall, Streamable HTTP is easier to implement, more secure, more reliable and more flexible.