Notes

MCP relocated the control point. The record did not come with it.

Enforcement can now sit outside the process that produced the call. That is a better home for a control. It is not, on its own, a record.

On 28 July the Model Context Protocol removed its session handshake. The framing was scale, and that part is real. The second-order effect is that enforcement can now sit outside the process that produced the call. That is a better home for a control. It is not, on its own, a record.

What the revision actually did

The initialize and notifications/initialized handshake is gone. So is the Mcp-Session-Id header and the protocol-level session that came with it. Every request now carries its own protocol version and client capabilities in the message body, under _meta. A server that needs to hold something across calls does what HTTP APIs have always done: it mints an explicit handle, returns it from a tool, and lets the model pass it back as an ordinary argument.

Worth marking now, because it comes back later: client identity is carried the same way, in io.modelcontextprotocol/clientInfo, but the specification says clients should send it rather than must. Version and capabilities are guaranteed on every request. Who is calling is not.

Google’s write-up of the change is the clearest account of why any of this happened. Round-robin load balancers do not know which container holds which in-memory session, so teams configured sticky affinity rules that then prevented even distribution of traffic. If a pod restarted, session state was lost instantly and transient errors landed in live conversations. Working around it meant shared Redis stores or gateway-level packet inspection, which the authors describe as introducing massive latency and operational costs. Remove the session and all of that goes with it. Servers sit behind an ordinary load balancer and scale to zero when idle.

Alongside the removal, the specification added something quieter. The Streamable HTTP transport now mirrors selected fields from the message body into HTTP headers. Mcp-Method carries the method and is required on all requests. Mcp-Name carries the tool name, resource URI or prompt name, and is required on tools/call, resources/read and prompts/get. MCP-Protocol-Version is required on every POST to the MCP endpoint. The specification states the purpose without decoration: the headers exist so that intermediaries, meaning load balancers, gateways and observability tooling, can route and inspect requests without parsing the body.

Google puts the same point in operational terms. Proxies, gateways and load balancers can route, rate-limit and audit traffic without inspecting the request body. For security and logging teams, they write, this is a massive win.

They are right that it is a win. It is worth being precise about what has been won.

The relocation

Until this revision, anything that wanted to make a decision about an agent’s tool call had to open the JSON-RPC body to find out what the call was. That is expensive at the edge and awkward to do safely, which is why most policy about agent behaviour ended up living inside the agent runtime or the MCP server itself. Enforcement sat inside the thing being enforced.

Promoting the method and the object name into standard headers moves the decision point outward. A gateway can now see that a request is a tools/call for execute_sql by reading two header lines, and can rate-limit, route by tenant, deny by tool, or write an audit entry without touching the payload. That work happens on infrastructure your security team probably already runs, using primitives it already understands.

This matters because of a property that has nothing to do with performance. A gateway can refuse. It can decline a call regardless of what the agent runtime believes it is permitted to do, on infrastructure the agent does not administer, and that refusal does not depend on the cooperation of the thing being refused. Most of the distance between a governance document and a governance capability is exactly that.

What the gateway knows, though, is a different trust question. The mirrored headers are written by the client, from its own request body, because the specification requires the client to extract those values and append them. The body remains the source of truth. So the edge acquires an unconditional power to deny, exercised on metadata that the governed process asserted about itself. Two different properties in one mechanism, and they are worth keeping apart when you write the control down.

THE REQUEST PATH Agent client writes body and headers Gateway routes, limits, audits MCP server validates, executes Backend the real effect WHAT IS LEGIBLE WITHOUT EXTRA WORK Gateway Mcp-Method Mcp-Name MCP-Protocol-Version Mcp-Param-* (opt-in) Verb and object, asserted by the client. MCP server the full body Source of truth. MUST reject a header that disagrees with it. Backend the effect Plus whatever the server chose to pass on.
Figure 1 / Where enforcement can now sit, and what each point can read without opening the body.

The gateway sees the verb and the object, not the arguments

Here is the constraint that decides how much governance you can practically put at the edge.

Mcp-Name gives you the tool name. It does not give you the arguments. A gateway watching agent traffic can tell you that execute_sql was called four hundred times this morning. It cannot tell you what any of those queries said without falling back to parsing the body, which is precisely the work this revision was designed to make unnecessary.

