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
| Shape | Has | Gets a reply? |
| Request | id + method (+ params) | Yes — a response with same id |
| Notification | method, no id | No — server sends 202 Accepted |
| Response | id + result or error | It is the reply |
Required headers (client → server)
| Header | When | Value |
Accept | every POST | application/json, text/event-stream (both!) |
Content-Type | every POST | application/json |
Mcp-Session-Id | every request after init (if server minted one) | the id from the initialize response |
MCP-Protocol-Version | every request after init | negotiated version, e.g. 2025-11-25 |
Status codes you'll meet
| Code | Meaning in MCP |
200 | Request answered — body is JSON or an SSE stream |
202 | Notification/response accepted; empty body |
400 | Bad request — e.g. missing session id, bad protocol version |
401 | No/invalid token — body+WWW-Authenticate point at OAuth discovery |
403 | Token valid but lacks the scope — or Origin rejected (DNS-rebinding guard, spec MUST, not always enforced) |
404 | Session expired/unknown → client must re-initialize |
405 | Method 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
| Method | Params | Result |
initialize | protocolVersion, capabilities, clientInfo | protocolVersion, capabilities, serverInfo, instructions? |
notifications/initialized | — | (notification → 202) |
tools/list | cursor? (pagination) | tools[], nextCursor? |
tools/call | name, arguments | content[], structuredContent?, isError? |
notifications/tools/list_changed | — | (server → client; re-list) |
Session lifecycle controls
| Action | How |
| Open session | server sets Mcp-Session-Id on the initialize response |
| Use session | client echoes Mcp-Session-Id on every request |
| Server push | client GET the endpoint → server SSE stream of requests/notifications |
| Resume stream | client GET with Last-Event-ID header → server replays |
| End session | client 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.
| Step | Wire | Standard |
| 1 · Challenge | 401 + WWW-Authenticate: Bearer … resource_metadata="…" | RFC 9728 |
| 2 · Resource metadata | GET the resource_metadata URL → authorization_servers, scopes_supported, resource | RFC 9728 |
| 3 · AS metadata | GET /.well-known/oauth-authorization-server → authorization_endpoint, token_endpoint, registration_endpoint, code_challenge_methods | RFC 8414 |
| 4 · Register | (optional) POST registration_endpoint for a client_id on the fly | RFC 7591 |
| 5 · Authorize | browser → authorize?…&code_challenge=…&resource=server, user consents, get code | OAuth 2.1 + PKCE |
| 6 · Token | POST token with code, code_verifier, resource → access token | RFC 8707 |
| 7 · Retry | re-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.