Covers MCP revisions 2024-11-05 through 2026-07-28. Verified against the specification in August 2026.

The Model Context Protocol has been revised five times since November 2024, and across those revisions the specification has steadily reduced the set of responsibilities it claims for itself. Authentication and token issuance were moved out first, over two revisions, to the identity provider an organisation already runs. Client identity was moved next, anchored to control of a domain rather than to a registration endpoint anyone could call. Consent followed in an extension that reached stable status in June 2026, relocating the decision from the employee to the administrator who is positioned to evaluate it, and session state and request routing were both moved out in the revision published in July, to the application and the gateway respectively.

The reduction accounts for most of the reason the protocol is now deployable inside an organisation, but the more useful observation concerns what it leaves behind. Each responsibility that was given up went to a party with a stronger claim to holding it, and after four rounds of this a single trust boundary remains for which no such party exists. That boundary is the point at which text authored by a third party enters a language model’s context and is read through the same channel as the model’s own instructions. Every attack class that has succeeded against MCP deployments in practice is located there, and the specification’s security documentation, which enumerates eleven attack classes without addressing any of them, concedes as much by omission.

What follows establishes the architecture the argument depends on, works through the five revisions in order, and then examines the boundary that could not be reassigned. An earlier post on policy engines observed in passing that this specification had changed materially three times during 2025, which is the observation this piece was written to discharge.

What MCP puts where

An MCP deployment has three components. A host application, which is a desktop client or an IDE or an internal agent, contains an MCP client, and that client speaks JSON-RPC to one or more MCP servers that expose capabilities. Stripped of the framing, this is an RPC protocol whose value proposition is combinatorial, since M applications integrating against N systems bespokely requires M×N pieces of integration code and a protocol reduces that to M+N. The property that makes integration cheap is that any client can talk to any server, and the security consequence follows directly, because any client can therefore talk to any server including one written by somebody hostile.

Servers expose tools, resources and prompts. Tools carry nearly all of the security weight, since a tool call is the moment at which a language model’s output becomes an action taken in some other system. A tool is defined by a name, a JSON Schema describing its arguments, and a natural-language description, and it is worth being precise about where that description goes, because the whole of the second half of this piece depends on it. When a client asks a server what tools it offers, the descriptions in the reply are placed into the language model’s context verbatim. They are read by the model in full and are ordinarily never displayed to the user.

Two transports are defined. The stdio transport launches a server as a subprocess of the host application, which means the server inherits the authority of the operating-system user, retrieves credentials from its environment, and presents no network surface. The HTTP transport, which since March 2025 has been Streamable HTTP, makes servers network-reachable and multi-tenant, and it is the transport for which the authorization specification is written. Authorization is optional at the protocol level and is defined only for HTTP, which is a detail worth carrying forward, because it determines how much of what follows applies to any given deployment.

Four boundaries

A trust boundary is a location at which data or instructions pass from one authority domain into another, and where something is consequently obliged to validate what has arrived. The decomposition that matters for MCP is by authority domain rather than by network hop, for the reason that the boundary which turns out to be undefendable involves no network hop at all and would be invisible to any decomposition organised around transport.

Four such boundaries exist in a deployment of the shape described above. The first separates a user’s intention from the action a model takes on their behalf, and it is crossed whenever an approval is granted or withheld. The second separates text supplied by a server from the model’s reasoning, and it is crossed every time a tool description or a tool result is placed into context. The third separates the client from the server across a network, and it is crossed by every request that carries a credential. The fourth separates the server from whatever system it calls downstream, and it is crossed whenever delegated authority is spent.

Almost every MCP vulnerability disclosed to date is a failure to recognise one of the four. The figure below is worth returning to as the chronology proceeds, since each revision can be located against a specific boundary and the pattern that emerges from doing so is the argument of this piece.

Four trust boundaries · two owners, one half, one none
Where instructions or data cross between authority domains, validation is required. Select a boundary.
where the boundaries fall
resource owneruser
host application
single token streammodel context
transport + authMCP client
resource serverMCP server
downstream systemsJira · S3 · database · SaaS
what crosses, and who owns it
what crosses
primary risk
what the specification provides
handed to
attacks that live here

