# The 2026-07-28 stateless revision

Canonical URL: https://vercel-mcp-reference.vercel.app/internals/spec-2026-07-28/
Markdown: https://vercel-mcp-reference.vercel.app/internals/spec-2026-07-28.md
Audience: engineer, architect, security. MCP spec version: 2026-07-28. Last reviewed: 2026-08-26. Status: stable.

> **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](https://vercel-mcp-reference.vercel.app/glossary/#initialization) handshake is gone, the [session](https://vercel-mcp-reference.vercel.app/glossary/#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](https://vercel-mcp-reference.vercel.app/glossary/#server) used to ask the client for mid-request (an [elicitation](https://vercel-mcp-reference.vercel.app/glossary/#elicitation) answer, a [sampling](https://vercel-mcp-reference.vercel.app/glossary/#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](https://vercel-mcp-reference.vercel.app/glossary/#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](https://vercel-mcp-reference.vercel.app/internals/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](#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](https://vercel-mcp-reference.vercel.app/internals/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:

```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](https://vercel-mcp-reference.vercel.app/internals/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](https://vercel-mcp-reference.vercel.app/internals/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](https://vercel-mcp-reference.vercel.app/glossary/#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](https://vercel-mcp-reference.vercel.app/testing/).

The [annotated message trace](https://vercel-mcp-reference.vercel.app/internals/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](#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](https://vercel-mcp-reference.vercel.app/client-side/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](https://vercel-mcp-reference.vercel.app/internals/tasks/). 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](https://vercel-mcp-reference.vercel.app/security/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](https://vercel-mcp-reference.vercel.app/security/checklist/#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](https://vercel-mcp-reference.vercel.app/internals/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](#sdk-status).

## Related

- [Serverless sessions](https://vercel-mcp-reference.vercel.app/internals/serverless-sessions/) - the 2025-11-25 friction this revision was designed to delete, and what replaced it.
- [Annotated message trace](https://vercel-mcp-reference.vercel.app/internals/message-trace/) - the current wire exchange, frame by frame.
- [Tasks (extension)](https://vercel-mcp-reference.vercel.app/internals/tasks/) - the utility that left core for an official extension, redesigned on the way out.
- [Authorization](https://vercel-mcp-reference.vercel.app/security/authorization/) - the auth model, which survives the statelessness change intact.
- [Transports](https://vercel-mcp-reference.vercel.app/internals/transports/) - Streamable HTTP under this revision.

## Bibliography

- Model Context Protocol Specification, *Key Changes*, version 2026-07-28 - <https://modelcontextprotocol.io/specification/2026-07-28/changelog>
- Model Context Protocol Specification, *Multi Round-Trip Requests*, version 2026-07-28 - <https://modelcontextprotocol.io/specification/2026-07-28/basic/patterns/mrtr>
- Model Context Protocol Specification, *Transports*, version 2026-07-28 - <https://modelcontextprotocol.io/specification/2026-07-28/basic/transports>
- Model Context Protocol Specification, *Versioning*, version 2026-07-28 - <https://modelcontextprotocol.io/specification/2026-07-28/basic/versioning>
- Model Context Protocol Specification, *Error codes* (allocation policy), version 2026-07-28 - <https://modelcontextprotocol.io/specification/2026-07-28/basic/index#error-codes>
- Model Context Protocol Specification, *Deprecated features registry*, version 2026-07-28 - <https://modelcontextprotocol.io/specification/2026-07-28/deprecated>
- Model Context Protocol, *Feature lifecycle and deprecation policy* - <https://modelcontextprotocol.io/community/feature-lifecycle>
- Model Context Protocol, *Tasks extension overview* - <https://modelcontextprotocol.io/extensions/tasks/overview>
- SEP-2567, *Remove protocol-level sessions* - <https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2567>
- SEP-2575, *Stateless protocol changes* - <https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2575>
- SEP-2322, *Multi Round-Trip Requests* - <https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2322>
- SEP-2663, *Tasks as an extension* - <https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2663>
- SEP-2549, *Cacheable list results* - <https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2549>
- SEP-2243, *Standard MCP request headers* - <https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2243>
- SEP-2577, *Deprecate Roots, Sampling, and Logging* - <https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2577>
