cd /news/ai-agents/mcp-goes-stateless-on-july-28-is-you… · home topics ai-agents article
[ARTICLE · art-48443] src=pub.towardsai.net ↗ pub= topic=ai-agents verified=true sentiment=· neutral

MCP Goes Stateless on July 28. Is Your Audit Trail Still Intact?

The Model Context Protocol (MCP) will become stateless on July 28, 2026, removing session IDs and the initialize handshake. This change breaks audit trails that relied on session IDs as correlation keys, forcing teams to implement application-owned evidence identities for compliance in regulated industries.

read11 min views1 publishedJul 6, 2026

I have spent years building solutions in regulated environments across banking, financial services, and telecommunications. That experience teaches you one thing quickly: when something goes wrong, the first question is never about latency or cost. It is some version of “reconstruct exactly what the system did on this account, in order, with proof.” Whatever lets you answer that question is your most valuable infrastructure, whether you designed it that way or not.

On July 28, the Model Context Protocol ships a release that a lot of teams will experience through that question. The 2026–07–28 spec release, locked as a release candidate on May 21 with SDK betas available since June 29, makes the protocol stateless. The initialize handshake is removed, the protocol-level session is removed, and the Mcp-Session-Id header goes with it. Every request becomes self-contained: protocol version and client info travel in _meta on each call, new Mcp-Method and Mcp-Name headers let gateways route without parsing bodies, and servers that need continuity mint explicit handles that the model passes back as ordinary arguments.

The migration checklists covering the breaking changes are everywhere. What I have not seen anyone write about is the evidence problem, so that is what this article covers: why session IDs ended up doing audit work they were never designed for, what actually breaks when they disappear, and the evidence model I would build instead. I will be upfront about scope: I have not migrated a production MCP fleet against this RC. This is a close read of the spec held up against how audit and dispute conversations actually go in banking and telecom, where I run distributed tracing and audit logging on production LLM platforms today.

Here is the claim the rest of the article defends. A session ID was never a durable audit model. It was an accidental join key, and stateless MCP forces teams to replace it with application-owned evidence identities. The teams in trouble on July 28 are not the ones losing a good audit trail. They are the ones discovering they were leaning on a transport convenience where a deliberately modeled identifier should have been.

That distinction matters because it changes what the migration work is. If you frame this as “the session is gone, propagate a trace ID instead,” you will rebuild the same fragile pattern one layer up. The real work is deciding, explicitly, what identifies a run, an action, and an approval in your system, and making those identifiers survive independently of whatever transport happens to carry them.

Under the previous transport spec, remote MCP over HTTP was stateful. Client and server performed an initialize handshake, the server could issue an Mcp-Session-Id, and subsequent requests carried that header. The session was a transport-scoped convenience: it let servers keep per-client state and let infrastructure route related requests to the same place.

In many deployments, that ID quietly became the correlation key for everything else. It was available on every request, free to log, easy to index, and often close enough to a logical agent run that teams treated it as one. Every log producer that touched a request had it without writing any code, which is exactly why it spread.

It is worth being precise about what the session never guaranteed, because this is where the audit story was always weaker than it looked. A session was not defined as one agent run. It could outlive a run, span multiple user interactions, be reused by a host, or be terminated by the server independently of anything your application considered a boundary. It carried no principal, no tenant, no business meaning. It was never designed to represent a transaction, a user approval, or a workflow. Teams that joined their dispute evidence on it were joining on an identifier whose lifecycle they did not control and whose semantics they had assumed rather than specified.

Concretely, an agent run in a banking deployment produces log lines like these, emitted by different services:

14:02:11  tools/call  lookup_account(acct=**7841)      sess 1868a90c14:02:13  elicitation "Confirm transfer of $12,400?"   sess 1868a90c14:02:19  tools/call  initiate_transfer(...)           sess 1868a90c14:02:20  result      transfer_id=TX-99120, posted     sess 1868a90c

