MCP just deleted the session. We pointed curl at our own server to see what broke.
The Model Context Protocol shipped revision 2026-07-28, and the headline is short: there is no session any more.
Two things went with it. The Mcp-Session-Id header is gone from the Streamable HTTP transport, and so is the initialize / notifications/initialized handshake. A protocol that used to open with an introduction now opens with the request you actually wanted to make.
We run an MCP server — it is the reason this directory exists in the form it does, because an agent can search it and draft a submission without a human pasting anything. So the question was not academic. We pointed curl at our own production endpoint and wrote down what came back.
What the spec actually removed
From the changelog for the revision, the changes that alter the wire:
- Sessions and
Mcp-Session-Idare removed. Servers that need state across calls mint their own handles and pass them as ordinary tool arguments. - The handshake is removed. Every request now carries its own protocol version and client capabilities in
_meta, underio.modelcontextprotocol/protocolVersionandio.modelcontextprotocol/clientCapabilities. server/discoverbecomes mandatory. Servers MUST implement it so a client can ask what revisions and capabilities exist before committing to one.- Every result carries
resultType—"complete", or"input_required"for the new multi round-trip pattern. - List results carry
ttlMsandcacheScope, so a client can cachetools/listinstead of re-asking. Mcp-MethodandMcp-Nameheaders are required on Streamable HTTP POSTs.- SSE resumability is gone. No
Last-Event-ID, no redelivery — a broken stream loses the request and the client re-issues it with a new ID.
Roots, Sampling and Logging are deprecated in the same revision, and Dynamic Client Registration is deprecated in favour of Client ID Metadata Documents. Both remain functional for now.
What our server does, request by request
All of this is one endpoint, https://worthtotry.com/api/mcp, running the 2.0.0 server SDK. Nothing below is configured per-client.
The old handshake still works. An initialize naming 2025-06-18 gets a normal answer, and the server replies in that revision:
"result": { "protocolVersion": "2025-06-18", "capabilities": { "tools": { "listChanged": true } }, … }
The old tools/list still works too — no _meta, no new headers, and the six tools come back in the shape a 2025-era client expects.
Ask in the new revision and the answer changes shape. The same method, with _meta declaring 2026-07-28:
resultType: complete | ttlMs: 0 | cacheScope: private
serverInfo: {"name": "worthtotry", "version": "1.0.0"}
resultType, the cache fields, and the server identifying itself in the result's _meta — none of which the old-revision response carried.
So the same URL serves both, and it decides which rules to enforce from what the request itself declares. That is the point of dropping sessions, and it is easy to miss: there is no longer anywhere to remember which revision you agreed on, so the agreement has to travel with every message.
Three failures worth knowing before you debug one
server/discover looks like it does not exist. Asked without the _meta envelope, our server answers:
{"error": {"code": -32601, "message": "Method not found"}}
Asked with it, the same method returns supportedVersions, capabilities and instructions. The method the new revision made mandatory is invisible to anyone probing in the old dialect — and "Method not found" is the error most likely to send you looking for a missing implementation rather than a missing envelope. It cost us a few minutes and a wrong assumption.
A partial envelope is rejected precisely. Declaring the version but omitting capabilities:
"Invalid _meta envelope for protocol revision 2026-07-28:
io.modelcontextprotocol/clientCapabilities: missing"
Both keys, or neither.
The headers and the body have to agree. A well-formed new-revision body with no Mcp-Method header:
code -32020 — "the request headers and body disagree: the body names
method tools/list but the required Mcp-Method header is absent"
-32020 is itself new. The revision partitions the JSON-RPC server-error range and reserves -32020 to -32099 for the spec, moving HeaderMismatch from -32001. If you have anything matching on the old numbers, that is a silent break.
Authorization did not change, and still works
The write tools sit behind OAuth, and an unauthenticated submit_tool returns what it did before:
HTTP/2 401
www-authenticate: Bearer error="invalid_token",
error_description="This tool requires authorization.", scope="submit",
resource_metadata="https://worthtotry.com/.well-known/oauth-protected-resource/api/mcp"
The resource_metadata pointer is RFC 9728, and it is how an agent finds its way to consent without a token ever appearing in a transcript. Statelessness did not touch it.
One thing we got wrong about ourselves, and then fixed
ttlMs: 0 on tools/list. That is the SDK's default, and it tells every client not to cache a list that only changes when we deploy. The revision added the field precisely so clients can stop re-asking; improving model prompt-cache hit rates is the stated reason. We were shipping zero and taking none of it — and only noticed because writing this made us read our own response.
The SDK takes a per-operation cacheHints option, so it was a real one-line fix: five minutes on tools/list, an hour on server/discover, both public.
public deserves a sentence, because it is the field that could leak. It means a shared cache may keep one caller's response and hand it to another, which is only safe if every caller gets the same bytes. Ours do: all six tools are registered unconditionally, so an anonymous request and an authorised one receive the identical list. The day any tool is registered behind a check on who is asking, that hint has to go back to private. We have written that on the option rather than in a commit message, where nobody would read it again.
What we are not claiming
One server, one SDK version, one afternoon. Every response above is from our live endpoint and can be reproduced against it — the MCP page has the connection command — but a different SDK will differ, and the SDKs are moving quickly right now.
We have not tested the parts of the revision we do not implement: the multi round-trip request pattern, subscriptions/listen, or the tasks extension. And backwards compatibility working today is not a promise about the deprecation window. Roots, Sampling and Logging have twelve months under the new lifecycle policy; the old handshake has no such guarantee written down that we could find.