MCP transport reference

Securing MCP Streamable HTTP transport

The transport-level checks a Streamable HTTP MCP server needs: Origin validation, local binding, session handling, and what changed when SSE became a response mode instead of a transport.

Updated July 2026Implementation guidemcp streamable http
Built for

Engineers moving an MCP server from stdio to a network transport.

Decision supported

What must be enforced at the HTTP layer before authorization and policy are even reached.

The control gap

Streamable HTTP replaced the older HTTP with SSE transport, and server-sent events are now one possible response shape on a single endpoint rather than a separate channel. The migration is usually smooth and the security assumptions are not. A local MCP server bound to all interfaces and reachable from a browser page inherits DNS rebinding as an attack path, and a long-lived stream keeps executing under a session established before a policy or consent changed.

What good looks like

The transport rejects requests it should never have accepted, sessions are identifiable and revocable, and no stream outlives the authority that started it.

  • Validate the Origin header on every request. A browser can reach a loopback server; a check on Origin is what stops a hostile page from driving it.
  • Bind local servers to 127.0.0.1 rather than 0.0.0.0, so the endpoint is not exposed to the rest of the network by default.
  • Return a cryptographically random session identifier and treat it as a correlation handle, never as proof of identity.
  • Re-evaluate authorization per tool call rather than per connection, so a revoked consent stops the next call rather than the next reconnect.

A production workflow

  1. The client posts an initialize request; the server checks Origin, authenticates the bearer token, and returns a session identifier.
  2. Subsequent calls carry both the token and the session header, and the server verifies the token again rather than trusting the session.
  3. Responses stream as server-sent events when the server chooses to; the same endpoint answers ordinary JSON otherwise.
  4. The client ends the session with a DELETE, and the server invalidates the identifier so a replayed header resolves to nothing.

Copy this

These are the checks that belong before your policy engine sees anything. Each one has a cheap failure mode and an expensive one.

POST /mcp/<gateway-slug>
Origin: https://claude.ai                 # 1. validate, or reject with 403
Authorization: Bearer <token>             # 2. verify signature, expiry, audience
Accept: application/json, text/event-stream
Mcp-Session-Id: <opaque, high entropy>    # 3. correlation only, never authority
Content-Type: application/json

HTTP/1.1 200 OK
Mcp-Session-Id: <returned on initialize>
Content-Type: text/event-stream           # or application/json for a single result

# Ending a session
DELETE /mcp/<gateway-slug>
Mcp-Session-Id: <id>
-> 204, and the identifier no longer resolves

# Local development
bind 127.0.0.1, not 0.0.0.0
reject Origin values that are not on your allowlist

The Accept header is worth reading closely. A Streamable HTTP client advertises both content types because the server decides per response whether to stream, and a proxy that strips text/event-stream turns long tool calls into silent timeouts.

Evidence to require

  • Rejected requests by reason: bad Origin, missing token, wrong audience, unknown session.
  • Session lifetime, the identity that opened it, and the call that closed it.
  • Whether each tool call was authorized independently or inherited a session-level decision.
  • Streams terminated because the underlying consent or policy changed mid-session.

Buyer checklist

  • Is the Origin header validated, and is the allowlist maintained anywhere other than a comment?
  • Does the server bind to loopback in local mode?
  • Is the session identifier random enough that guessing it is not a viable attack?
  • Does revoking a consent stop calls on an already open session, or only new connections?

Practical answers

Common implementation questions

Is SSE deprecated in MCP?

The separate HTTP with SSE transport was superseded by Streamable HTTP. Server-sent events remain, but as a response mode on the single MCP endpoint rather than as a second transport with its own URL.

Do we still need transport checks if the server requires OAuth?

Yes. Origin validation and loopback binding defend against a browser being used as a confused deputy against a local endpoint, which is a different problem from an unauthenticated caller.

Should the session identifier be used for authorization?

No. Treat it as a correlation handle. Every request should carry its own credential, and every tool call should be authorized on its own merits.

Continue the evaluation

Related controls