How an MCP session is born over Streamable HTTP — every byte, from a real local trace.
The whole protocol is simpler than it looks once you see it on the wire. In this lesson
you will drive the opening of a real MCP session by hand with curl, against a
local reference server, and be able to name every header, status code, and field. This is
the foundation everything else (listing and calling tools, then auth)
sits on top of.
MCP is just JSON-RPC messages passed over a transport. Streamable HTTP
is one such transport: the client POSTs a JSON-RPC message to a single URL,
and the server answers with either one JSON object or a stream of them.
That's the entire trick.spec
Before any real work, MCP requires a fixed handshake.spec It is exactly three messages:
initialize request — "here's the
protocol version I speak and what I can do."initialize response — "agreed on
that version; here's what I can do, and your session id."notifications/initialized notification
— "ack, I'm ready." No reply expected.A JSON-RPC request has an id and expects a
reply. A notification has a method but no id,
so by definition it gets no response — that's why step 3 returns a bare 202.
Until step 3 lands, neither side should send normal traffic. Skip the handshake and the server rejects you — we'll prove that below.
initializeHere is the actual request we sent, with the transport-critical headers called out:
The client MUST send Accept: application/json, text/event-stream.
It is telling the server: "I can handle a plain JSON reply or a Server-Sent-Events
stream — your choice." Forget it and a strict server refuses you. This single header is the
pivot of the whole Streamable HTTP design.spec
Our local server chose to answer not with plain JSON but with an SSE stream. Look at the response headers, then the body:
Three things just happened, and each is worth naming:
protocolVersion: 2025-11-25.
If it couldn't speak that, it would reply with a version it prefers and the client
decides whether to continue.speccapabilities object
is a contract: it has tools (with listChanged), resources,
prompts, logging, more. You may only use features that appear here.mcp-session-id is the server saying
"I'm stateful — quote this id on every future request."text/event-stream is a plain-text streaming format: lines like
event:, data:, and id:, with a blank line ending each
event. One HTTP response can therefore carry many MCP messages over time. Here it
carried just one real message (plus an empty priming frame), but the same channel is how a
server later streams progress and notifications. The JSON-RPC payload is always the value
after data: .
initializedNow the client acks. Note the two headers every post-handshake request must carry: the session id and the negotiated protocol version.
A 202 with no body is the correct, expected answer to any notification.
The server accepted it; there is nothing to return because notifications carry no id.
The session is now open for business.
Send a real request without a session id and the server rejects it — a clean demonstration that state, not just authentication, is being enforced:
The spec orders servers to validate the Origin header and
return 403 on a foreign origin, to block DNS-rebinding attacks against local
servers.spec
When we sent Origin: http://evil.example.com, this build happily
returned 200 and a full session. Lesson: "MUST" in a spec is a requirement on
implementers, not a guarantee about the server in front of you. Always verify, never
assume the protection is on. We'll return to this in the security lessons.
The local server is already running on http://127.0.0.1:3001/mcp. Drive the
handshake by hand. From this workspace:
Then answer, from what you see: which content-type did the server choose, and what is the session id? Ask me anything that doesn't line up.
Accept header list both application/json and text/event-stream?notifications/initialized with 202 and no body. Why no body?MCP Specification 2025-11-25 — Base / Transports. The single most important page for this course: it defines Streamable HTTP end to end. Read the "Sending Messages to the Server" and "Session Management" sections — you've now seen on the wire everything they describe.