TTA · Spec Briefing
Spec revision 2026-07-28

MCP Went Stateless.
Here Is What QA Needs to Know.

The Model Context Protocol dropped sessions: no more handshake, no more Mcp-Session-Id, every request self-contained. This briefing reads the QA-relevant changes as a diff: what was removed, what replaced it, which of your test assertions break, and what stays true for everything you have already learned.

5 sections 6 diff hunks 6 deprecations tracked 12-month minimum removal window
01

The story: one request, everything included

Old MCP opened a conversation: handshake first, then a session id on every call, with server and client keeping shared state alive between them. New MCP is a series of self-contained envelopes: each request names its protocol version and capabilities inside _meta, and SHOULD say who is calling (clientInfo). If versions clash, the server answers UnsupportedProtocolVersionError and that is the whole negotiation.

One new obligation came with the freedom: servers MUST implement server/discover, which advertises supported versions, capabilities, and identity. Clients MAY call it up front, and on STDIO it doubles as the backward-compatibility probe.

02

The change ledger, read as six diff hunks

@@ stateless core @@QA impact: high
- initialize / notifications/initialized handshake- Mcp-Session-Id header on Streamable HTTP+ _meta: io.modelcontextprotocol/protocolVersion + clientCapabilities per request+ server/discover (servers MUST implement)+ explicit server-minted handles as ordinary tool arguments

The headline change. List endpoints no longer vary per connection, so a fleet of identical server replicas behaves identically. Anything in your harness that mocked the handshake or asserted on session headers is now testing a protocol that new servers no longer speak.

Test it: restart the server mid-suite. Under the new shape, in-flight requests fail and re-issue cleanly; nothing should depend on a session surviving.
@@ MRTR: multi round-trip requests @@QA impact: medium
- server-initiated roots/list, sampling/createMessage, elicitation/create+ resultType: "input_required" + inputRequests in the result+ client retries the ORIGINAL request with inputResponses  every result now carries resultType ("complete" | "input_required")

Servers stop calling the client back over an open stream. When a server needs something (a directory, a model completion, a human answer), it returns an interim result saying "input required", and the client retries the same request carrying the answers. Correlation across retries rides in requestState.

  • Back-compat rule worth memorizing. Results from older servers that omit resultType MUST be treated as "complete".
Test it: a client under test must survive an input_required interim result and retry correctly, that is a brand-new happy path plus a brand-new timeout path.
@@ transport: headers, caching, no redelivery @@QA impact: high
- SSE resumability: Last-Event-ID, event ids, message redelivery- HTTP GET endpoint, resources/subscribe, resources/unsubscribe+ Mcp-Method and Mcp-Name headers required on Streamable HTTP POSTs+ ttlMs + cacheScope on tools/prompts/resources list + resources/read results+ subscriptions/listen: one opt-in stream for change notifications

Gateways and rate limiters can now route and police MCP traffic on plain HTTP headers without parsing JSON bodies. List and read results declare how long they may be cached (ttlMs) and whether shared intermediaries may cache them (cacheScope "public" or "private"). And when a response stream breaks, the in-flight request is simply lost: clients MUST re-issue it as a new request with a new id.

Test it: two new bug classes arrive, stale-cache bugs (a client trusting ttlMs past a real change) and retry bugs (double-effects when a lost request is re-issued against a non-idempotent tool).
@@ tasks move to an extension @@QA impact: low
- experimental tasks in core; blocking tasks/result; tasks/list+ io.modelcontextprotocol/tasks extension+ poll with tasks/get; send input with tasks/update+ servers may return task handles unsolicited

Long-running work formalizes as an extension: poll-based instead of blocking, with a new client-to-server input channel. Capabilities gained an extensions field on both sides to declare support.

Test it: polling loops need budget assertions (poll interval, give-up time), the classic async-testing discipline.
@@ authorization hardening @@QA impact: medium
+ clients MUST validate a present iss (RFC 9207) against the recorded issuer+ application_type required during Dynamic Client Registration+ credentials keyed by issuer, never reused across auth servers  DCR itself is deprecated in favor of CIMD (next section)

Three tightenings aimed at confused-deputy and mixed-up-issuer attacks. If you test an MCP client that does OAuth, the issuer-binding rules are new required behavior, not suggestions.

Test it: negative cases: a mismatched iss must abort redemption, and credentials from one authorization server must never be replayed against another.
@@ error codes renumbered @@QA impact: high
- resource not found: -32002+ resource not found: -32602 (Invalid Params, JSON-RPC alignment)- HeaderMismatch -32001, MissingRequiredClientCapability -32003, UnsupportedProtocolVersion -32004+ renumbered: -32020, -32021, -32022 (spec range -32020..-32099 reserved)