There is a mechanism for exposing arguments, and it is opt-in on the server’s side. A server marks a parameter in its tool schema with an x-mcp-header annotation, and conforming clients then mirror that value into an Mcp-Param-{Name} header. The specification constrains it. Only primitive types, and integers within the JavaScript safe range. Not number. Only properties statically reachable from the schema root through a chain of properties keys, never through arrays, never through oneOf, anyOf, allOf, not, if, then, else or $ref. Values that cannot travel safely as plain ASCII, whether because of non-ASCII characters, control characters or surrounding whitespace, are base64-encoded behind a sentinel marker, and the same rule applies to Mcp-Name.

Be careful about the conclusion here, because it is easy to overstate. Those rules do not forbid the interesting parameters. A top-level string is perfectly eligible, and the specification’s own worked example is an execute_sql tool whose query parameter simply is not annotated while region is. What limits edge visibility is not the schema rules. It is the server author’s choice, plus the practical reality that nobody wants a multi-kilobyte SQL statement in an HTTP header traversing their logging stack.

That distinction matters, because it tells you where to apply pressure. If you want a parameter enforceable at the gateway, someone has to decide to expose it, and that someone is usually not you. Coarse dimensions get exposed: region, tenant, environment, a boolean. The payload does not.

So the honest description of what moved is narrower than the headline. Routing, rate limiting, tenancy and tool-level authorisation moved to the edge cheaply. Judgement about what the agent is doing to what did not. If your control story is that a gateway will catch it, the gateway catches it at the resolution the headers happen to carry.

Two sources of truth in one request

The specification is unusually candid about the risk it has created. Servers that process the body must reject any request whose headers disagree with it, returning 400 Bad Request and JSON-RPC error -32020, HeaderMismatch. The stated reason is to prevent vulnerabilities when different components in the network rely on different sources of truth, giving as its example a load balancer routing on the header value while the MCP server executes based on the body value.

That is a confused deputy described in a standards document, and the mitigation is a validation step the server must perform correctly on every request. There is a second condition in the same section, softer and easier to miss. It sits in a note rather than the normative text, and it says should rather than must: intermediaries that enforce policy based on mirrored headers should verify that the declared protocol version is one that requires header-body validation, and reject the request otherwise. Equixly’s analysis, published a week after the specification, flags the same thing.

Put plainly. A gateway that trusts Mcp-Name without checking the protocol version can be told a call is one thing while the server is told it is another. The specification anticipated it and wrote the mitigation down as a recommendation. Whether your deployment implements it is a question about your configuration, and nothing in the standard will fail loudly if you do not.

What the protocol stopped keeping

The revision removed more than the session, and the removals are where the accountability question really lives.

Stream resumability is gone. The Last-Event-ID header and SSE event IDs are no longer part of the transport. A broken response stream loses the in-flight request, and the client must re-issue it as a new request with a new request ID. Equixly’s reading of the consequence is worth quoting accurately, because it is narrower than the version I have seen repeated: the core protocol defines no idempotency key, so whether a retry duplicates a payment, a deployment, a message or a deletion depends entirely on whether the tool underneath was written to notice. Redelivery was never an idempotency guarantee. But it did mean a dropped connection sometimes resolved itself, and now it never does. Agents retry. That is what they do.

The Logging feature is deprecated. So are Roots and Sampling. The suggested migration for logging is to write to stderr on stdio, or to use OpenTelemetry. In the same release the specification documented OpenTelemetry trace context conventions for _meta, covering traceparent, tracestate and baggage.

Taken together, those changes say something worth sitting with. The protocol has stopped attempting to be the place where the story of a request is held. It has handed that job to your telemetry stack and told you which conventions to use. That is a reasonable engineering decision. It is also a transfer of responsibility, and transfers of responsibility are the thing organisations are worst at noticing when they arrive disguised as a version bump.

The correlation problem, specifically

Before this revision, a session gave you a natural correlation key. The server minted an Mcp-Session-Id, the infrastructure could route on it, and requests belonging to one connection shared it. It was never an audit trail. It was a spine you could hang one on, and it existed whether or not anyone had thought about the problem.

That spine has been withdrawn, and the thing most likely to be reached for instead is weaker than it looks. Client identity now travels in clientInfo on a should, not a must. Build a correlation story on it and you have built on something a conforming client is allowed to omit.

What replaces it properly is whatever your application mints and propagates deliberately, using the trace context conventions the specification now documents, and whatever your gateway is configured to write down. Both are choices somebody has to make.

