stdio and Streamable HTTP

A transport is a binding: how messages are framed, how metadata travels, how cancellation is signalled. The messages themselves are identical on both.

By the end of this lesson you can

  • list the stdio rules: framing, the streams, cancellation, shutdown, credentials;
  • list the Streamable HTTP rules: one endpoint, POST only, JSON or SSE replies, cancellation, 405;
  • name the four Mcp-* headers and what each mirrors;
  • explain why a header that disagrees with the body is rejected with -32020, and why servers check Origin.

Two bindings, one protocol

Framing, replies, cancellation

stdio Host Process stdin: one line stdout: one line stderr: logs notifications/cancelled {…}\n {…}\n log Streamable HTTP Client POST /mcp POST + Mcp-* headers JSON, or an SSE stream ✕cancel: close the stream GET or DELETE → 405 POST GET
  1. stdio: the client launches the server as a subprocess and writes one JSON-RPC message per line on its stdin. No embedded newlines.
  2. Responses come back one per line on stdout, which carries only protocol.
  3. Logs go to stderr, never stdout.
  4. To cancel a request on stdio, the client sends a notifications/cancelled notification.
  5. Streamable HTTP: one endpoint, POST only. Each message is its own request, with headers that mirror the body.
  6. The reply is one JSON object, or an SSE stream scoped to that one request (for progress, then the result).
  7. To cancel on HTTP, the client closes the response stream.
  8. GET and DELETE are answered 405 Method Not Allowed. There is no session to open a stream for or to delete; session ids are ignored.

stdio

  • The client launches the server as a subprocess.
  • One JSON-RPC message per line, no embedded newlines.
  • stdout carries only protocol; stderr is for logs.
  • Cancel with notifications/cancelled.
  • Shutdown: close stdin, wait, then SIGTERM and SIGKILL.
  • No OAuth: credentials come from the environment.
  • If it crashes, restart the process and retry: nothing was kept.

Streamable HTTP

  • One endpoint, POST only. Each message is its own request.
  • The reply is one JSON object or an SSE stream scoped to that request.
  • Cancel by closing the response stream.
  • Headers mirror the body so gateways can route without parsing it.
  • Validate Origin against DNS rebinding; bind to localhost when local.
  • GET and DELETE answer 405; session ids are ignored.

Headers that mirror the body

A gateway or load balancer wants to route, rate-limit and log by method and tool without parsing JSON. So the 2026-07-28 revision copies those values into headers.

HeaderMirrorsRequired on
MCP-Protocol-Version_meta protocol versionEvery POST
Mcp-MethodmethodEvery request
Mcp-Nameparams.name or params.uritools/call, resources/read, prompts/get
Mcp-Param-{Name}A tool argument marked x-mcp-headerWhen the tool declares it

Spec rule

If a header disagrees with the body, the server answers 400 with error -32020, HeaderMismatch. The rule exists because a load balancer might route on the header while the server acts on the body: a request whose header says read_docs but whose body says delete_repo would slip past a gateway policy.

Key idea

A local HTTP server is reachable from any web page you visit, through DNS rebinding. That is why servers must validate Origin and should bind to 127.0.0.1 when they only serve the local machine.

Send a request to a modern server

The body is fixed: tools/call for the tool named in the box, with _meta version 2026-07-28. Change the method and headers and see what the server says.

Request line and headers

Body


        

Server answers

Press Send.

Quick check

How does a client cancel an in-flight request on Streamable HTTP?

  • It sends notifications/cancelled in a new POST
  • It closes the response stream for that request
  • It sends DELETE /mcp
On HTTP the request and its stream are one unit, so closing the stream is the cancellation. notifications/cancelled is the stdio way; DELETE gets 405.

The body says "name": "delete_repo" but the Mcp-Name header says read_docs. What happens?

  • The server trusts the body and deletes the repo
  • The server trusts the header and reads the docs
  • 400 with error -32020 HeaderMismatch
Headers exist so intermediaries can act without parsing the body; a mismatch would let a request say one thing to the gateway and another to the server, so it is rejected.

Why do servers validate the Origin header?

  • To route requests to the right replica
  • So a web page cannot reach a local server through DNS rebinding
  • Because OAuth requires it
A malicious page can make its domain resolve to 127.0.0.1 and talk to servers on your machine. Checking Origin (and binding to localhost) stops it.

Recap