When the dispute team asks what happened before TX-99120 posted, the answer today is a filter on the last column. Remove it and load balance those four requests across arbitrary instances, which is precisely what the stateless design enables, and the events still exist but nothing ties the confirmation elicitation to the transfer that followed it. Reconstruction degrades to best-effort correlation on timestamps and payload contents. In an internal postmortem that is annoying. In front of a regulator, “we believe these events are related based on timing” is a sentence you do not want to say.

The blast radius is wider than raw logs. Inventory where session IDs appear in your SIEM queries, alerting rules, dashboards, support tooling, and dispute runbooks. Those artifacts break silently: the queries still run, they just stop returning complete answers, and nobody notices until the next investigation.

One honest caveat: this pattern is common, not universal. Teams that already correlate end to end on application-level identifiers will have a calm July 28. But every deployment I have seen that grew from an SDK quickstart inherited the session as its default join key, because it was simply there.

The replacement is not one identifier, it is a small set of identities with different jobs. Collapsing them into a single ID is how the session problem happened in the first place.

The audit event store is primary. Tracing points into it, not the other way around. For regulated actions, I would generate an application-owned run_id and operation_id at the host, propagate them alongside trace context in _meta on every MCP request, and append immutable audit events that reference all three.

There is one record in this model that deserves special attention because it is where agent systems fail audits in a way traditional systems rarely do: the approval. My example run has an elicitation (“Confirm transfer of $12,400?”) followed by a transfer. It is not enough to prove the user approved a transfer. You have to prove they approved the transfer that actually executed. That means the approval event binds to a canonical hash of the exact action, roughly:

approval_idrun_idactor_idtenant_idaction_typecanonical_action_hashdisplayed_amountdisplayed_payeeapproval_timestampapproval_channelpolicy_version

Without the action hash, a prompt-injected or simply buggy agent can obtain approval for one action and execute a different one, and your evidence will show a valid-looking approval next to a completed transfer with nothing proving they describe the same thing. In a dispute, that gap is the whole case.

Explicit handles are where the deep problems live, because a handle minted by a server now travels through the model’s context window, the least trusted and most injectable layer in the stack. That cuts two ways. On the evidence side, handles are visible in a way session state never was: you can log issuance, every use, and the principal behind each use, which done well is a better audit story than an opaque session. On the security side, handles inherit every risk of the context window: they can be steered by prompt injection, replayed from old transcripts or poisoned documents, and leaked across tenant boundaries through shared documents and summaries.

The rules that make handles safe are the standard rules for bearer-ish identifiers, applied to a surface where the “client” constructing requests is a language model that reads untrusted text all day:

I have built workspace-scoped, ownership-based authorization for multi-tenant LLM analytics, and the rule that transfers directly is the second one: the moment a handle is treated as authority rather than as a reference, every leak becomes a breach.

The new spec adds ttlMs and cacheScope to list and resource-read results, modeled on HTTP caching semantics, so clients know how long a tools/list response stays fresh and whether it can be shared across users. Sensible for scale, with one compliance edge worth simulating before an access reviewer simulates it for you.

Suppose a client caches a user’s tool catalog with a 30 minute TTL, and 10 minutes later you revoke that user’s authorization for one of the tools. For the remaining 20 minutes, a spec-compliant client can keep presenting the revoked tool to the model and attempting calls, because its cache is still fresh. When the access review asks whether access was removed immediately upon revocation, the honest answer becomes “within ttlMs, unless the server also rejects at call time.”

The fix is old and boring, which is why it gets skipped. A cached tool catalog is a UX and planning artifact; authorization has to be evaluated against the current principal, tenant, policy, and resource state when the tool executes. Then set catalog TTLs no longer than your revocation SLA so the model is not shown options it will be denied. In production I would make the revocation fire drill part of go-live: revoke a test user mid-run, measure how long the capability stays reachable, and put that number in the runbook, because someone will eventually ask for it under worse circumstances.

The spec hands you good raw material for correlation: W3C Trace Context propagation through _meta is now documented, with traceparent, tracestate, and baggage key names pinned, so a trace that starts in the host can follow a call through the client SDK, the MCP server, and downstream systems as one span tree. The protocol Logging primitive is deprecated in the same release, with stderr and OpenTelemetry named as the migration path, under a lifecycle policy that keeps deprecated features working for at least 12 months.

