Part 6 of 7 · 5 min read
Exercises
Easy
Easy 1: Run and read (20 minutes)
In code/mini-transport, run pytest and python demo.py. In section 1 of the demo, list every HTTP header on the request and say which body field each one mirrors.
Done when: you name MCP-Protocol-Version, Mcp-Method and Mcp-Name with their source fields, say which headers are ordinary HTTP (Content-Type, Accept, Content-Length), and explain in one sentence why the stdio request needs none of them.
Hint
the headers that start with MCP or Mcp are the mirrored ones; everything else is HTTP.
Easy 2: Is this line a message? (20 minutes)
A stdio server writes these to stdout. For each, say whether the client should accept it as a message, and what the author should change if not. (1) {"jsonrpc":"2.0","id":3,"result":{}} (2) INFO starting up (3) a JSON object printed across three lines with indentation (4) {"jsonrpc":"2.0","method":"notifications/progress","params":{}} (5) an empty line (6) {"id":4,"result":{}}
Done when: you have six answers; you note that (3) is broken framing even though the JSON is valid, that (5) should be ignored, and that (6) is not valid JSON-RPC because it lacks the version field.
Hint
the frame on stdio is the line, so a message that spans lines cannot be read.
Easy 3: Pick the status (20 minutes)
Give the HTTP status (and error code if any) a current-revision server should return: a notification it accepts; a request from a page on a foreign origin; a request whose Mcp-Name header differs from the body; a request for a method it does not have; a request for a protocol version it does not support; a GET to the endpoint; an old client sending Mcp-Session-Id.
Done when: your answers match the table in section 3 (202; 403; 400 with -32020; 404 with -32601; 400 with the unsupported-version error; 405; ignore the header and answer normally), and you say which two the specification text left to you.
Hint
use the "Status codes" table and check which statuses the page names.
Medium
Medium 1: Headers for other types (60 minutes)
Extend the example so that x-mcp-header works on integer and boolean parameters, with 42.0 and 42 treated as equal on the server. Add tests, including a Base64 case.
Done when: a tool with an integer and a boolean annotated parameter round-trips, the server rejects a mismatched integer, and a value such as 007 is compared numerically.
Hint
compare integers after converting both sides to numbers; compare strings and booleans as text.
Medium 2: A retry policy that knows about side effects (90 minutes)
Add a client method that retries a broken call only when the tool is marked read-only or the arguments contain an idempotency key; otherwise it must not retry and must return a message for the model saying that the outcome is uncertain and what to check.
Done when: tests show a read-only retry succeeding, a keyed write producing exactly one hold, an unkeyed write producing a refusal text with no second attempt, and the refusal naming a tool to check.
Hint
the model-facing refusal should look like module 1's: say what happened, what is uncertain, and the next step.
Medium 3: A listener that survives reconnects (90 minutes)
Build a small Watcher that keeps a subscriptions/listen stream open over HTTP, reconnects with a delay when it breaks, re-reads the resources it cares about after every reconnect, and counts how many changes it caught by notification versus by re-reading.
Done when: a test that breaks the stream and changes a resource during the gap shows the watcher ending with the new value, with the change counted as "found by re-reading".
Hint
keep the last value you saw per resource and compare after the re-read.
Hard
Hard 1: The same server over a real SDK (150 minutes)
Build the library server with an official SDK in a language you use, expose it over both transports, and connect an inspector program or desktop assistant to it. Capture the first request on each. (I did not run this, so your results are the ground truth.)
Done when: you give a table of differences between your capture and this example's messages, say which revision the SDK speaks, and report whether it sends the mirrored headers.
Hint
look at the first message sent by the client; the era shows there.
Hard 2: Put a real reverse proxy in front (150 minutes)
Run the example HTTP server behind nginx, Caddy or another proxy. Test a long-running streamed call with and without X-Accel-Buffering: no, and a subscriptions/listen stream with and without the keep-alive. (I did not run this.)
Done when: you report when the first progress event reaches the client in each case, at what idle time the proxy closes a quiet stream, and what setting fixed each problem.
Hint
print timestamps with each event on the client side.
Hard 3: Show DNS rebinding on your own machine (150 minutes)
On a machine and network you control, with a server you wrote, demonstrate in a real browser that a page from another origin can reach a local MCP endpoint without an origin check, and cannot with one. Do not point this at anyone else's service. (I did not run this.)
Done when: you record what the browser sent (the Origin and Host headers) in both cases, and explain why a request from a script on the same machine still succeeds with the check on.
Hint
browsers send Origin on cross-origin POSTs; see what changes when the page's name resolves to 127.0.0.1.