Scope and audience

Bearer semantics are assumed here, and the consequence that matters is that a token presented by a party which acquired it improperly cannot be distinguished, at the point of validation, from the same token presented by the party it was issued to. Any constraint has therefore to be carried in the token itself. Scope has carried one such constraint since the first MCP revision to specify authorization at all, restricting the operations a holder may invoke. Audience carries the other, restricting the resource server at which the token will be accepted. Its absence from the March 2025 model is the specific defect the June revision was written to repair, since a token that enumerates scopes without naming a destination will be honoured by every resource server in the issuing domain that recognises those scopes, which turns the compromise of any single server into a credential-harvesting position against all of them.

The figure below carries one sequence per revision and is referred to from each of the five sections that follow. Reading the five in order shows the parties multiplying from two to three and the checks migrating outward from the server to the parties better placed to perform them.

What passes between the parties
sequence
on the wire

2024-11-05: nothing had been claimed yet

No boundary defended.

The first stable release specified the JSON-RPC core, the three primitives and the two transports, and it contained essentially no authorization guidance, extending to no direction on the use of Authorization headers. The omission was consistent with the deployment model of the period, in which servers ran as local subprocesses and inherited the authority of the operating-system user, so that the trust model governing an MCP server was the model governing any locally installed command-line tool. Installation was the only boundary carrying weight, and operating-system sandboxing was the appropriate control for it.

Setting the figure above to 2024-11-05 shows the whole of the exchange. The host spawns the server, the environment it inherits carries whatever credentials the server will use, and the two ends exchange tools/list and tools/call over stdin and stdout. No token appears anywhere in the sequence, because nothing has been delegated and there is consequently nothing to validate.

The relevant generalisation concerns the manner in which such models fail. A security model is rarely wrong at the time it is written, and becomes wrong when the deployment context shifts beneath it without anything in the specification announcing that the shift has occurred. MCP servers became network-reachable and multi-tenant during the first quarter of 2025, at which point the implicit model was silently invalidated.

2025-03-26: the specification claims too much

Boundary 3, attempted.

The March revision replaced the original two-endpoint transport with Streamable HTTP, introduced tool annotations, and added JSON-RPC batching, which was removed three months afterwards. Its substantive change was an OAuth 2.1 framework for HTTP transports under which the MCP server was designated as its own authorization server in addition to its existing role as resource server.

The obligations carried by that designation are easy to understate when they are described abstractly. An authorization server conducts user authentication, records consent, issues and rotates and revokes tokens, validates PKCE parameters and manages client registration, which is a body of work that Okta and Microsoft staff with dedicated teams and still occasionally get wrong. Under the March design, exposing a single tool over HTTP required implementing that component first.

The 2025-03-26 sequence shows what that arrangement looks like on the wire. The 401 points at metadata published on the same host, registration happens at that host’s own /register endpoint, and the authorization and token endpoints belong to it as well, so the exchange never leaves mcp.acme.example. The token returned at the end of it carries no aud claim.

The objection was raised structurally by Aaron Parecki, who edits the OAuth specifications at Okta, in Let’s fix OAuth in MCP on 3 April 2025. It did not rest on a defect in any particular implementation, which is what made it difficult to answer by patching one.

A second objection concerned enterprise architecture, where organisations operate exactly one authorization server, which is their identity provider, and treat every backend system as a resource. A design under which each of two hundred internal MCP servers is also an identity provider produces two hundred consent surfaces and no central point of revocation. Roughly three months elapsed between the publication of the criticism and the ratification of a redesigned specification, which is short for a standards process and indicates how much weight the problem was carrying.

2025-06-18: identity is reassigned to the identity provider

Boundaries 3 and 4.

MCP servers were reclassified as pure OAuth resource servers, discovery was moved to Protected Resource Metadata under RFC 9728, and resource indicators under RFC 8707 were made mandatory. This is the pivotal revision, and the mechanism is most easily followed by switching the figure above to 2025-06-18 and stepping through it, since three parties now appear where there had been two.

