MCP protocol versions explained
What changed in each MCP revision from 2024-11-05 to 2026-07-28, how clients negotiate and fall back between them, and what stateless means in 2026.
On this page
An MCP version is a date string, YYYY-MM-DD, naming the last day backwards-incompatible changes were made. The versioning page (opens in a new tab) lists 2026-07-28 as current. This page covers the five revisions a remote server can meet today.
Comparison table
| 2024-11-05 | 2025-03-26 | 2025-06-18 | 2025-11-25 | 2026-07-28 | |
|---|---|---|---|---|---|
| Remote transport | HTTP+SSE (two endpoints) | Streamable HTTP | Streamable HTTP | Streamable HTTP | Streamable HTTP, POST only |
| Handshake | initialize | initialize | initialize | initialize | None; per-request _meta |
| Version negotiation | In initialize | In initialize | In initialize | In initialize | Per request; server/discover advertises versions |
| Session header | None | Mcp-Session-Id | Mcp-Session-Id | MCP-Session-Id | Removed |
MCP-Protocol-Version header | No | No | Required after init | Required after init | Required on every POST |
Mcp-Method / Mcp-Name | No | No | No | No | Required |
| JSON-RPC batching | No | Yes | Removed | No | No |
| GET stream | SSE endpoint | Optional | Optional | Optional | Removed |
Last-Event-ID resumption | No | Yes | Yes | Yes (always via GET) | Removed |
| Authorization spec | None | OAuth 2.1 | Server is an OAuth resource server; RFC 9728, RFC 8707 | Adds OIDC discovery, CIMD, scope step-up | DCR deprecated; RFC 9207 iss; issuer-bound credentials |
Invalid Origin | Validate | Validate | Validate | Validate; 403 | Validate; 403 |
Header names are case-insensitive, so the Mcp-Session-Id/MCP-Session-Id spelling change is cosmetic. See the HTTP headers reference for each header.
2024-11-05
- Transport: stdio and HTTP with Server-Sent Events. The server exposes an SSE endpoint and a separate POST endpoint; on connect it "MUST send an
endpointevent" naming the POST URI (transports (opens in a new tab)). - Auth: no authorization specification. Servers MUST validate
Originand SHOULD authenticate connections. - Lifecycle:
initializerequest, result, thennotifications/initialized. - Unsupported version error: the lifecycle example uses
-32602withdata.supportedanddata.requested.
2025-03-26
From the changelog (opens in a new tab):
- Transport: Streamable HTTP replaces HTTP+SSE. One endpoint takes POST (and optionally GET). The server may assign
Mcp-Session-Idon theinitializeresponse; the client returns it on every later request. - Auth: an authorization framework based on OAuth 2.1.
- Messages: JSON-RPC batching added.
- Capabilities: tool annotations (for example read-only or destructive), audio content, a
completionscapability, andmessageon progress notifications.
2025-06-18
From the changelog (opens in a new tab):
- Headers: after initialization the client MUST send the negotiated version in
MCP-Protocol-Versionon every HTTP request. A server that receives no header and has no other signal SHOULD assume2025-03-26; an invalid or unsupported value MUST get400 Bad Request. - Messages: JSON-RPC batching removed.
- Auth: MCP servers are OAuth resource servers that publish protected resource metadata; clients MUST send RFC 8707 resource indicators.
- Capabilities: structured tool output (
outputSchema,structuredContent), elicitation, resource links in tool results,titlefields, and_metaon more types. - Lifecycle: lifecycle operation wording tightened from SHOULD to MUST; a security best practices page added.
2025-11-25
From the changelog (opens in a new tab):
- Transport: an invalid
OriginMUST get403 Forbidden. Servers may close an SSE stream at will after sending an SSEretryfield, and clients reconnect; resumption is always a GET withLast-Event-ID. - Auth: OpenID Connect Discovery for authorization servers, Client ID Metadata Documents as the recommended registration mechanism, incremental scope consent through
WWW-Authenticate, and protected resource metadata discovery aligned with RFC 9728 (the header is optional; clients fall back to.well-known). - Errors: input validation errors are tool execution errors (
isError: true), not protocol errors. - Capabilities: icons, URL-mode elicitation, tools in sampling, experimental tasks, and JSON Schema 2020-12 as the default dialect.
2026-07-28
From the changelog (opens in a new tab):
- Lifecycle: the
initialize/notifications/initializedhandshake is removed. Every request carriesio.modelcontextprotocol/protocolVersionandio.modelcontextprotocol/clientCapabilitiesinparams._meta. Servers MUST implementserver/discover. - Transport: protocol-level sessions and
Mcp-Session-Idare removed. The GET stream andresources/subscribeare replaced bysubscriptions/listen, a long-lived POST response stream.Last-Event-IDresumption is removed; a broken stream loses the in-flight request and the client re-issues it with a new ID. - Headers:
Mcp-Methodon every POST,Mcp-Nameontools/call,resources/readandprompts/get, and optionalMcp-Param-*headers declared withx-mcp-header. - Server-to-client requests: sampling, elicitation and roots requests are replaced by multi round-trip requests: the server returns
resultType: "input_required"and the client retries withinputResponses. All results carryresultType. - Removed methods:
ping,logging/setLevel,notifications/roots/list_changed. - Errors: resource not found moves from
-32002to-32602; new codes-32020HeaderMismatch,-32021MissingRequiredClientCapability,-32022UnsupportedProtocolVersion. See MCP error codes. - Auth: Dynamic Client Registration deprecated in favour of Client ID Metadata Documents; clients MUST validate a present RFC 9207
iss; credentials are bound to the issuer. See the OAuth 2.1 reference. - Deprecated: Roots, Sampling and Logging; the HTTP+SSE transport is formally Deprecated.
- Caching: list and read results carry
ttlMsandcacheScope.
Stateless in 2026
The overview (opens in a new tab) calls MCP "a stateless protocol". In practice:
- A server MUST NOT rely on earlier requests for the client's version, capabilities or identity; each request states them in
_meta. - There is no session ID to store, share between replicas, or expire. Any instance can serve any request.
- State that spans calls, such as a cart or an open browser, MUST be referenced by an explicit identifier, typically a handle returned by one tool and passed as an argument to the next.
- A missing required
_metafield gets-32602with HTTP400.
Stateless does not mean unauthenticated: the Authorization header still travels on every request.
POST /mcp HTTP/1.1
Content-Type: application/json
Accept: application/json, text/event-stream
MCP-Protocol-Version: 2026-07-28
Mcp-Method: tools/call
Mcp-Name: get_weather
{"jsonrpc":"2.0","id":7,"method":"tools/call","params":{"name":"get_weather","arguments":{"location":"Paris"},"_meta":{"io.modelcontextprotocol/protocolVersion":"2026-07-28","io.modelcontextprotocol/clientCapabilities":{}}}}Negotiation through 2025-11-25
Defined in the lifecycle (opens in a new tab):
- The client sends
initializewith aprotocolVersionit supports, which SHOULD be its latest. - If the server supports it, it MUST answer with the same version; otherwise with another version it supports, which SHOULD be its latest.
- If the client does not support the server's answer, it SHOULD disconnect.
- Over HTTP, the client then sends
MCP-Protocol-Versionon every request (2025-06-18 and later).
To reach a 2024-11-05 server, a Streamable HTTP client POSTs initialize; if that fails with 400, 404 or 405, it sends GET and expects an SSE stream whose first event is endpoint (2025-11-25 transports (opens in a new tab)).
Negotiation in 2026-07-28
Defined in versioning and compatibility (opens in a new tab):
- There is no handshake. Each request names its version; a server that does not implement it MUST return
-32022withdata.supportedanddata.requested, over HTTP with status400. - The client SHOULD pick a mutually supported version from
supportedand retry. - Clients MAY call
server/discoverfirst. Its result carriessupportedVersions,capabilitiesand server identity. - Era detection over HTTP: on a
400, inspect the body. A recognized 2026 error means correct and retry; an empty or unrecognized body means fall back toinitialize, and then to HTTP+SSE if needed. - A dual-era server serves requests with 2026
_metastatelessly and aninitializewith 2025 session semantics.
| Client | Server | Result |
|---|---|---|
| 2026 only | 2025 only | Fails |
| Dual-era | 2025 only | Works through initialize fallback |
| 2025 only | 2026 only | Fails; over HTTP with 400 for missing headers |
| 2025 only | Dual-era | Works |
How mcptest negotiates
mcptest is a dual-era client built on the official TypeScript SDK (@modelcontextprotocol/client 2.0).
createNegotiatingMcpClientinsrc/utils/mcpClient.tssetsversionNegotiation: { mode: 'auto' }. The SDK first sendsserver/discoverwith a 2026-07-28_metaenvelope.- A valid discover result that lists a version mcptest supports selects the stateless era. A
-32022naming a mutual 2026 version triggers one corrective retry. - Anything else that is not proof of 2026 support, including an unrecognized
400, falls back toinitialize. The SDK offers2025-11-25and accepts2025-11-25,2025-06-18,2025-03-26,2024-11-05or2024-10-07in reply. - A
401or403stops negotiation as an authorization outcome rather than a fallback, and a5xxis a failure. - Legacy SSE candidates use
createLegacyMcpClient(mode: 'legacy'), which skips the probe. src/utils/transportDetection.tskeeps the exact URL you entered and also tries conventional/mcpand/ssesiblings. Candidates are tried in priority groups, with a 5-second deadline per group, first directly and then through the optional CORS proxy.- The result shows the negotiated era (
stateless,statefulorlegacy) and version. In reports,src/compatibility/profiles.tschecks the protocol era (2024, 2025 or 2026) and session behavior against host profiles for ChatGPT, Claude, Cursor, VS Code/Copilot and a generic SDK. For ChatGPT it addsprotocol.stateful-scaling-caveatwhen the server holds session state.
Try it
To see a 2026-07-28 stateless negotiation, open Context7 in the playground. It is a public catalog server that negotiated 2026-07-28 at its last validation. Then compare its request headers with those of a 2025-era server.