Skip to content

Community-maintained FDE reference. Not an official Vercel or Anthropic project. About this project

The 2026-07-28 stateless revision

Audience:engineerarchitectsecurityMCP spec 2026-07-28

Published and verified. The 2026-07-28 revision is final and published at modelcontextprotocol.io/specification/2026-07-28/. Every claim on this page has been re-verified against the published specification and its changelog (an earlier version of this page was sourced from the draft changelog and carried status: draft). The rest of this reference now speaks 2026-07-28 natively, so this page serves as the revision-history record: what changed relative to 2025-11-25, and why it changed that way.

Plain-language explanation

TL;DR: This revision made the protocol stateless. The initialization handshake is gone, the session id header is gone, and every request is self-contained: it carries its own protocol version and capabilities in _meta, and anything a server used to ask the client for mid-request (an elicitation answer, a sampling completion) is now returned as an “input required” result that the client satisfies by retrying the original request with the answers attached. If that sounds like it was designed for a Vercel Function, that is because serverless deployments spent two years demonstrating that MCP’s stateful session model was the part that did not survive contact with reality. This repo’s page on that friction, Serverless sessions, catalogued the workarounds this revision existed to delete; the spec has now deleted them.

One caution before building: the published spec, what the deployed handler serves, and what a default SDK client sends are three different things. See SDK status below.

What changed

All items below are the published changelog’s own change set, with RFC-2119 language and SEP numbers as it states them.

Sessions and the handshake are removed

  • Protocol-level sessions and the Mcp-Session-Id header are removed from Streamable HTTP. List endpoints (tools/list, resources/list, prompts/list) no longer vary per connection. Servers needing cross-call state use explicit, server-minted handles passed as ordinary tool arguments (SEP-2567).
  • The initialize / notifications/initialized handshake is removed. Every request carries its protocol version and client capabilities in _meta (io.modelcontextprotocol/protocolVersion, io.modelcontextprotocol/clientCapabilities); clients SHOULD send io.modelcontextprotocol/clientInfo per request and servers SHOULD return io.modelcontextprotocol/serverInfo per result. Version mismatches return an UnsupportedProtocolVersionError (SEP-2575).
  • A new server/discover RPC: servers MUST implement it to advertise supported versions, capabilities, and identity; clients MAY call it up front for version selection or as a backward-compatibility probe on stdio (SEP-2575).

Server push is reshaped

  • The HTTP GET listening stream and resources/subscribe / resources/unsubscribe are replaced by subscriptions/listen: one long-lived POST-response stream for opted-in change notifications (toolsListChanged, promptsListChanged, resourcesListChanged, resourceSubscriptions), tagged with io.modelcontextprotocol/subscriptionId. Request-scoped notifications such as progress and log messages stay on the response stream of the request they relate to (SEP-2575).
  • SSE resumability and redelivery are removed (Last-Event-ID, SSE event ids). A broken response stream loses the in-flight request; clients MUST re-issue it as a new request with a new id (SEP-2575).
  • ping, logging/setLevel, and notifications/roots/list_changed are removed. Log level becomes per-request via io.modelcontextprotocol/logLevel in _meta, and servers MUST NOT emit notifications/message for requests that did not opt in (SEP-2575).

Server-initiated requests become MRTR

  • The Multi Round-Trip Requests (MRTR) pattern replaces server-initiated requests (roots/list, sampling/createMessage, elicitation/create). A server needing input returns an InputRequiredResult (resultType: "input_required") whose inputRequests carry what it needs; the client retries the original request with inputResponses attached (SEP-2322).
  • Every result now carries a required resultType field: "complete" normally, "input_required" for MRTR interim results. Clients MUST treat results from earlier-protocol servers that omit the field as "complete" (SEP-2322).
  • Consequently the 2025-11-25 URL-mode elicitation additions (notifications/elicitation/complete, elicitationId) are removed; correlation across retries moves into requestState.

Tasks move out of core

  • Experimental tasks leave the core protocol for an official extension, io.modelcontextprotocol/tasks. The redesign replaces the blocking tasks/result with polling via tasks/get, adds tasks/update for client-to-server input, removes tasks/list, and lets servers return task handles unsolicited, without per-request opt-in (SEP-2663). The extension is documented outside the core spec, at the extensions section of the site.

Deprecations

  • Roots, Sampling, and Logging are deprecated (SEP-2577), remaining functional during a minimum twelve-month window under the new feature-lifecycle policy (SEP-2596). Suggested migrations: pass directories via tool parameters or configuration instead of roots; integrate directly with LLM provider APIs instead of sampling; log to stderr or OpenTelemetry instead of MCP logging.
  • The HTTP+SSE transport (deprecated since 2025-03-26) is formally reclassified as Deprecated, and OAuth Dynamic Client Registration (RFC 7591) is deprecated in favor of Client ID Metadata Documents; it remains available for backwards compatibility with authorization servers that do not support CIMD.
  • The includeContext values "thisServer" and "allServers" (soft-deprecated in 2025-11-25) are formally Deprecated; omit the field or use "none".

Transport, caching, and auth details

  • Streamable HTTP POSTs must carry standard Mcp-Method and Mcp-Name request headers, with x-mcp-header support for custom headers from tool parameters (SEP-2243). The method and tool name become visible to the HTTP layer before body parsing.
  • List and read results gain a CacheableResult interface with required ttlMs (freshness hint, ms) and cacheScope ("public" or "private") fields on tools/list, prompts/list, resources/list, resources/read, and resources/templates/list; servers SHOULD return tools in deterministic order to help client-side and LLM prompt caching (SEP-2549).
  • ClientCapabilities and ServerCapabilities gain an extensions field; OpenTelemetry trace-context _meta keys (traceparent, tracestate, baggage) are documented (SEP-414).
  • Auth hardening: authorization servers SHOULD send the RFC 9207 iss parameter and clients MUST validate a present one against the recorded issuer before redeeming the code (SEP-2468); registered credentials are bound to their issuing authorization server (SEP-2352); clients must set an appropriate application_type during registration (SEP-837).
  • Housekeeping: resource-not-found moves from -32002 to -32602; an error-code allocation policy keeps -32000 to -32019 implementation-defined (existing SDK usage grandfathered) and reserves -32020 to -32099 for the spec. The codes introduced in this revision are renumbered accordingly (HeaderMismatch -32020, MissingRequiredClientCapability -32021, UnsupportedProtocolVersion -32022), and HeaderMismatchError is added to the schema itself, where it previously existed only in transport prose. Tool schemas loosen to full JSON Schema 2020-12, and structuredContent may be any JSON value (SEP-2106).

Governance

  • The revision ships with a formal feature lifecycle and deprecation policy (SEP-2596): features are Active, Deprecated, or Removed, deprecation windows last at least twelve months, and a published registry tracks every feature currently in the Deprecated state. The deprecations above are the policy’s first occupants.

A stateless tool call

What the change set adds up to on the wire:

Server on VercelClientServer on VercelClientno initialize, no session id_meta carries protocolVersion and capabilitiesalt[server needs user input]POST tools/call, Mcp-Method and Mcp-Name headersresultType input_required, inputRequestsretry tools/call with inputResponsesresultType complete, serverInfo in _meta
Server on VercelClientServer on VercelClientno initialize, no session id_meta carries protocolVersion and capabilitiesalt[server needs user input]POST tools/call, Mcp-Method and Mcp-Name headersresultType input_required, inputRequestsretry tools/call with inputResponsesresultType complete, serverInfo in _meta
Mermaid sequence diagramOpen in Mermaid Live Editor
Diagram source (Mermaid)
sequenceDiagram
    participant C as Client
    participant S as Server on Vercel
    Note over C,S: no initialize, no session id
    C->>S: POST tools/call, Mcp-Method and Mcp-Name headers
    Note over C: _meta carries protocolVersion and capabilities
    alt server needs user input
        S-->>C: resultType input_required, inputRequests
        C->>S: retry tools/call with inputResponses
    end
    S-->>C: resultType complete, serverInfo in _meta