A client issues a request carrying no token and receives a 401 whose header points at a metadata document, that document names both the authorization server governing the resource and the resource’s own canonical identifier, and the client then presents itself to the named authorization server with a resource parameter identifying the server the token is intended for. The token returned carries a matching aud claim, which the MCP server validates against its own identifier before processing anything. The merit of the 401 is that it publishes a pointer to the resource’s governance instead of requesting a credential, which converts an unknown URL into a discoverable authorization graph without client-side configuration. The header has been optional since 2025-11-25, with the same document reachable at the well-known path.

The failure this prevents is worth stating concretely. A server is stood up at traveI.example.com, where the fourth character is a capital i rather than a lowercase L and the substitution is invisible in most typefaces. A user induced to connect to it causes a genuine token to be issued by their own identity provider, and the impostor then presents that token at the real travel.example.com. The replay control in the figure demonstrates both designs. Once resource is mandatory the impostor’s token names the impostor in its aud claim and is rejected on arrival, which reduces a systemic credential-theft risk to a contained one.

Token passthrough was named and prohibited in the same revision, with servers instructed that they “MUST NOT accept any tokens that were not explicitly issued for the MCP server.” Where a downstream call is required the sanctioned mechanism is token exchange under RFC 8693, in which an audience-bound token is presented to the authorization server and a different token, scoped to the downstream API, is returned, so that user context is propagated while authority is not.

2025-11-25: client identity is reassigned to domain control

Boundary 3.

Under dynamic client registration any party may register, with the consequence that a client_id carries no evidence about the identity of the caller and nothing prevents an attacker from registering a client that is presented to users as an official integration. The registration endpoint was nonetheless the only practical answer to the many-to-many topology between clients and servers, so removing it outright was never available and the problem had to be solved by changing what an identifier is.

Client ID Metadata Documents make the client_id an HTTPS URL that resolves to a document the client publishes, which the authorization server retrieves and caches. Registration in the earlier sense disappears, and what replaces it is a claim the authorization server is able to verify for itself at the moment the claim is made.

The 2025-11-25 sequence differs from its predecessor at exactly one point. When the client presents its URL-form client_id, the authorization server issues a request in the opposite direction, retrieving the metadata document from the client’s own domain and validating the supplied redirect_uri against what it finds there. Every other step is unchanged.

One further change in this revision anticipates the second half of the argument. URL-mode elicitation relocates credential entry and OAuth and payment flows to a trusted external URL so that the secret never enters model context at all. Since a language model cannot be made to handle secrets reliably, the design routes them around the model instead of attempting to constrain its behaviour, and architectural avoidance of that kind is preferable to behavioural control wherever it is available.

Boundary 1, in part.

An employee connecting an agent to fifteen internal servers is presented with fifteen consent dialogs, and the habituation that results constitutes a security failure rather than an inconvenience, because a user conditioned to approve fifteen requests will approve the sixteenth without inspection. The dialogs also pose a question the employee has no basis on which to answer.

Enterprise-Managed Authorization reached stable status on 18 June 2026. It is an extension, versioned independently of the core and forming no part of any of the five revisions, a point most secondary coverage reports incorrectly. The flow proceeds in three steps, beginning when the employee authenticates to the client through corporate single sign-on, continuing when the client exchanges the resulting identity assertion at the identity provider for a short-lived grant known as an ID-JAG, and completing when that grant is presented to the MCP authorization server in return for an access token.

Enterprise-Managed Authorization · who decides
An employee connects an agent to one of fifteen internal servers. The decision is taken at step two, without their involvement.
Scope note: this governs connection, not individual calls. Per-action authorization remains with the resource server.

Policy is evaluated at the middle step by the employer, and the denied branch in the figure is the more instructive of the two. Where the user falls outside the approved group the chain terminates at the identity provider, no token is issued, and nothing whatever is displayed to the employee, whereas under ordinary OAuth that same user would have been shown a consent dialog and permitted to approve it.

Two qualifications apply to the extension. It determines whether a user may connect a given client to a given server and with which scopes, and it does not address whether a particular call carrying particular arguments should proceed, which remains a separate question at a separate boundary. It also landed outside the core, as the tasks feature did in the same period, which indicates that the core has ceased to be the location at which new capability is added.

2026-07-28: state and routing are reassigned

Boundary 3.

The July revision is the most substantial since remote MCP was introduced, and its central change was the removal of protocol sessions. The initialize handshake and the Mcp-Session-Id header were both retired, and each request now describes itself by carrying its protocol version and client identity and capabilities in a metadata field, so that any request may be handled by any instance sitting behind a conventional load balancer.

Two considerations favoured the change independently of one another. Operationally, a session identifier implies server affinity, and affinity implies sticky routing or replication or a shared store, each of which imposes a cost on every operator running the protocol at scale. The security argument is the stronger of the two and follows from the treatment of audience above, since a session identifier whose possession grants continued access functions as a bearer credential while lacking every protection an access token carries, having no signature and no audience restriction and no expiry and no scope and no revocation path. The specification was accordingly obliged to require that session identifiers be globally unique, cryptographically secure, bound to user identity, and never treated as authentication in themselves.

Any accompanying claim that state was eliminated would be overstated. Persistence remains necessary for ordinary functionality, and servers now issue an explicit handle which is passed back as a conventional tool argument, with the consequence that the same document which removed sessions introduces state handle hijacking as a named attack and requires that handles be verified on arrival, never treated as authentication, generated non-deterministically, and bound server-side to the authenticated user. Those requirements correspond closely to the ones just deleted, and the correspondence is directly verifiable, since the superseded session-hijacking guidance remains published at the 2025-11-25 URL that the current security page links to. What improved is the placement, since the risk now sits in a layer holding enough context to manage it, a point the tools specification states economically in observing that “a handle is a name, not a capability.”

Routing was reassigned in the same revision. The Mcp-Method and Mcp-Name headers permit a gateway to route and meter without parsing JSON bodies, and x-mcp-header goes further by allowing a tool’s input schema to designate a parameter for mirroring into an HTTP header, so that intermediaries can route on argument values without reading the payload. Tool definitions are now being shaped around what the intermediary needs to see.

The 2026-07-28 sequence shows the three consequences together. A request carries its protocol version and client identity in _meta and its method and tool name in headers, the gateway routes and meters on those headers without deserialising the body, and a state handle returned by one instance is presented to a different instance on the following call, which verifies that the handle belongs to the principal named in the token.

Five revisions, one extension · tracked by subtraction
The left column is what the trusted core still holds. Watch it shrink, and watch the one item that never finds an owner.
what the protocol holds
the revision
added
removed or deprecated

Reading the boundary tags on the five sections above in sequence produces the pattern the rest of this piece depends on. Four revisions operate on boundary 3 or boundary 4, one addresses boundary 1 in part, and boundary 2 is touched by none of them.

The responsibility that could not be reassigned

Boundary 2.

A language model receives a single undifferentiated token stream. No architectural separation exists within it between instructions written by the developer, input supplied by the user, and text returned by a tool, and training can shift the probability that a model privileges one over another without establishing a boundary that holds under pressure. Tool descriptions authored by whoever wrote the server are delivered into that stream verbatim and are ordinarily displayed to nobody. The limit this places on any specification is precise, in that a specification can constrain what a server is permitted to do while remaining unable to constrain what text does to a model once the text has arrived.

The specification appears to accept as much. The attack classes enumerated on its Security Best Practices page are the confused deputy problem, token passthrough, server-side request forgery, state handle hijacking, local server compromise, OAuth authorization URL validation, stdio transport security in proxy scenarios, mix-up attacks, localhost redirect URI impersonation, CIMD trust policies and scope minimisation. Eleven classes, and each of them is situated at a boundary for which some party held a better claim. None of them addresses what a tool description does after it has reached the model.

