Model Context Protocol Security: How the MCP Transport Layer Changes the Inspection Boundary
The Model Context Protocol standardizes how an LLM client connects to tool servers and exchanges context, tool calls, and tool results. The 2026-07-28 revision removed protocol-level sessions and the GET stream, and it now mirrors the method and target name into HTTP headers specifically so intermediaries can route and inspect a call without parsing the body. This piece walks through the two transports, what the Streamable HTTP request actually carries, the OAuth 2.1 requirements the spec places on servers, the identity-aware policy decisions a deployment commits per call, and the audit record format that survives an Article 12 review.

TL;DR
- MCP security belongs at the transport boundary: inspect Streamable HTTP at the gateway, and control stdio through process-launch rules.
- Reject HTTP requests with a missing or old MCP-Protocol-Version before trusting mirrored headers, then reject any header-body mismatch.
- Bind each call to a verified person and server audience, authorize the agent’s allowed tools, and inspect or block sensitive parameters before forwarding.
- Treat explicit state handles as authorization data, not authentication, and record the verified identity, policy decisions, tool arguments, and result data for review.
The Model Context Protocol (MCP) standardizes the connection between an LLM client and the external tool servers it calls. The protocol defines a request shape (tools/call, resources/read, prompts/get, and the corresponding list methods), a response shape, and a transport binding the client and server agree on. Two standard transports exist: stdio, where the client launches the server as a subprocess and they exchange newline-delimited JSON-RPC over the standard streams, and Streamable HTTP, where every message is an HTTP POST to a single MCP endpoint. The transport determines where the inspection boundary sits, and the deployer's enforcement architecture follows from that.
Two things changed since most MCP security writing was published, and both matter for anyone putting a policy layer in front of these servers. The 2026-07-28 revision, which became the current specification on 28 July 2026, removed protocol-level sessions and the standalone GET stream: no Mcp-Session-Id, no Last-Event-ID resumption, a stateless core where every request carries its own metadata. And the same revision started mirroring selected body fields into HTTP headers for the express purpose of letting intermediaries route and inspect requests without parsing the body. The specification now writes gateway behaviour into the transport binding, which is a considerably better position than the one this article described a year ago.
I want to walk through the two transports, what a Streamable HTTP request actually carries, the OAuth requirements the spec places on servers, the identity-aware policy decisions a deployment commits per call, and the audit record format that survives an EU AI Act Article 12 review.
The two MCP transports
The stdio transport launches the MCP server as a subprocess of the MCP client. The client writes JSON-RPC messages to the server's stdin and reads them from stdout, delimited by newlines, with stderr available for logging. The communication traverses no HTTP endpoint. An inspection layer at the HTTP boundary cannot see stdio traffic, because that traffic never leaves the process pair. The specification is explicit that stdio implementations should retrieve credentials from the environment rather than following the MCP authorization flow. stdio suits trusted local servers, a CLI tool or a local database client, where the client and server already share a trust boundary.
Streamable HTTP connects the client to the server over a single HTTP endpoint that accepts POST. Every JSON-RPC request or notification is its own POST. The server answers with either a single JSON object or an SSE stream scoped to that one request, carrying progress notifications and then the final response. Long-lived change notifications now arrive on the response stream of an explicit subscriptions/listen request rather than on a general-purpose channel. The transport crosses the network, terminates TLS at the server endpoint, and exposes request and response payloads at the HTTP boundary. This is the transport any non-local production MCP server uses.
The older HTTP+SSE transport from protocol version 2024-11-05 has been deprecated since protocol version 2025-03-26 and is now formally classified as Deprecated under the feature lifecycle policy, eligible for removal. A deployment still running it is running a transport with a scheduled end date, and the fallback path (POST first, fall back to GET-plus-endpoint-event on a 4xx) is worth knowing about because a policy layer sitting in the middle sees both shapes during migration.
The inspection boundary a deployment owes sits on the HTTP transport. stdio is out of scope for an HTTP-layer inspection product, and saying so plainly is more useful than pretending otherwise. A mixed deployment covers its HTTP servers through the inspection layer and its stdio servers through operating-system process-launch policy: which binaries may be launched, by which user, with which arguments.
What the Streamable HTTP request carries
Four classes of data reach the inspection layer, and one of them is new.
The first is the JSON-RPC envelope: the method, the parameters object, and the request identifier. The envelope is what the inspection layer matches against the server's tool catalog.
The second is the call parameters. For tools/call, the tool name and arguments. For resources/read, the URI being requested. These carry the content the agent's planner produced, which is to say the least trustworthy part of the request.
The third class is the one the 2026-07-28 revision added, and it changes the economics of inspection. The transport mirrors body fields into HTTP headers so that load balancers, gateways and observability tooling can route and inspect without deserializing JSON. MCP-Protocol-Version is required on every POST. Mcp-Method carries the method. Mcp-Name carries params.name or params.uri, so a tools/call to get_weather arrives with Mcp-Name: get_weather in the headers. Both are required for compliance. Servers may additionally annotate individual tool parameters with x-mcp-header in the tool's inputSchema, which mirrors that parameter into an Mcp-Param-{Name} header, and clients must honour those annotations. A Spanner tool that takes a region argument can surface Mcp-Param-Region: us-west1 for a gateway to route or restrict on. Values that will not survive as plain ASCII are carried Base64-encoded between the sentinels =?base64? and ?=.
The body remains the source of truth, and the spec closes the obvious gap that creates. Any server that processes the body must validate that the header values match it, decoding the Base64 sentinel form first, and must reject a mismatch with 400 Bad Request and JSON-RPC error code -32020 (HeaderMismatch). The stated reason is precisely the split-brain risk a policy layer introduces: a load balancer routing on the header while the server executes on the body.
There is a second instruction aimed directly at anyone building enforcement on these headers. An intermediary that enforces policy on the mirrored values should first verify that MCP-Protocol-Version names a revision requiring header-body validation. If the version is older, or the field is missing entirely, the correct response is to reject the request outright rather than to act on values nothing has checked. A gateway that skips that check can be fed a 2025-03-26-era request whose Mcp-Name header says one tool and whose body calls another. Treat the version check as a precondition of trusting any mirrored header.
The fourth class is the response payload: the tool result, including whatever the server retrieved on the agent's behalf. The classifier reads it and tags the data classes it carries.
What the specification requires of the server
An inspection layer that assumes MCP servers are unauthenticated is designing against a version of the protocol that no longer exists. Authorization is optional in the sense that a server may choose not to implement it, but a server that does implement it is bound to a specific shape, set out in the authorization specification.
A protected MCP server acts as an OAuth 2.1 resource server. RFC 9728 Protected Resource Metadata is mandatory on the server side, and it is how a client learns which authorization servers protect the resource. Discovery on the client side runs through that document, taking the resource_metadata parameter from a WWW-Authenticate header on a 401 or falling back to the well-known URI. Resource Indicators (RFC 8707) are also mandatory for clients: a resource parameter naming the canonical URI of the target server goes on both the authorization request and the token request, sent whether or not the authorization server is known to support it. PKCE with S256 is required, and a client whose authorization server metadata omits code_challenge_methods_supported must refuse to proceed rather than fall back.
Then the two rules that matter most for a proxy. Servers must validate that an access token was issued specifically for them as the audience, and must reject anything else. And token passthrough is explicitly forbidden: a server must not accept tokens that were not issued for it, and where it calls an upstream API it must obtain a separate token rather than forwarding the one it received. The security best practices document gives the reasoning, and the accountability argument is the one worth quoting to an audit team: with passthrough, the downstream resource server's logs show requests that appear to originate from a different identity than the component actually forwarding them.
An enforcement layer inherits an obligation from that. A proxy that terminates TLS and re-originates the call is, structurally, in the position the spec warns about. The correct behaviour is to preserve the audience relationship rather than to relay bearer tokens across an audience boundary, and to record the identity it verified rather than the identity a downstream log will infer. The MCP OAuth authorization piece goes deeper on the flow.
Sessions being gone also retires an old attack and introduces its replacement. Where earlier revisions warned about session hijacking against Mcp-Session-Id, 2026-07-28 describes state handle hijacking: servers that need cross-request state mint an explicit handle and receive it back as an ordinary tool argument. Servers must verify every inbound request, and possession of a handle is explicitly not authentication. The recommended binding keys stored state as <user_id>:<handle>, with the user ID derived from the verified token rather than supplied by the caller, so a guessed handle presented by the wrong principal gets rejected. An inspection layer that sees a handle in tool arguments is looking at an authorization-relevant field, not an opaque blob.
The identity-aware policy decisions the deployment commits per call
The deployment commits six decisions per MCP call.
The first is the version precondition. The layer reads MCP-Protocol-Version and confirms it names a revision whose servers are required to validate headers against the body. A request that omits the header, or names an older revision, gets rejected rather than evaluated, because every subsequent decision that reads a mirrored header would otherwise be reading an unverified claim. This is the cheapest of the six and the one most implementations skip.
The second is identity verification. Authorization travels in the Authorization: Bearer header, and the specification requires it on every HTTP request from client to server even when those requests belong to one logical interaction, which is convenient: there is no handshake-only credential to miss. The layer verifies the token, checks that its audience names the MCP server the request is actually addressed to, and resolves the natural person the agent is acting for. A request that arrives without a resolvable person behind it produces a block at the boundary, because a record naming only a service account will not answer the question an auditor asks.
The third is tool authorization. The policy bundle bound to the agent's route describes which tools that agent may call. A tools/call against a tool outside the list produces a block. Since the bundle is keyed on the agent identifier and the route, two agents hitting the same MCP server can hold different tool subsets. The Mcp-Method and Mcp-Name headers make this decision cheap: the layer can reach a routing verdict before touching the body, then confirm against the body before allowing the call through.
The fourth is parameter inspection. The classifier reads the tool call arguments and tags the data classes they carry. An argument pushing customer PII into a tool the policy disallows for PII produces either a redaction, masking the PII before forwarding, or a block. Any Mcp-Param-{Name} headers the tool schema declares get decoded and compared against the body values here, since a mismatch is both a spec violation and a signal worth logging.
The fifth is response classification. The classifier reads the tool result and tags what it carries. A result returning data above the agent's authorized classification is redacted before it reaches the agent's next context window. This is also where the injection-via-tool-return-value case lands: masking injection-carrying content before the planner reads it, which is the one place in an agent loop where a policy layer can get between untrusted retrieved text and the model. Tool poisoning prevention covers the adjacent case where the tool description rather than the return value carries the payload.
The sixth is the audit commit, described below.
The audit record format that survives Article 12
Article 12 of the EU AI Act requires high-risk systems to technically allow automatic recording of events over the system's lifetime, sufficient to identify risk situations, support post-market monitoring, and support the deployer's Article 26(5) monitoring duty. The identification of the natural persons who verified a result is an explicit item only for the remote biometric identification systems in Annex III point 1(a), under Article 12(3)(d), and it is worth being precise about that rather than overclaiming. Article 19 and Article 26(6) then set a six-month retention floor for providers and deployers respectively.
The date moved. Regulation (EU) 2026/1744 entered into force on 27 July 2026 and deferred the standalone Annex III high-risk obligations to 2 December 2027, with product-embedded Annex I systems following on 2 August 2028. An agent platform being designed now has more runway than last year's plan assumed, and the same amount of work. Alongside that, NIST AI RMF MANAGE 1.3 asks for evidence that AI risks are tracked across the lifecycle, and ISO/IEC 42001 Annex A covers the operational records an AI system produces in service.
The MCP audit record carries ten fields per call:
- The natural-person identifier the agent is acting for.
- The agent identifier.
- The MCP server identifier, as the canonical resource URI the token was audience-bound to.
- The method invoked, cross-checked between
Mcp-Methodand the body. - The tool or resource targeted, cross-checked between
Mcp-Nameand the body. - The classification assigned to the parameters and to the response.
- The protocol revision the call declared.
- The policy version that evaluated the call.
- The decision outcome, one of pass, modify, redact or block.
- The integrity metadata that chains this record to the previous one.
Recording the protocol revision alongside the decision is the field teams leave out and then need. When a revision changes what the transport guarantees, as 2026-07-28 did by removing sessions, the only way to reason about a record written six months earlier is to know which rules were in force when it was written.
The record series joins to the agent's broader request series on the agent identifier and the natural-person identifier. A regulator query along the lines of "show me the MCP calls a specific data subject's interaction produced" returns the tool calls in time order, joined to the model decisions that emitted them. The retention and chaining mechanics are covered in AI audit log immutability.
The deployment topology that fits production MCP
The path a single tool call takes, in order:
- The agent runtime hands the MCP client the identity context: the natural person and the agent identifier, plus the access token audience-bound to the target server.
- The MCP client POSTs to the MCP endpoint, carrying
MCP-Protocol-Version,Mcp-Method,Mcp-Nameand anyMcp-Param-*headers the tool schema declares. - The inspection layer checks the protocol revision, verifies the token and its audience, resolves the person, authorizes the tool against the route's policy bundle, classifies the parameters, and confirms every mirrored header against the body.
- The MCP server executes the tool.
- The response returns through the inspection layer, which classifies it, redacts what the agent is not cleared to see, and commits the audit record.
- The sanitized result reaches the MCP client and then the agent's next context window.
The layer holds no state across calls, which the 2026-07-28 core makes straightforward: with protocol sessions gone, every request already carries its own metadata and its own credential, so there is nothing to correlate and no session table to keep warm. Any state the MCP server needs lives on the server side behind an explicit handle. The two record sets compose without overlapping, the server's logs covering its internal handling and the inspection layer's records covering the regulatory obligations.
DeepInspect
DeepInspect is the inspection layer at the HTTP-transport MCP boundary. The product terminates the TLS between the MCP client and the MCP server, reads the JSON-RPC envelope and the tool call parameters and the response payloads, evaluates identity-bound policy per route, applies pass, modify, redact, or block decisions, and commits per-decision audit records to a tamper-evident store with hash chaining across records.
The product runs as a stateless proxy in front of the deployment's Streamable HTTP MCP servers, which lines up with the direction the protocol itself took in 2026-07-28. stdio MCP servers stay covered by the operating system's process-launch policy, the appropriate boundary for traffic that never crosses a network. A mixed deployment covers its HTTP servers through the inspection layer and its stdio servers through existing host-level controls, and we would rather say that plainly than claim a boundary the architecture does not have.
If your team is rolling out MCP across the agent stack and the audit team is asking how tool-call lineage gets recorded, we should compare notes. Let's talk today.
Frequently asked questions
- What changed for gateways in the 2026-07-28 revision?
Three things. Protocol-level sessions are gone, so
Mcp-Session-Idis no longer minted or echoed and a server that only speaks this revision should ignore it. The standalone GET stream is gone, so a GET or DELETE to the MCP endpoint should return405 Method Not Allowed, andLast-Event-IDresumption is not supported. And request metadata is now mirrored into headers that intermediaries are explicitly invited to route and inspect on, with a matching server-side duty to reject any header that disagrees with the body. The net effect is that a gateway can make a routing decision from headers alone and still be backed by a spec-level guarantee that the headers are not lying.- Does the inspection layer buffer SSE responses?
It must not, and the specification anticipates the failure. Servers are told to send
X-Accel-Buffering: nowhen opening an SSE stream, because a reverse proxy that accumulates events before forwarding them adds latency and can break the streaming behaviour the client expects. Any inspection layer in that path needs to stream through rather than accumulate, which means classification and redaction on the response side has to work incrementally. Cancellation compounds this: on Streamable HTTP, closing the response stream is itself the cancellation signal, so a proxy that holds a stream open after the client has gone leaves the server working on an abandoned request.- How does the inspection layer handle MCP servers running on localhost?
A localhost MCP server reached over HTTP (the loopback address with a chosen port) traverses the local TCP stack but not the network. The inspection layer can sit on the loopback path through an iptables or eBPF redirect, the same way a transparent proxy interposes on local traffic. The policy decisions and audit records work identically. The decision to interpose on localhost depends on the trust boundary the deployer assigns to the localhost server.
- What happens when a tool call's response carries content that would be a prompt injection?
The inspection layer's response classifier reads the tool result and tags the data classes the result carries. The classifier also runs an injection-content scan against the result. A result that matches the injection patterns produces a redact decision: the injection-carrying content is masked before the result reaches the agent's next context. The agent's planner reads the sanitized result and the injection cannot reach the model's decision surface. The audit record captures the redaction so the security team can review the patterns over time.
- Does the layer support MCP servers we run for partners outside our SSO?
The inspection layer can apply per-route policy that handles partner-MCP servers under a partner-identity context. The partner's MCP server reaches the deployer's MCP client through the HTTP boundary the inspection layer covers. The policy bundle for partner routes describes the tools the partner-side server can call into and the classifications the deployer accepts in the responses. The audit record carries the partner identifier on each call so the deployer can review the partner-route activity separately from internal-route activity.