The annotated message trace walks this exchange frame by frame; under 2025-11-25 the same trace needed three extra handshake frames before the first useful request.

Key deltas at a glance

  • Removed: initialize, notifications/initialized, Mcp-Session-Id, ping, logging/setLevel, notifications/roots/list_changed, resources/subscribe, resources/unsubscribe, the GET listening stream, SSE resumability, core tasks/*.
  • Added: server/discover (client → server), subscriptions/listen (client → server, long-lived response stream), resultType on every result, _meta protocol envelope keys, Mcp-Method / Mcp-Name headers, ttlMs / cacheScope, the extensions capability field.
  • Moved: tasks to the io.modelcontextprotocol/tasks extension; server-initiated requests into MRTR results.
  • Deprecated: roots, sampling, MCP logging, HTTP+SSE, RFC 7591 dynamic client registration, the "thisServer" / "allServers" values of includeContext.

Why serverless is its happy path

Each removal deletes a workaround this repo used to have to teach:

  • No session, no session store. Under 2025-11-25, a stateful server on Vercel needed Redis or had to run sessionless and hope clients coped; Serverless sessions is a whole page about that gap. Under this revision, statelessness is not a degraded mode, it is the mode, and cross-call state becomes explicit handles in tool arguments, which serialize, shard, and survive redeploys.
  • Every request is routable. With version, capabilities, and identity in _meta, and the method and tool name in Mcp-Method / Mcp-Name headers, any invocation on any instance can serve any request, and Routing Middleware or a WAF can route and rate-limit per method without parsing bodies.
  • No held-open streams to babysit. MRTR turns “server keeps a stream open waiting for the user” into “server returns immediately, client retries when ready”. A maxDuration budget stops being a mid-elicitation death sentence.
  • CDN-shaped caching. ttlMs plus cacheScope: "public" on tools/list is exactly the contract an edge cache in front of a function wants.
  • Deprecating sampling, roots, and logging removes the three features that assumed a long-lived, bidirectional channel between peers, the assumption a function invocation never satisfied.

SDK status

The deployable stack has moved to the v2 package line: mcp-handler 2.1.1 (peer @modelcontextprotocol/server ^2.0.0) with @modelcontextprotocol/server 2.0.0 and, for tests, @modelcontextprotocol/client 2.0.0. This repo’s examples run on those pins; examples/minimal-server (in the repository) is the template.

Wire status: the pinned stack (mcp-handler 2.1.1 on @modelcontextprotocol/server 2.0.0) serves the 2026-07-28 contract natively over Streamable HTTP and falls back to stateless 2025-11-25 Streamable HTTP for legacy clients; the SDK Client defaults to that legacy handshake unless you opt in to modern version negotiation, so the examples’ in-memory test suites exercise only the legacy path. Concretely, verified against the installed packages on 2026-08-26:

  • Server side, over HTTP: modern. mcp-handler 2.1.1 serves 2026-07-28 natively: server/discover, the per-request _meta envelope, resultType on every result, ttlMs/cacheScope on list results (0 and "private" unless you configure cache hints), Mcp-Method/Mcp-Name validation with -32020, MRTR input_required results, and subscriptions/listen. @modelcontextprotocol/server 2.0.0 declares SUPPORTED_MODERN_PROTOCOL_VERSIONS = ["2026-07-28"]; its LATEST_PROTOCOL_VERSION = "2025-11-25" constant only names the version answered on the legacy initialize path. A 2025-era client gets that stateless legacy fallback from the same handler: initialize answered at 2025-11-25, no Mcp-Session-Id issued, GET and DELETE answered 405. HTTP+SSE and the Redis dependency are gone from 2.x.
  • Client side: legacy by default. @modelcontextprotocol/client 2.0.0 defaults versionNegotiation.mode to 'legacy', so a plain connect() performs the initialize handshake at 2025-11-25 and its results carry no resultType, ttlMs, or cacheScope. Opt in with versionNegotiation: { mode: 'auto' } (probes server/discover and falls back on -32601) or pin a version.
  • In-memory tests: legacy only. A bare McpServer over InMemoryTransport answers server/discover with -32601, so the examples’ vitest suites cannot observe the modern fields; those belong in an HTTP-level check against the handler. See Testing.

The annotated message trace shows both shapes and the curl that produces the modern one.

Common misconceptions

  • “The spec is published, so my stack already speaks it.” Check, do not assume: publication and implementation are separate events. This repo’s pinned handler does serve 2026-07-28 natively, but a default SDK client still opens with the legacy handshake, so a claim about what your deployment emits must be checked against a capture with the client you actually use. See SDK status.
  • “Stateless means no state anywhere.” No: it means state is explicit. Servers mint handles and pass them as tool arguments; the protocol layer stops pretending to remember things for you.
  • “MRTR is just elicitation renamed.” No: it inverts the direction. There are no server-initiated requests at all; the server returns its questions inside a result, and the client retries with answers. Consent UX still applies, at the retry boundary; see Consent UX.
  • “Sampling being deprecated means my server loses LLM access.” No: the suggested migration is calling an LLM provider directly from the server, rather than round-tripping through the client.
  • “My 2025-11-25 client breaks immediately.” No: results from earlier-protocol servers without resultType MUST be treated as "complete", and deprecated features remain functional through a minimum twelve-month lifecycle window.

Debugging notes

  • How to tell which revision a server speaks: probe with server/discover; a -32601 back strongly suggests a 2025-11-25 (or older) server expecting an initialize handshake. In the other direction, a stateless-revision server receiving initialize will not know the method.
  • Symptom: 400 complaining about missing headers on a new-revision server → the client did not send Mcp-Method / Mcp-Name. Likely cause: an old SDK. Where to look: request headers, then the HeaderMismatchError (-32020) body.
  • Symptom: an “interim” result you did not expect → you received resultType: "input_required" and treated it as final. Where to look: your result-handling switch; it needs an MRTR retry path.
  • Symptom: reconnect-and-resume stops working → SSE resumability is removed in this revision; re-issue the request with a new id instead of replaying Last-Event-ID.

Security implications

  • No session id, no session hijacking, which retires a whole checklist row; but server-minted handles inherit the threat model instead: they must be unguessable and bound to the verified principal, exactly like task ids. The gate’s default must be denial.
  • Per-request auth is unchanged in shape: OAuth 2.1 protected-resource metadata still fronts the endpoint, and the revision tightens the client side (RFC 9207 iss validation, issuer-bound credentials, CIMD over dynamic registration). See Authorization.
  • Mcp-Method and Mcp-Name headers are a gift to your firewall: per-tool WAF rules and rate limits without body inspection. They are also a new spoofing surface; the spec defines a HeaderMismatchError precisely because servers must verify the headers against the body rather than trust either alone.
  • cacheScope is a data-leak control: mark anything principal-dependent "private" or a shared cache will happily serve one user’s tool catalog to another. See Output trust.

Runnable example

Every example in this repo builds on the v2 stack (examples/minimal-server (in the repository) is the smallest), and all of them are stateless by design, which is exactly the shape this revision rewards. Deployed (or under npm run dev), each serves the new frames (server/discover, resultType, the _meta envelope) to any client that sends modern headers; the message trace has the curl. What the examples’ vitest suites do not show is those same frames, because the in-memory transport stays on the legacy handshake; see SDK status.

Bibliography