Course · Term 7 of 30
MCP Servers
Every lesson in this course builds one system: a stablecoin reserve attestation registry that discovers issuers, reads the PDFs they publish, and refuses to publish a field it could not read. What it is and where this lesson sits.
The Model Context Protocol is JSON-RPC 2.0 spoken to a child process over
standard input and output. Three methods carry almost all of it: initialize
agrees a protocol version, tools/list returns what the server offers,
tools/call runs one. A reader who has written a command-line tool has written
most of this before under a different name.
The protocol is not why the term is load-bearing. This is: a tool description is a string somebody else wrote, and it arrives in the model's context as instructions. Adding a third-party server is accepting text you did not write into the place where your instructions live. That is a trust boundary, it opens the moment the server is registered, and nothing about the protocol announces it.
The mechanism
src/mcp.ts is a client and a ledger. The client buffers stdout until a
newline, parses one JSON object per line, and matches replies to callers by id.
The framing is the part people get wrong: stdout is a stream, so a reply can
arrive split across two chunks and two replies can arrive in one.
The ledger records every server with its origin, its author, what it can reach, and — for anything third-party — a pin. An unpinned third-party server is refused rather than warned about, because a warning is a thing you scroll past:
unpinned third-party server -> refusing to register third-party server
"vendor-search" with no pin
scripts/fixtures/attestation-server.mjs is the server, first-party and local,
answering over the same thirty-issuer seed the rest of the registry uses. It is
first-party on purpose. Pointing detection work at third-party servers in the
wild would be testing somebody else's system without asking.
The receipt
artifacts/07-mcp.json:
initialize protocol 2025-06-18, server "attestation-index"
tools/list 1 tool(s)
attestation_index 66 tokens of description, 4e881123ace7e82f
tools/call USDC -> 74,000,000,000 circulating
tools/call USDCC -> isError, unknown ticker: USDCC
descriptions entering context: 66 tokens across 1 tool(s)
description drift: 0 against the real pin, 1 against a planted one
Two numbers there are worth naming. Sixty-six tokens of description enter the context for one tool, priced by term 3's estimator, and they are instruction- shaped text charged on every turn the tool is offered. And the unknown ticker returns an error rather than the nearest match, which is the same posture the registry takes about unread fields: a near miss carrying a plausible shape is worse than an absence.
Forward pointer: term 26
Tool poisoning is taught at tier 5 and the exposure starts here, at tier 2, nineteen terms earlier. A curriculum that waits until term 26 to mention it has let the reader take the risk and then explained it afterwards.
The minimum defensive practice, stated here so a reader who stops after this unit still has it:
- Pin the server to a version or a digest. Unpinned third-party code can change its tool descriptions between one run and the next.
- Hash the descriptions and store the hashes.
describeHashanddriftedDescriptionsdo this in twenty lines. - Read the descriptions before a model does. They are instructions.
- Treat a description that changed under its pin as an incident, not an update.
The demo plants a mismatched pin and checks the drift check fires, because a detector nobody has watched fail is a detector nobody has tested.
Break it first
mutation tests demo
unpinned third-party accepted 1 failed | 13 passed exit 0
protocol version never checked 1 failed | 13 passed exit 0
description drift never reported 1 failed | 13 passed exit 1
replies handed to the wrong caller 15 passed exit 0
The last row failed to fail, and fixing that is the most useful thing in this unit. The concurrency test was passing against a fixture that replies in request order, and against such a server "hand the reply to the oldest waiting caller" and "hand it to the caller whose id it carries" are the same instruction. The test could not tell correct id-matching from luck.
scripts/fixtures/reordering-server.mjs collects a batch and flushes it
backwards. Against that server the two come apart, and the mutation goes from
15 passed to 1 failed | 14 passed. The client did not change; the fixture
did.
Where it overlaps
Line E is the tool surface and its trust boundary, and this is the rung where the surface stops being code we wrote. Term 5 established that a credential's scope is its blast radius; a server's scope is the same question asked about capability instead of secrets. Line A is context economics: tool descriptions are resident text, the same permanent line on the bill term 4 measured for standing instructions. Line B is instruction packaging, and this is the uncomfortable rung, where instructions arrive from outside.
A personal teaching project, in development. The registry it builds is a teaching artifact and is not an assurance opinion about any issuer. Not affiliated with or endorsed by any employer, or by any vendor whose tools it describes. Where a unit depends on a specific flag or path, it names the version it was verified against. All units.