← Blog

MCP Streamable HTTP Security: A Reverse-Proxy Acceptance Test

Parminder Singh
Parminder Singh··8 min read
Summarize with AI

The 2026-07-28 MCP Streamable HTTP transport sends each JSON-RPC request as its own POST and permits either a JSON response or a request-scoped SSE stream. It also exposes protocol and method metadata in required headers, plus target metadata when applicable. The server must verify those headers against the body. A production acceptance test should cover header-body mismatches, unsupported versions, OAuth audience checks, stream buffering, cancellation, and downgrade behavior.

Platform & Architectureai-securityagentic-aillm-securityidentity-and-authorizationpolicy-enforcementarchitecture
MCP Streamable HTTP Security: A Reverse-Proxy Acceptance Test

The 2026-07-28 Model Context Protocol revision gives a reverse proxy two headers for every request: MCP-Protocol-Version and Mcp-Method. It adds Mcp-Name for named targets. The server must compare them with the JSON-RPC body and reject a mismatch. That creates an inspectable HTTP control point, but only if the proxy preserves streaming, carries authorization on every request, rejects unsafe revisions, and records the exact failure.

I want to make this concrete as an acceptance test. A diagram showing "MCP gateway" proves placement. The test proves behavior when headers lie or tokens target the wrong audience. It also tests what happens when nginx decides to buffer an SSE stream.

TL;DR

  • The 2026-07-28 transport uses one POST endpoint. Each request gets a JSON response or a request-scoped SSE stream.
  • Required headers expose protocol and method metadata, plus the target when applicable. The server must reject header-body disagreement.
  • Test token audience, downgrade handling, stream buffering, cancellation, timeout behavior, and error preservation before production.
  • The HTTP path is enforceable. stdio, local process launch, and tool implementation security need separate controls.

The current transport changes the proxy contract

The official Streamable HTTP specification for 2026-07-28 defines a single MCP endpoint that accepts POST. The client sends every JSON-RPC request or notification in its own HTTP POST. The server answers a request with either one JSON object or a Server-Sent Events stream scoped to that request. Long-lived notifications use the response stream of an explicit subscription request.

The revision removed the separate GET stream and protocol-level sessions found in earlier Streamable HTTP versions. A current-only server should return 405 Method Not Allowed for methods the endpoint no longer supports. Migration code may still speak to older revisions, so the reverse proxy needs an explicit version policy rather than an assumption that every /mcp request has the same semantics.

This article narrows the broader Model Context Protocol security architecture into one deployment decision: can the reverse proxy preserve and enforce the current transport contract under adverse inputs and ordinary infrastructure failure?

Test 1: version and mirrored-header validation

Every POST in the current revision must include MCP-Protocol-Version. The value must match the protocol version in the request body's _meta. A mismatch requires 400 Bad Request with a HeaderMismatch JSON-RPC error. An unsupported revision also requires 400, with an error that lists supported versions.

Mcp-Method mirrors the JSON-RPC method for every request. Mcp-Name mirrors params.name or params.uri for tools/call, resources/read, and prompts/get. These headers are required for compliance. The server must decode any specified Base64 representation and compare headers with the body. It must reject disagreements.

Build negative cases into CI and the pre-production route test:

  • Change Mcp-Method to resources/read while the body calls tools/call.
  • Name read_customer in the header and export_customers in the body.
  • Send a current header with an older body version.
  • Omit one required header.
  • Send a malformed encoded name.
  • Name an unsupported revision.

Assert the HTTP status and JSON-RPC code. Separately assert the absence of tool execution. A 400 followed by a completed tool call is a failed control.

Test 2: proxy decisions must use verified metadata

A proxy can route or make an early policy decision from mirrored headers without deserializing the full body. That is useful only when the server revision guarantees comparison with the body. The specification tells intermediaries to verify that the declared protocol revision requires header-body validation before relying on mirrored values.

Set a minimum accepted revision on the governed route. Reject a missing or older version when the policy decision depends on Mcp-Method or Mcp-Name, including custom parameter headers. If migration requires older clients, put them on a separate compatibility route with body parsing and an expiry date. Mixing trusted current metadata with unverified legacy metadata on one policy path invites a split decision.

I would fail a production review if the gateway authorizes on Mcp-Name while the upstream server accepts an older revision that never verifies it. The green allow decision and the executed tool can then refer to different names.

Record both the declared revision and the server validation result. The MCP policy enforcement guide covers role and tool policy. The acceptance test here establishes that the fields feeding that policy deserve trust.

Test 3: OAuth audience belongs to every POST

The current MCP authorization specification applies to HTTP transports. A protected MCP server acts as an OAuth 2.1 resource server. Authorization must be present on every HTTP request, and access tokens must stay out of URI query strings.

The server must validate that the token was issued specifically for it as the intended audience. Invalid or expired tokens receive 401. A token with insufficient permission can receive 403. The specification forbids accepting or transiting tokens intended for other resources. If the MCP server calls an upstream API, it needs a separate token for that audience.

Begin acceptance testing with a valid token, then expire it and confirm the request receives 401. Present credentials issued for a neighboring MCP server and credentials intended for a downstream API. Test insufficient scope. Then place a bearer credential in the query string. Confirm that the proxy strips authorization from logs and error bodies. Then verify that the identity in the decision record came from validated token context rather than a caller-supplied header.