I run OpenTelemetry tracing on production LLM platforms and I want to be precise about the boundary, because “just use OTel” is doing a lot of work in the migration advice. Trace context should connect the execution path. It should not be the only artifact you rely on to prove what happened. Traces get sampled and dropped. Inbound trace context can be absent, malformed, or maliciously supplied, which is why the gateway should validate or replace it rather than trust it. Observability retention is usually weeks; audit retention is usually years. Spans are mutable or deletable in many backends, which disqualifies them as primary evidence. High-cardinality identity and tenant data does not belong scattered across telemetry attributes. And the OpenTelemetry GenAI semantic conventions are still under active development, so their schema should inform your telemetry, not become your evidence model. If you adopt them, pin the version and record it per span, because attribute names can change under you.

The architecture that respects the boundary keeps observability and evidence as separate planes that reference each other:

Host  ├── creates run_id and operation_id  ├── starts trusted trace_id  ├── audit event: run_startedMCP gateway  ├── validates or replaces inbound trace context  ├── records authenticated principal and tenant  ├── audit event: tool_invocation_requestedMCP server  ├── authorizes the call at execution time  ├── validates handle ownership, expiry, scope, revocation  ├── audit event: handle_used / action_preparedConfirmation service  ├── binds approval to canonical_action_hash  └── audit event: approval_grantedAction service  ├── executes idempotently  └── audit event: action_committed

Audit events land in an immutable retention system with restricted writers, retention lock or WORM controls where required, and a cryptographic integrity mechanism such as chained hashes, signed event batches, or independently retained digests. Append-only alone is not tamper-evident; append-only plus integrity proofs is.

The acceptance test for the whole design is a rehearsal of the auditor’s question. Hand a colleague only a transaction ID from staging and see whether they can reconstruct the full run, including the bound approval, from the evidence store alone. On my platforms, the day we could answer that question without tribal knowledge was the day audit conversations stopped being scary, and that was true long before MCP existed.

Two clocks are running: the final spec on July 28, and the 12-month deprecation guarantee for Logging, Roots, and Sampling.

Before July 28, the work is inventory. Find every place a session ID appears in log pipelines, SIEM queries, dashboards, and dispute runbooks. Read the RC changelog and the trace propagation section of the draft spec directly rather than trusting summaries, including this one, because details can still shift before the final. Decide your identity model from section 4 and start emitting run_id and trace context in parallel with sessions now, so you have overlap data. Audit which tools mint or accept handles and whether those handles are opaque, high-entropy, and tenant-bound. Compare catalog cache TTLs against your revocation SLA.

Within the 12-month window, do the rebuild: migrate off protocol Logging onto pinned OTel conventions, add handle lifecycle logging, enforce call-time authorization everywhere, add approval binding with canonical action hashes to any workflow that executes consequential actions, stand up the evidence store with integrity proofs, and run the revocation fire drill and the auditor rehearsal until both are boring.

The limits of this analysis are worth restating. It is based on the release candidate and could be invalidated in details by the final spec. The accidental-join-key pattern is widespread but not universal. And the architecture in section 7 is a reference shape from someone who has built its components on adjacent production systems, not a report from a completed MCP migration. What I am confident about is the underlying claim: correlation you inherit from a transport is correlation you do not own, and July 28 is the day a lot of teams find out which kind they had.

Sources: the MCP 2026–07–28 release candidate announcement, the SDK beta announcement, the previous transport specification, the draft spec changelog, and the OpenTelemetry GenAI observability material. Deprecation semantics and convention status are live documents; verify both before you build.

MCP Goes Stateless on July 28. Is Your Audit Trail Still Intact? was originally published in Towards AI on Medium, where people are continuing the conversation by highlighting and responding to this story.

── more in #ai-agents 4 stories · sorted by recency
── more on @model context protocol 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
Live at https://your-agent.zahid.host
Get free account → Pricing
from €0/mo · no card required
LIVE [news/mcp-goes-stateless-o…] indexed:0 read:11min 2026-07-06 ·