Loading
0x40Lesson 5 of 15

Move messages: stdio and Streamable HTTP

See how messages travel to local and remote servers, and why HTTP headers must match the body.

20 min 6-question quiz 2 code exercises
By the end of this lesson you can
  • Describe the stdio transport’s framing and rules
  • List the headers every Streamable HTTP request carries
  • Explain why servers reject header/body mismatches

stdio: a local subprocess

With stdio, the client launches the server as a subprocess and they talk over its standard streams:

  • the client writes requests to the server’s stdin, one JSON message per line,
  • the server writes responses to stdout, one per line - messages must not contain embedded newlines,
  • stdout is only for MCP messages; logs go to stderr,
  • the client shuts the server down by closing its stdin.

Streamable HTTP: a remote server

A remote server exposes one MCP endpoint, like https://example.com/mcp. Every message is its own HTTP POST. The server answers with either plain JSON or a Server-Sent Events stream (for progress notifications before the final result).

Each POST also mirrors key body fields into headers, so load balancers, gateways and firewalls can route and rate-limit without parsing JSON:

HeaderCopied from
MCP-Protocol-Version_meta protocol version
Mcp-Methodmethod
Mcp-Nameparams.name or params.uri (for tools/call, resources/read, prompts/get)
a Streamable HTTP request
1POST /mcp HTTP/1.1
2Content-Type: application/json
3Accept: application/json, text/event-stream
4MCP-Protocol-Version: 2026-07-28
5Mcp-Method: tools/call
6Mcp-Name: get_weather
7
8{
9  "jsonrpc": "2.0",
10  "id": 1,
11  "method": "tools/call",
12  "params": {
13    "name": "get_weather",
14    "arguments": {
15      "location": "Seattle, WA"
16    },
17    "_meta": {
18      "io.modelcontextprotocol/protocolVersion": "2026-07-28",
19      "io.modelcontextprotocol/clientInfo": {
20        "name": "ExampleClient",
21        "version": "1.0.0"
22      },
23      "io.modelcontextprotocol/clientCapabilities": {}
24    }
25  }
26}

If a header and the body disagree - the gateway routes on Mcp-Name: get_weather but the body calls delete_account - an attacker could slip past policies. So servers must reject mismatches with 400 Bad Request and error -32020 (HeaderMismatch). Values that aren’t plain ASCII are sent Base64-encoded as =?base64?…?=.

Try it

Accept or reject?

Each line summarizes an HTTP request to an MCP server. Should the server accept it, or reject it?

0 of 5 sortedScore 0/0
  • “Mcp-Method: tools/call, body method tools/call, Mcp-Name matches the tool name”

  • “Mcp-Name: get_weather, body calls tool delete_files”

  • “MCP-Protocol-Version: 2026-07-28, body _meta says 2025-11-25”

  • “tools/call with no Mcp-Name header”

  • “Origin: https://evil.example on a server only meant for a local app”

Key takeaways

  • stdio: newline-delimited JSON over stdin/stdout; stdout is for messages only, logs go to stderr.

  • Streamable HTTP: one POST endpoint; headers mirror version, method and name for routing.

  • Header/body mismatches are rejected (-32020); local servers bind to localhost and check Origin.

Lesson quiz

6 questions · pass with 5 correct · up to 50 XP

Passing this quiz completes the lesson and keeps your streak going. Questions you miss come back in review sessions later.

Practice: write Python

Write Python in the editor and run it against sample inputs. Python runs locally in your browser using a WebAssembly runtime.

Exercise 1

Validate request headers

+25 XP

The first line is a JSON object of HTTP headers (header names may use any letter case); the second line is the JSON-RPC body. Check, in this order:

  1. MCP-Protocol-Version equals the body’s params._meta["io.modelcontextprotocol/protocolVersion"]
  2. Mcp-Method equals the body’s method
  3. for tools/call and prompts/get, Mcp-Name equals params.name; for resources/read, it equals params.uri

Print ok, or -32020 HeaderMismatch: NAME for the first failing header (NAME as written above). A missing header counts as a mismatch.

  • All match
  • Smuggled tool
  • Version mismatch
main.py
Loading editor…

Python runs in a sandboxed browser worker with a 60 second time limit. Its runtime loads from the Pyodide CDN; your code stays in this browser.

Exercise 2

Encode a header value

+25 XP

Read one value (the rest of the line, exactly as given - it may have leading or trailing spaces). If every character is printable ASCII (codes 32–126), there is no leading or trailing whitespace, and it doesn’t already look like =?base64?…?=, print it unchanged. Otherwise print =?base64? + the Base64 of its UTF-8 bytes + ?=.

  • Plain ASCII
  • Non-ASCII
  • Padded
  • Looks encoded
main.py
Loading editor…

Python runs in a sandboxed browser worker with a 60 second time limit. Its runtime loads from the Pyodide CDN; your code stays in this browser.

Questions about this lesson

Stuck? Ask. Figured something out? Share it. Explaining is one of the best ways to learn.

Loading posts…

Did you like the lesson? 😆👍
Consider a donation to support our work: