Repository navigation
MCP 2026-07-28 removed sessions: the v0.3 MCP profile needs a different correlation anchor #324
Description
What changed upstream
MCP revision 2026-07-28 removed the construct the v0.3 MCP profile was going to bind to. From the changelog, major changes 1 and 2:
Remove protocol-level sessions and the
Mcp-Session-Idheader from the Streamable HTTP transport. List endpoints (tools/list,resources/list,prompts/list) no longer vary per-connection. Servers that need cross-call state use explicit, server-minted handles passed as ordinary tool arguments (SEP-2567).
Make MCP stateless: remove the
initialize/notifications/initializedhandshake. Every request now carries its protocol version and client capabilities in_meta(SEP-2575).
There is no MCP session any more, and no handshake in which one could be established.
Why it lands here
ROADMAP.md scopes the v0.3 item as the normative MCP profile, "claim shape and binding rules for MCP tool-call transcripts... for upstream contribution to MCP spec governance."
Our own description of that evidence is written in session terms:
- §3.3 names the row "Session evidence": the Trust Record, policy hash, runtime measurement and
tool_transcript.hash"bind the governed session". - §3.3.3 requires a receipt to bind "back to the TRACE session, transcript entry, or call".
session_idis a signed field with a MUST: a verifier rejects a disclosure whosesession_iddoes not match the receipt stream under verification.
Nothing in v0.2 is invalidated by this. TRACE's session_id names a receipt stream we mint, not an MCP session, and the same is true of cMCP's audit-chain session_id. The exposure is confined to the profile: writing binding rules that map a transcript onto an MCP session, and then proposing them upstream to the body that has just removed sessions, is a bad opening and an easy one to walk into given how §3.3 currently reads.
What the profile should anchor on instead
Two candidates, both named by the revision itself.
- OpenTelemetry trace context. Minor change 2 documents
traceparent,tracestateandbaggageas_metaconventions (SEP-414). For a stateless protocol this is the standards-aligned correlation hook, and it is already what an operator is likely to have. - Server-minted handles as tool arguments, which is what SEP-2567 names as the replacement for cross-call state. This puts correlation inside the transcript we already hash rather than beside it.
Either way the profile should say explicitly that the correlation identifier is TRACE's or the server's, and is not an MCP protocol construct.
A second-order consequence for transcripts
Major change 9 removes SSE stream resumability:
A broken response stream loses the in-flight request; clients MUST re-issue it as a new request with a new request ID.
So a transcript cannot assume request ids are stable across a retry, and must tolerate abandoned attempts sitting beside the completed one. Any per-call ordering or completeness claim in the profile needs to survive that, and tool_transcript already carries the caveat that the record does not expose every call without the transcript bytes.
Adapter surface to check
Roots, Sampling and Logging are deprecated (SEP-2577), under a twelve-month window. Any producer adapter that reads them keeps working for now but should not gain new dependencies on them.
Status of the reference implementation
cMCP has already migrated and is ahead of this document. src/cmcp_runtime/mcp/server.py speaks 2026-07-28 outbound and keeps the handshake revisions for legacy clients only, with a comment explaining that 2026-07-28 "must never be the answer to a handshake: it names a protocol in which this request does not exist".
So this is a specification-language gap rather than an implementation gap, which is the cheap kind to fix, and cheapest before the profile is drafted rather than after.
Ask
Decide the correlation anchor before the v0.3 MCP profile is written, and reword §3.3's session framing so the profile does not inherit a construct that upstream has removed.
Activity
Mayur021 commentedon Sep 14, 2026
Both anchors correlate calls. There is a second binding the profile has to choose, and 2026-07-28 made it harder rather than easier.
Before SEP-2567, tools/list varied per connection, so a session carried an implicit snapshot of what the tools claimed to do. That is gone, and there is no handshake left to pin one to. At verification time nothing says what the called tool was declared to do at the moment it was called.
tool_transcript.hash binds which tools were called, with what parameters and responses. It does not bind the contract they were called under. That stops being academic once the protocol is stateless, because declarations move underneath.
We measured this. 35 crawls of the public MCP registry between June and August 2026, 44,172 tools on 2,043 servers. 83.8% of tools declare at least one canonical effect annotation, and 59.3% hold a declaration still bound to an unmutated contract. The gap is 24.5 points, and it stays between 21.6 and 24.7 across every threshold tested. Over the longest uninterrupted 21-day window, 18.3% of the declaring and judgeable tools saw a confirmed contract mutation. The corpus holds 783 confirmed declaration changes, and 86.0% of first declarations land on the permissive pole. All four annotation defaults are restrictive, so that move produces no change event for a monitor to fire on.
The consequence here is that a Level 1 record can verify cleanly and the relying party still cannot tell whether the tool that ran means now what it meant then.
Suggestion for the profile: require a digest over the declaration set in force at call time, the canonicalized tools/list result, carried in the transcript and covered by tool_transcript.hash. Section 3.2 already reaches this case where it says the canonicalization rule covers a bridge profile's declaration and tool-call digests, where no schema constrains the input. The mechanism is there. The profile is where it becomes required.
Method and data: https://doi.org/10.5281/zenodo.22649163
madeinplutofabio commentedon Sep 17, 2026
I think this issue has two separable bindings, not one.
The first is the correlation anchor after MCP removed protocol-level sessions.
traceparent/tracestateand server-minted handles are both plausible ways to correlate a flow or call family, but neither tells a verifier what the called tool was declared to mean at call time.The second is the declaration snapshot. I agree with @Mayur021 that the v0.3 MCP profile should bind the tool declaration set, or at least the relevant tool declaration, as it existed when the call was made. Otherwise
tool_transcript.hashcan bind the call name, arguments, and response while leaving the called contract movable.The existing PIC/TRACE bridge is a useful local precedent for the separation, without making this a PIC dependency. It keeps the authorization artifact, declaration digest, tool-call digest, and transcript/successor evidence separate:
I think the MCP profile needs the same separation of concerns:
That would avoid recreating the old MCP session object under another name, while still giving a verifier enough evidence to survive stateless MCP and mutable tool declarations.
A small non-normative cleanup could also make the current text less session-shaped before the profile is drafted:
tool_transcript.call_countdescriptions from “tool calls in this session” to “tool calls in this transcript” inschema/trace-claim.jsonandschema/trace-claim-v0.3-draft.json;Mayur021 commentedon Sep 17, 2026
The separation is right, and the schema already has the precedent for it.
policy.bundle_hashis described as the digest of the policy bundle in force at execution time, sitting as its own object besidetool_transcript. A declaration digest in force at call time is that same shape applied to a different artifact, so this is symmetry rather than a new mechanism.Two things on the shape, one of which I think matters more than it looks.
Separation of concerns should not become separate signing.
tool_transcript.hashis described as bound into the EAT envelope. If the declaration digest sits beside the transcript hash rather than inside what the envelope covers, a producer can present a transcript that verifies while the declaration digest moves, which is the same movability one level up. Whichever field it lands in, it needs to be inside the signed set.On "the declaration set, or at least the relevant tool declaration", I would keep the set. A digest over the canonicalized
tools/listcosts what a digest over one entry costs, one hash, so the narrower form saves nothing. It does lose something: the agent chose from the list, not from the entry, so a description change elsewhere in the set can move the selection without touching the called tool's own declaration. In the corpus the movement that matters is a property of the set rather than of any single row. 86.0% of first declarations land on the permissive pole, and because all four annotation defaults are restrictive, that move produces no change event at all on the entry a narrow binding would cover.Both non-normative cleanups are worth doing.
call_countreads "tool calls in this session" in bothtrace-claim.jsonandtrace-claim-v0.3-draft.json, and disambiguating "session" in 3.3.3 from an MCP protocol session removes the last place the text still assumes the construct 2026-07-28 deleted.madeinplutofabio commentedon Sep 18, 2026
@Mayur021 Agreed on both refinements.
On signing: yes, the declaration-set digest only helps if it is inside the signed evidence envelope. Otherwise it creates the same movability problem one layer above the transcript.
On scope: your argument for the full canonicalized
tools/listset is stronger than the narrower "relevant declaration" form. I used the narrower wording to avoid over-specifying the profile too early, but I agree the set is the safer evidence boundary: the agent selected from the set, and movement elsewhere in the set can change the meaning of that selection.So the profile shape I would now read as:
The small wording cleanups seem independently useful and non-normative.
imran-siddique commentedon Sep 24, 2026
The MCP changelog confirms the session removal and new request IDs on retries. For the draft, keep TRACE's receipt-stream identity distinct from MCP transport state. Record each request attempt separately; an OTel context or server handle can link attempts, but neither establishes authorization, execution or completeness.
Keep the declaration-set digest inside the signed record, separate from the transcript digest. The draft needs to specify the retained declaration bytes and canonicalization, including pagination and changes between discovery and dispatch. A digest alone does not establish which declarations the agent actually saw.
Please start with the session-wording cleanup and a small profile example covering a lost response, retry and changed declaration set. Leave absent outcomes unknown. That gives us a concrete basis for the v0.3 binding rules without changing v0.2 semantics.
Merge remote-tracking branch 'origin/main' into docs/digest-algorithm…
imran-siddique commentedon Oct 15, 2026
Closing. #413 now defines a TRACE session as the receipt stream, and #423 added the retry and declaration-snapshot example, which were the two things my 09-24 comment asked for. The normative MCP profile sits on the ROADMAP and gets its own issue when someone picks it up.