Token audience is especially important when one reverse proxy fronts several MCP servers. Route selection must happen before audience validation, and the selected resource URI must be the audience tested.

Test 4: SSE must remain a stream

A server can answer one POST with text/event-stream. The transport specification recommends X-Accel-Buffering: no so reverse proxies such as nginx deliver events immediately. Without it, a proxy may accumulate events and release them in a batch, adding latency and breaking expected stream behavior.

Run an instrumented tool that emits progress events at known intervals before a final result. Measure arrival at the client side of the gateway. Confirm the first event arrives before completion. Confirm that memory use stays bounded during a long response. Disable buffering explicitly in proxy configuration instead of trusting the upstream header alone.

Test cancellation separately by closing the client connection while the tool is running. Verify that the upstream work receives the cancellation signal described by the transport. Confirm the proxy releases the connection and writes a cancelled outcome rather than a success. Then test idle timeouts against a long subscription response. Keep-alive comments should pass through without being rewritten into data events.

On a whiteboard, an SSE stream is one arrow. In production, it is a socket held open through a load balancer, web application firewall, proxy, and server. Each hop gets a timeout and buffering test.

Test 5: errors must survive the intermediary

An MCP client needs to distinguish transport rejection, authentication failure, unsupported protocol, missing method, and JSON-RPC application error. A proxy that converts every failure into a branded 502 destroys that distinction and makes client recovery unsafe.

Preserve relevant status codes and JSON-RPC error bodies after applying data-loss controls. Test 400 for malformed or mismatched metadata and 401 for invalid authorization. Test 403 for insufficient permissions, 404 for a JSON-RPC method the server does not implement under the current transport rules, and 405 for an unsupported HTTP method. Confirm that the response content type remains correct.

Log the gateway decision and upstream disposition separately. A denied request should have no upstream disposition because it never reached the server. An upstream 400 HeaderMismatch after a gateway allow decision indicates that the gateway trusted bad metadata or modified the request incorrectly. That pair belongs on an alert.

The LLM audit logging implementation guide should consume these fields without collapsing transport and policy failures into one generic error.

The acceptance record

Keep one signed test record per environment and release. It should name the client and server versions, proxy build and configuration hash, accepted protocol revisions, canonical resource URI, authorization server, tested cases, observed status and JSON-RPC codes, stream timing, cancellation result, and open exceptions. Attach packet-level or structured evidence with secrets removed.

Repeat the pack when the MCP revision changes or a reverse proxy or load balancer is upgraded. Rerun it when an authorization server changes or timeout and buffering settings move. Also rerun it when an MCP server begins serving a new route behind the same listener. Shared infrastructure makes drift cheap.

The official MCP security best practices covers adjacent threats such as token passthrough, OAuth confused deputy problems, SSRF during metadata discovery, and state-handle hijacking. Build separate tests for those threats. The Streamable HTTP pack should reference them while staying focused on the observable proxy contract.

The HTTP boundary has a hard edge

This acceptance test applies to HTTP calls that cross the controlled proxy. stdio messages travel through process pipes, so the proxy cannot inspect them. Local server installation, executable provenance, filesystem access, environment credentials, and process isolation sit with endpoint and workload controls. Tool code may still contain SSRF and injection defects. It may also contain authorization defects after a compliant MCP request reaches it.

The boundary also excludes direct requests that bypass the governed route. Network policy and service discovery should force production clients through the inspected endpoint. Credentials should enforce the same path. The acceptance pack should attempt a direct connection and preserve the denial as evidence.

Precision here protects the useful claim. The gateway can enforce policy and record decisions on authenticated Streamable HTTP traffic that traverses it. It cannot secure every component called "MCP."

DeepInspect

DeepInspect can sit inline on the authenticated HTTP path between MCP clients and Streamable HTTP servers. It can evaluate identity and route policy. It can also inspect JSON-RPC request and response content while recording the protocol revision, target, policy version, and outcome for each governed decision. The acceptance pack above tests the surrounding proxy path rather than asking a product label to stand in for evidence.

DeepInspect's boundary ends with the HTTP traffic it receives. stdio, local process execution, server installation, tool implementation defects, and routes that bypass the proxy require host, application, identity, and network controls. That division should appear in the deployment record beside the passing tests.

Book a technical deep dive at deepinspect.ai.

Frequently asked questions

Should a proxy parse the JSON-RPC body?

A current proxy can use required mirrored headers for early routing and policy, provided it verifies the declared protocol revision and the upstream server enforces header-body equality. Parameter classification and content policy still require body inspection. For older revisions, mirrored-header guarantees may be absent, so body parsing or route rejection is required.

Can one endpoint support current and older clients?

Compatibility is possible, but each revision needs an explicit behavior path. The 2026-07-28 revision removed the GET stream and protocol sessions, both of which older clients may expect. Separate routes reduce ambiguity and let the current route reject downgrade attempts. Give any compatibility route an owner and telemetry. Set a retirement date as well.

Is `Mcp-Session-Id` an authentication control?

The current revision removed protocol-level sessions. Older guidance also warned against treating session identifiers as authentication. Validate authorization on every HTTP request and bind any application state handle to verified user context. Possession of an opaque handle should never replace token validation.

Which failures should block deployment?

Block deployment if a tool executes after a mismatch or failed authorization. Also block if a wrong-audience token reaches the server, if the proxy makes policy decisions from unverified legacy headers, or if buffering breaks progress and cancellation. Block when direct production access can bypass the governed route.