Terminology
This page uses the following terms for interoperability across protocol revisions:- Modern: protocol versions that convey version, identity, and
capabilities as per-request metadata (revision
2026-07-28and later). - Legacy: protocol versions that establish a session with an
initializehandshake (2025-11-25and earlier). - Dual-era: an implementation that supports both modern and legacy versions.
Protocol Version Negotiation
Every request declares the protocol version it is using in its_meta field. On HTTP, this is
also carried in the
MCP-Protocol-Version header.
If the server does not implement the requested version (whether the version
is unknown to the server, or is a known version the server has chosen not to
support), it MUST respond with an
UnsupportedProtocolVersionError
listing the versions it does support:
supported
list and retry the request, or surface an error to the user if no compatible
version exists.
Servers MUST implement
server/discover. Clients
MAY call it before sending any other requests to learn the server’s
supported versions up front, but are not required to: a client is free to
invoke any RPC inline and handle UnsupportedProtocolVersionError if its
preferred version is not supported.
Extension Negotiation
Clients and servers can negotiate support for optional extensions beyond the core protocol. Extensions are advertised in theextensions field of capabilities, which is a map of
extension identifiers to per-extension settings objects.
The following is an example of a client that advertises the
MCP Apps extension identified as io.modelcontextprotocol/ui:
io.modelcontextprotocol/tasks:
Backward Compatibility with Initialization-Based Versions
A server that wishes to support both legacy clients (which expect aninitialize handshake) and modern clients (which
use per-request metadata) MAY implement both behaviors.
A client that needs to interoperate with both kinds of servers detects the
server’s era with transport-specific mechanics, specified in the binding
pages:
- stdio:
probe with
server/discoverand fall back on any error that is not a recognized modern error. - Streamable HTTP:
attempt a modern request and inspect the body of a
400 Bad Requestbefore falling back.
UnsupportedProtocolVersionError)
identifies a modern server: the client retries with a supported version
rather than falling back. Anything else identifies a legacy server.
The era determination is a property of the server, not of an individual
request. Clients SHOULD cache the result for the lifetime of the server
process (stdio) or origin (HTTP), and MAY persist it across restarts of
the same server configuration, re-probing if the cached assumption later
fails.
Compatibility Matrix
The following matrix summarizes the expected outcome of every combination of client and server era:| Client | Server | Outcome |
|---|---|---|
| Modern | Modern | Works. server/discover is optional; version mismatches surface as UnsupportedProtocolVersionError and the client retries with a mutually supported version. |
| Modern | Legacy | Fails. The server may reject the request with an implementation-defined error, stay silent, or even process an era-ambiguous method under legacy semantics. On stdio, clients SHOULD send server/discover first to fail deterministically; the client then surfaces an actionable error to the user. |
| Dual-era | Modern | Works. The stdio probe returns a DiscoverResult (or UnsupportedProtocolVersionError); on HTTP, the first modern request succeeds or returns a modern error. The client stays modern. |
| Dual-era | Legacy | Works. stdio: the probe returns a non-modern error or times out, and the client falls back to initialize. HTTP: the modern request returns a 4xx without a recognized modern error body, and the client falls back to initialize (and possibly further to the deprecated HTTP+SSE transport). |
| Legacy | Modern | Fails. initialize is an unknown method: the server returns Method not found (-32601); on HTTP this is a 404 with the JSON-RPC error in the body. Legacy clients have no fall-forward mechanism. Modern servers SHOULD include an error message naming the protocol versions they support, since legacy clients surface it to users. |
| Legacy | Dual-era | Works. The server answers initialize and serves the client according to the negotiated legacy revision. |
| Legacy | Legacy | Works according to the legacy revision; out of scope for this document. |
- A request carrying modern per-request
_metais served statelessly according to this revision. - An
initializerequest selects legacy semantics, scoped to the stdio process (stdio) or the session (HTTP), as specified by the negotiated legacy protocol version.