Every request carries its own protocol version and capabilities; there is no handshake and no session, and a server that reads version, capabilities or identity from an earlier request is doing something the spec forbids
The 2026-07-28 revision declares MCP "a stateless protocol: all the information needed to process a request is contained in the request itself" (the request travels as JSON-RPC, a small id-and-method envelope). In practice, every request has to describe itself; the spec enforces that with two rules. Servers "MUST NOT rely on prior requests over the same connection to establish context (e.g., capabilities, protocol version, client identity)", and state that has to span requests "MUST be referenced by an explicit identifier the client passes on each request". Two fields in the request's _meta object (MCP's per-request metadata) are therefore required on every request, io.modelcontextprotocol/protocolVersion and io.modelcontextprotocol/clientCapabilities; a request missing either "is malformed" and gets -32602. On HTTP the version goes in an MCP-Protocol-Version header; an Mcp-Method header is required on all requests and an Mcp-Name header on tools/call, resources/read and prompts/get, "so that intermediaries (load balancers, gateways, observability tooling) can route and inspect requests without parsing the body".
There is no initialize: "There is no negotiation handshake. Every request carries its protocol version, and the server accepts or rejects each request independently". Sessions and Mcp-Session-Id are gone: a modern-only server receiving one should "ignore it, and do not mint or echo session IDs". The core spec does not promise that any instance can serve any request; SEP-2575 explains why the stateless design allows it in principle, and Study D shows where it breaks. To compare the two eras, use the toggle and step through each with Prev and Next; the legacy side is the 2025-11-25 specification, with the client sendingnotifications/initialized and the session being a MAY.
server/discover (optional)"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."
server/discover probe first.Put everything the server needs in the request. Treat server/discover as optional information, not a setup step.
- Required on every request:
protocolVersionandclientCapabilitiesin_meta; On HTTP theMCP-Protocol-Versionheader (its value "MUST" match the body or the server returns400with-32020),Mcp-Methodon all requests, andMcp-Nameontools/call,resources/readandprompts/get. clientInfoandserverInfoare SHOULD and "are self-reported by the sender and are not verified by the protocol"; implementations "SHOULD NOT rely on them for security decisions".- On HTTP a broken response stream loses the in-flight request and "clients MUST re-issue it as a new request with a new request ID"; on stdio a restart loses in-flight requests and "the client can retry them against the fresh process". The re-issue needs no handshake.
Go deeper
Dual-era servers exist: "A request carrying modern per-request _meta is served statelessly according to this revision. An initialize request selects legacy semantics", and a modern-only server "SHOULD name the protocol versions it supports in any error it returns to an initialize request". The specification's own index page still says extensions are "negotiated during initialization"; the normative pages say per request.