Which sharpens the question the change raises. Not whether your agents are governed. Whether, if a customer or a regulator asked you next quarter to reconstruct what an agent did on a given afternoon and on whose authority, you could do it from records that already exist. Under the old shape the answer was sometimes recoverable by accident. Under the new one it is deliberate or it is absent.

Where the legal obligation actually sits

It is tempting to reach straight for the EU AI Act here, so it is worth being careful about what it does and does not say.

Article 12 requires that high-risk AI systems technically allow for the automatic recording of events over the lifetime of the system, at a level of traceability appropriate to the intended purpose of the system. Two qualifications matter before anyone quotes it at a delivery team. It bites only on systems classified high-risk under Article 6 and Annex III, which most agent tooling is not. And the duty falls on providers, through the obligations in Articles 16 and 19, and on deployers under Article 26(6). It attaches to parties, not to a protocol.

Which is the point, and it survives the qualifications. Where the obligation applies, it is unaffected by what MCP does or does not keep. The transport getting simpler does not make the record-keeping duty smaller, and nobody has ever successfully told an auditor that the standard stopped tracking it.

For everyone outside the high-risk perimeter, the pressure is commercial rather than statutory, and it arrives through contracts, security questionnaires and incident reviews. The question is the same one either way. It just gets asked by a different person.

The ground is still moving

One more thing worth naming, because it affects how confidently anyone should be building right now.

In April, AWS shipped elicitation, sampling and progress notifications as client capabilities on Bedrock AgentCore Runtime, written against specification version 2025-11-25, on the stated reasoning that stateless MCP servers could not handle those scenarios. Six weeks later the release candidate for this revision appeared, and by 28 July one of those three, Sampling, was deprecated, while the delivery mechanism for elicitation had been replaced wholesale by the Multi Round-Trip Requests pattern. Elicitation itself survives, as do progress notifications. The shape they arrive in does not.

That is not a criticism of anyone, and it is a smaller claim than the version doing the rounds. It is an observation about tempo. The specification has since adopted a feature lifecycle policy with a minimum twelve-month deprecation window, which is a real improvement and a tacit admission that the previous pace was hard to build against.

It is also a reason to be careful about where you put your controls. Controls that depend on protocol features inherit the protocol’s release cadence. Controls that sit on your own infrastructure and reason about your own identifiers do not.

What to do about it

Six things, in the order I would do them.

  • Find out which revision you are on. Every compliant request POST now carries MCP-Protocol-Version, though the specification declines to define header requirements for notification POSTs, so build the audit around requests. If nothing in your estate can answer the question today, that is the first finding, not the transport change.
  • Decide what the gateway is for before you build it. It can enforce on method, object name and whichever parameters a server has chosen to expose. Write that boundary down, so nobody later assumes it covers the payload.
  • Check the mismatch handling on both sides. Servers must reject header-body disagreement with -32020. Intermediaries are only advised to refuse enforcement on unvalidated protocol versions, which means nothing will remind you. Test both.
  • Mint your own correlation identifier and carry it end to end. The specification has told you where to put it, in the OpenTelemetry trace context conventions in _meta. Do not build the story on clientInfo, which clients are only advised to send.
  • Assume retries. The protocol defines no idempotency key. Anything that moves money, provisions infrastructure, sends a message or deletes something needs a key of its own, held by the tool.
  • Ask what the record is for. Traffic logs answer what was called. Accountability questions are about who authorised it, against which policy, and what happened as a result. Those are different fields, and the second set does not appear because you turned on the first.

The trade, stated plainly

Google’s own sentence is the fairest summary anyone has written of this change. As the responsibility of managing state shifts from the transport layer to the application layer, security becomes paramount.

The convenience moved. The responsibility moved with it. The difference is that the convenience arrived by upgrade and the responsibility has to be picked up on purpose.

If your agent traffic crossed a gateway tomorrow, would the record hold up when someone asked for it?


Jason Cruz Falzon is the creator of the Cognitive Delivery Framework and the Cognitive Delivery Index. jason@cognitivedelivery.co.uk · cognitivedelivery.co.uk

Sources

Written in the open

New material lands week by week

Research, the kernel devlog, news and the papers are all published here first. Get them by email, or follow the feeds.

Or follow the work directly: Research · Kernel devlog · News · CDI assessment