MCP Specification Changes: What Changed and Why It Matters
The Model Context Protocol has five dated revisions, from 2024-11-05 to 2026-07-28. What each one added, removed and deprecated, how old and new clients and servers still talk, and what to check if you own either side.
8 min read
The Model Context Protocol has had five dated revisions. 2024-11-05 was the first public one: stateful connections over stdio or HTTP with Server-Sent Events. 2025-03-26 added OAuth-based authorization, replaced that HTTP transport with Streamable HTTP, and added tool annotations. 2025-06-18 removed JSON-RPC batching and added structured tool output, elicitation, and servers as OAuth resource servers. 2025-11-25 added icons, URL-mode elicitation, Client ID Metadata Documents and experimental tasks. 2026-07-28, the current revision, made the protocol stateless: no initialize handshake, no sessions, and server requests replaced by results that ask for more input. If you run a server or a client, the last one is the change to plan for.
Everything below comes from the specification’s own changelog pages, one per revision. For what tools, resources and prompts are, see the three building blocks; for what the sign-in looks like to a user, see how MCP sign-in works. If MCP is new to you, start with what MCP is.
How MCP versions work
A version is a date. The versioning page (opens in a new tab) says it marks the last date backwards-incompatible changes were made, and that the version is not bumped for changes that keep compatibility. A revision is Draft while in progress, Current when ready for use (and still open to compatible changes), and Final once it will not change again. Today the current revision is 2026-07-28. Individual features can also be marked Deprecated, and a deprecated feature stays in the specification for at least twelve months before it can be removed.
2024-11-05: the starting point
The first revision defined the shape that has lasted: JSON-RPC 2.0 messages between a client inside a host application and a server; stateful connections with capability negotiation; servers offering resources, prompts and tools; and clients optionally offering sampling. It had two transports. stdio, where the client launches the server and they talk over standard input and output, and HTTP with Server-Sent Events, which needed two endpoints: one long-lived event stream from the server and a separate address for the client’s POSTs. There was no authorization in the protocol itself.
2025-03-26: authorization and Streamable HTTP
The 2025-03-26 changelog (opens in a new tab) lists four major changes:
- An authorization framework based on OAuth 2.1, which is where the browser sign-in for remote servers comes from.
- Streamable HTTP replaced HTTP with SSE: one endpoint that can answer with plain JSON or open a stream when it needs to.
- JSON-RPC batching, so several requests could travel in one message.
- Tool annotations describing behaviour, such as whether a tool is read-only or destructive.
It also added audio content alongside text and images, a completions capability for argument autocompletion, and a message field on progress notifications.
2025-06-18: structured output, elicitation, tighter auth
The 2025-06-18 changelog (opens in a new tab) took batching back out and made authorization more precise:
- Structured tool output: a tool can return typed JSON as well as text for the model.
- MCP servers are classified as OAuth resource servers, with protected resource metadata that tells a client where to sign in.
- Clients must implement Resource Indicators (RFC 8707), so a token issued for one server cannot be replayed at another.
- Elicitation, so a server can ask the user for more information mid-task.
- Resource links in tool results, a
titlefield for human-friendly names, and wider use of_meta. - On HTTP, the negotiated version must be sent in an
MCP-Protocol-Versionheader on every later request.
2025-11-25: discovery, icons and tasks
The 2025-11-25 changelog (opens in a new tab) is mostly additions. Authorization servers can be discovered with OpenID Connect Discovery; scopes can be asked for incrementally through the WWW-Authenticate header; and Client ID Metadata Documents became the recommended way for a client to register. Tools, resources and prompts can carry icons, and there is guidance on tool names. Elicitation gained a URL mode, for things such as payments or third-party sign-ins that should not pass through the client. Sampling can include tools. Experimental tasks let a request run long and be polled for its result.
Two minor changes matter more than they look. Input validation errors should come back as tool execution errors, not protocol errors, so the model can read them and correct itself. And servers must answer 403 Forbidden to an invalid Origin header on Streamable HTTP. JSON Schema 2020-12 also became the default dialect for schemas.
2026-07-28: stateless MCP
The current revision is the largest change since the first. The 2026-07-28 changelog (opens in a new tab) lists nine major changes; these are the ones most implementations meet:
- No handshake. The
initializeexchange is gone; every request carries its protocol version and client capabilities in_meta, and a mismatch returnsUnsupportedProtocolVersionError. - No sessions. The
Mcp-Session-Idheader is removed, and list results no longer vary per connection. A server that needs state across calls mints its own handle and takes it back as an ordinary tool argument. - A new required method,
server/discover, which returns the versions, capabilities and identity a server supports. - Server-initiated requests such as elicitation, sampling and roots are replaced by multi round-trip requests: the server returns a result marked
input_required, and the client retries the original request with the answers. Every result now carries aresultType. - Change notifications move to one opt-in stream,
subscriptions/listen, replacing the HTTP GET endpoint and resource subscribe and unsubscribe.pingandlogging/setLevelare removed. - Tasks leave the core protocol for an official extension, and SSE stream resumability is removed: a broken stream means re-sending the request.
The minor changes include required Mcp-Method and Mcp-Name headers on HTTP POSTs, cache hints (ttlMs and cacheScope) on list results, a deterministic order for tools/list, any JSON Schema 2020-12 keyword in tool schemas, a new error code for a missing resource, and a rule that clients validate the iss parameter in an authorization response. Four things are now listed as deprecated: Roots, Sampling and Logging; the old HTTP+SSE transport, which Streamable HTTP replaced back in 2025-03-26 and which this revision formally reclassifies; two includeContext values; and Dynamic Client Registration, in favour of Client ID Metadata Documents.
Old and new still talk, mostly
The specification calls 2025-11-25 and earlier “legacy” and 2026-07-28 “modern”, and an implementation that handles both “dual-era”. Its compatibility matrix (opens in a new tab) is the most useful table in the release. A dual-era client works with any server: on stdio it probes with server/discover and falls back to initialize on an unrecognised error; on HTTP it tries a modern request and falls back on a 400 without a modern error body. A dual-era server works with any client. The two failures are a modern-only client against a legacy server, and a legacy client against a modern-only server, which has no way to fall forward. The official Python SDK’s client, for example, is dual-era by default, as shown in building an MCP client in Python.
If you run a server
- Decide your era. Supporting both is allowed and is the safe choice while clients catch up. If you go modern-only, name the versions you support in the error you return to an
initializerequest, because it may be the only message a legacy client can show. - Remove any reliance on sessions. If a job spans several calls, return an id from the first and accept it as an argument on the next.
- Implement
server/discover, identify yourself in each result’s_meta, and return tools in a stable order. - Move any elicitation or sampling you do to the input-required pattern, and stop building on Roots, Sampling or Logging: log to stderr on stdio, or to your own telemetry.
- Return validation failures as tool results with
isError, not protocol errors, so the model can fix its arguments. - Check your authorization server:
issin authorization responses, and support for Client ID Metadata Documents now that Dynamic Client Registration is deprecated.
If you build or choose a client
- Use an SDK release that is dual-era, and log the protocol version each connection agrees, so a surprise shows up in your logs rather than in a user’s chat.
- Treat a result without
resultTypefrom an older server as complete, as the changelog requires, and handleinput_requiredby asking the user and retrying. - Stop expecting a session id, a
ping, or a resumable stream. Retry a broken request as a new request. - Key stored OAuth credentials by the authorization server that issued them, and validate
issbefore redeeming a code. - Watch for tool schemas that use more of JSON Schema than your model provider accepts; the protocol now allows any 2020-12 keyword.
Where fenbs stands
fenbs, a task board where AI assistants are members with roles, runs an MCP server at https://fenbs.ai/api/mcp. Its tools already follow the rules that carried forward: a permission refusal comes back as a tool result with isError, naming the permission that was missing and the role the caller holds, rather than as a protocol error, and each change is recorded in History under the assistant’s name. The tool list and how to connect are in the MCP docs.
Related
Writing a server against the current SDK: how to build an MCP server. The client side in Python: building an MCP client in Python. Adding sign-in to a server: how to add authentication to an MCP server.