One vulnerability is worth working through, because it shows why the control most clients reach for does not help. Trail of Bits described it in April 2025 under the name line jumping. The reply to a tool listing enters model context before any tool has been invoked, and therefore before an approval gate has an event available to intercept.

One agent turn · when the text arrives
where do you put the approval gate?
session trace
model context at gate time

The figure demonstrates a property of approval gates that is easily missed. The gate shipped by almost every client fires on tool invocation and is misplaced in two opposite directions depending on the attack, firing six steps after the hostile text reached the model when a description has been poisoned, and two steps before the hostile content exists when a tool result has been poisoned, on a call the user legitimately intends. A single control is therefore wrong on both sides for opposite reasons, which follows from the undifferentiated stream rather than from a defect in any particular client.

Operational consequences

  • Target 2025-11-25 now, and open a 2026-07-28 branch if remote servers are in production. The migration is not a version bump, because every location holding per-session state has to be identified and either eliminated or converted to an explicit, user-bound handle. All four Tier 1 SDKs shipped support on the release date, so what remains to wait for is client negotiation, and one public server operator measured one connection in eight on the stateless revision three weeks after release.
  • Assert on the token’s audience in an integration test. A missing .well-known document and an authorization server that accepts the resource parameter without acting on it both produce a deployment that looks configured and is not, and neither condition is visible anywhere except in a token that has actually been issued.
  • Audit for fail-open elicitation. An implementation that returns success when the client cannot elicit has converted a confirmation into a formality, and servers are instructed that they “SHOULD NOT assume that elicitation requests will always succeed,” so an unsupported client has to be treated as a refusal wherever elicitation gates a destructive action. Under 2026-07-28 the client declares elicitation support on every request, so the condition is cheap to test.
  • Pin hashes of tool names, descriptions and schemas at approval time, re-verify on load, and require re-approval on drift. It is the only rug-pull defence that does not depend on a human re-reading a description. Pin per tool and per identity rather than across the listing as a whole, since a tool set “MAY vary by the authorization presented on the request” and a scope upgrade would otherwise register as drift. Settle what load means as well, since a listing now carries a server-supplied lifetime and a client honouring it may not fetch again for some time.
  • Place a gateway in front of every networked deployment, with its contents covered in the policy engine post. Tool allow-listing deserves mention, since a tool that is never listed cannot inject anything.
  • Treat a registry listing as an authenticated claim of authorship and nothing further. The registry verifies namespaces and hosts metadata without evaluating code or tool honesty or runtime behaviour, so a listing marks the beginning of an admission process rather than its conclusion. Entries are removed on report, which is a reaction rather than an evaluation, and the registry is still in preview.
  • Inventory stdio servers before anything else. Authorization is optional at the protocol level and is defined only for HTTP, and implementations using stdio are directed that they “SHOULD NOT” follow it and should retrieve credentials from the environment instead. A stdio server on a developer workstation holding production credentials therefore sits outside both the authorization specification and any gateway that has been deployed.

Three problems remain unresolved. Prompt injection is not addressable at the protocol layer, agent identity is the subject of active work with no ratified standard and was explicitly placed outside the core, and runtime per-action authorization remains the responsibility of the resource server or an external policy engine.

The residue

The reduction was correct and is substantially complete. Identity and consent and state and routing were each reassigned to a party with a stronger claim, and the result is a protocol an organisation can deploy without accepting an unreasonable quantity of bespoke security work.

What survives four rounds of reassignment is boundary 2, at which text of unknown provenance meets a model reading it through the channel that carries its instructions. No third party can be given that boundary, because the party extending trust is the model itself. Adoption of a specification revision is therefore a compatibility decision with security benefits attached, and treated as a security programme it will leave an organisation exposed at precisely the points the specification has spent two years stating that it does not address.

Get the next post

Deep dives on LLM systems: agent platforms, inference, attention. In your inbox, no spam.

Prefer a feed reader? Subscribe via RSS.

References

Specification (primary)

Standards

Analysis