The quietest change on the page and the one most likely to fail a pipeline on upgrade day. The error-code space is now partitioned: -32000..-32019 stays implementation-defined (existing SDK usage grandfathered), -32020..-32099 belongs to the spec.

Test it: grep your suites for hardcoded -32002 and the renumbered trio right now; assert on error semantics or symbolic names where the SDK offers them, not raw integers.
03

The deprecation registry, and the clock on it

The revision also formalized a feature lifecycle: Active, Deprecated, Removed, with a public registry of everything deprecated. Deprecated features keep working during the window; new implementations should not adopt them.

⏰ Minimum twelve months between Deprecated and Removed, so nothing here breaks today.
FeatureStatusMigrate to
RootsDeprecatedPass directories and files via tool parameters, resource URIs, or server configuration.
SamplingDeprecatedIntegrate directly with LLM provider APIs.
LoggingDeprecatedLog to stderr on stdio, or use OpenTelemetry (trace context now has documented _meta conventions).
HTTP+SSE transportDeprecatedStreamable HTTP.
includeContext "thisServer" / "allServers"DeprecatedOmit the field or use "none"; removed no later than Sampling itself.
Dynamic Client Registration (RFC 7591)DeprecatedClient ID Metadata Documents (CIMD); DCR stays for authorization servers without CIMD support.

Also gone outright in this revision (not deprecated, removed): ping, logging/setLevel, notifications/roots/list_changed, and SSE resumability. Log level now travels per request as io.modelcontextprotocol/logLevel in _meta.

04

What it breaks in your tests, and what holds

Breaks on the new shape
  • Hardcoded error-code assertions: -32002 is now -32602, and the header/capability/version trio moved to -32020..-32022.
  • Handshake mocks and Mcp-Session-Id fixtures: dead code against new servers.
  • Stream-resume assumptions: no Last-Event-ID redelivery; lost request means re-issue, so non-idempotent tools need double-effect tests.
  • Suites that treat tools/list as per-connection: lists no longer vary by connection and SHOULD come back in deterministic order.
  • Clients that ignore resultType: an input_required interim result is now a normal response you must handle.
Holds steady
  • Tools, prompts, and resources as concepts: unchanged. Calling a tool looks the same to the person prompting.
  • Usage-level tutorials (wiring Playwright MCP into an agent, using tools from Copilot or Claude Code) stay correct; it is the plumbing chapters that age.
  • Deprecated features keep working for the whole window, so existing setups run while you migrate deliberately.
  • Deterministic list ordering is a gift: list snapshots become stable, and prompt caching gets better hit rates.
  • STDIO local workflows: server/discover doubles as the compatibility probe, old and new can coexist during the transition.
MRTRmulti round-trip requests, the retry-with-answers pattern CIMDClient ID Metadata Documents, the DCR successor ttlMshow long a list result may be cached cacheScope"public" or "private": may intermediaries cache it server/discoverthe mandatory who-are-you RPC resultType"complete" or "input_required" on every result
05

Questions testers actually ask

Do my existing MCP setups stop working?

Mostly no. Deprecated features remain functional for a minimum of twelve months under the lifecycle policy, and old-revision client/server pairs keep speaking the old shapes to each other. The removed pieces (handshake, session header, SSE resumability) only bite once one side moves to the new revision. Also, and back-compat rules are explicit (missing resultType reads as "complete", server/discover probes old servers on STDIO). The pressure is on new builds: do not adopt deprecated shapes in anything you start now.

Does this change how I use Playwright MCP or other servers day to day?

At the usage level, no: you still connect a server and the agent still calls tools. The changes live underneath: how requests carry identity, how streams recover (they do not, they re-issue), how gateways route and cache. Your usage muscle memory survives; your protocol-level fixtures may not.

What should I grep my test suites for this week?

Raw error codes (-32002, -32001, -32003, -32004), Mcp-Session-Id, Last-Event-ID, resources/subscribe, logging/setLevel, and ping. Every hit needs a review: it is either dead code on the new shape, or an assertion that breaks the day you upgrade.

Why did they do this at all?

Scale and simplicity. A stateless server runs behind any round-robin load balancer with no shared session storage, list responses become cacheable (ttlMs), gateways route on headers instead of parsing bodies, and prompt caches hit more often thanks to deterministic tool ordering. The stateful design made remote deployment the hard part; this revision makes it boring.

Is Sampling really going away? My course covers it.

Deprecated with the twelve-month minimum, alongside Roots and Logging. The suggested migration is calling LLM provider APIs directly. For course material: teach the concept as historical context if you must, but point new builds at provider APIs and mark the chapter accordingly.