From SSE to Streamable HTTP: Understanding MCP Transport the Right Way
If you’ve ever looked at MCP server code and thought:
“Why does this need both GET and POST?
Why does the server reply on SSE instead of POST?
Why is SSE now deprecated but examples still exist?”
— you’re not alone. MCP transport is not obvious, and most explanations online are incomplete.
This article breaks down MCP’s evolution from SSE Transport to Streamable HTTP, explaining exactly how and why it works.
The Core Problem MCP Solves
MCP is not REST.
A tool call in MCP may:
- take minutes
- stream tokens
- pause for human approval
- emit logs anytime
- resume later
This means:
The server must push messages whenever it wants, not only when the client asks.
Classic HTTP cannot do that.
Phase 1 – MCP over SSE (Deprecated Model)
Architecture
GET /mcp → server → client (SSE stream)
POST /messages → client → server (JSON-RPC)
Two physical connections.
Why GET is used
Client sends:
GET /mcp
Accept: text/event-stream
Server responds with:
Content-Type: text/event-stream
Now the connection stays open forever.
But HTTP has a hard rule:
Once GET is opened, the client can never send data on that connection again.
The connection becomes:
Client ───▶ Server (headers only, once)
Client ◀─── Server (stream forever)
Why POST is mandatory
Because the client still must say:
- call tool
- cancel job
- approve human step
So MCP adds a second gate:
POST /messages?sessionId=abc123
What SSEServerTransport('/messages', res) really does
That one line:
const transport = new SSEServerTransport('/messages', res);
Immediately sends this over SSE:
event: initialize
data: { "sessionId":"abc123", "postEndpoint":"/messages" }
The client now knows:
- its sessionId
- where to POST commands
Where replies go
Never on POST. Always on SSE.
POST /messages → HTTP 200 OK (empty)
SSE stream → tool results, logs, tokens, progress
POST only carries questions.
SSE only carries answers.
This pair emulates WebSocket using plain HTTP.
Why SSE Transport Was Deprecated
This model requires:
- two open sockets per client
- session maps in memory
- reconnection logic
- idle pipes consuming infra
It scales badly.
Phase 2 – Streamable HTTP (Modern MCP)
MCP kept streaming — but removed permanent SSE pipes.
Now MCP uses:
POST /mcp ←→ streaming HTTP response
How Streamable HTTP Works
Client:
POST /mcp
Server:
HTTP/1.1 200 OK
Transfer-Encoding: chunked
Then the server keeps writing:
{"progress":10}
{"log":"fetching data"}
{"token":"hello"}
{"final":"done"}
The POST connection itself becomes the temporary stream.
No GET. No sessionId. No idle pipes.
Old vs New Model
| Feature | SSE Transport | Streamable HTTP |
|---|---|---|
| Permanent connection | Yes (GET) | No |
| Client uplink | POST /messages | POST /mcp |
| Server streaming | SSE only | POST response |
| Session store | Required | Not required |
| Infra friendly | ❌ | ✅ |
| Recommended | ❌ Deprecated | ✅ Default |
Why SSE Examples Still Exist
Because:
- Browsers love SSE
- Legacy MCP clients still exist
- It is conceptually simple
- Good for learning MCP internals
But it is no longer the future.
Final Mental Model
Old MCP
“Open a pipe and talk whenever.”
New MCP
“Every task owns its own stream.
When the task ends, the connection ends.”