MCP Protocol Internals · Reference

MCP over Streamable HTTP

One-page cheat sheet · spec revision 2025-11-25

The shape of it

One endpoint (e.g. http://127.0.0.1:3001/mcp). Client POSTs one JSON-RPC message per request. Server replies with either a single application/json body or a text/event-stream (SSE) of messages — its choice. Client GET opens a server-push SSE stream.

JSON-RPC message shapes

ShapeHasGets a reply?
Requestid + method (+ params)Yes — a response with same id
Notificationmethod, no idNo — server sends 202 Accepted
Responseid + result or errorIt is the reply

Required headers (client → server)

HeaderWhenValue
Acceptevery POSTapplication/json, text/event-stream (both!)
Content-Typeevery POSTapplication/json
Mcp-Session-Idevery request after init (if server minted one)the id from the initialize response
MCP-Protocol-Versionevery request after initnegotiated version, e.g. 2025-11-25

Status codes you'll meet

CodeMeaning in MCP
200Request answered — body is JSON or an SSE stream
202Notification/response accepted; empty body
400Bad request — e.g. missing session id, bad protocol version
401No/invalid token — body+WWW-Authenticate point at OAuth discovery
403Token valid but lacks the scope — or Origin rejected (DNS-rebinding guard, spec MUST, not always enforced)
404Session expired/unknown → client must re-initialize
405Method not allowed — e.g. server offers no GET stream / no DELETE

The handshake (always 3 messages)

C→S POST initialize {protocolVersion, capabilities, clientInfo} S→C 200 result {protocolVersion, capabilities, serverInfo, instructions} + Mcp-Session-Id C→S POST notifications/initialized → S→C 202 (empty) ── session open ──

Core methods

MethodParamsResult
initializeprotocolVersion, capabilities, clientInfoprotocolVersion, capabilities, serverInfo, instructions?
notifications/initialized(notification → 202)
tools/listcursor? (pagination)tools[], nextCursor?
tools/callname, argumentscontent[], structuredContent?, isError?
notifications/tools/list_changed(server → client; re-list)

Session lifecycle controls

ActionHow
Open sessionserver sets Mcp-Session-Id on the initialize response
Use sessionclient echoes Mcp-Session-Id on every request
Server pushclient GET the endpoint → server SSE stream of requests/notifications
Resume streamclient GET with Last-Event-ID header → server replays
End sessionclient DELETE with Mcp-Session-Id (server MAY answer 405)

Authorization (OAuth 2.1) — optional, HTTP only

MCP server = OAuth Resource Server. It demands an Authorization: Bearer token and, on a 401, hands the client a discovery trail to get one. Mcp-Session-Id is transport state, not a credential — they are different headers with different jobs.

StepWireStandard
1 · Challenge401 + WWW-Authenticate: Bearer … resource_metadata="…"RFC 9728
2 · Resource metadataGET the resource_metadata URL → authorization_servers, scopes_supported, resourceRFC 9728
3 · AS metadataGET /.well-known/oauth-authorization-serverauthorization_endpoint, token_endpoint, registration_endpoint, code_challenge_methodsRFC 8414
4 · Register(optional) POST registration_endpoint for a client_id on the flyRFC 7591
5 · Authorizebrowser → authorize?…&code_challenge=…&resource=server, user consents, get codeOAuth 2.1 + PKCE
6 · TokenPOST token with code, code_verifier, resource → access tokenRFC 8707
7 · Retryre-send MCP request with Authorization: Bearer …

MUSTs that bite: PKCE required; the resource parameter (RFC 8707) binds the token's audience to one server; the server MUST validate that audience and MUST NOT pass tokens through to upstream APIs.

Source: MCP Spec 2025-11-25 — Transports, Authorization, Lifecycle, Tools. Built from real traffic against the everything reference server.