« Phase 02 · Warmup · Track Overview
Hitchhiker's Guide — MCP & the Tool Estate
The 30-second mental model
MCP turns M×N integrations into M+N: one protocol between an agent host and a tool provider. JSON-RPC 2.0, a capability-negotiating handshake, three primitives.
Then it stops. No authorization, no tenancy, no versioning, no tool identity, no idempotency, no audit. Those are the platform's, and they are the actual job. "We use MCP" is a transport decision, not a tool strategy.
The numbers and codes
| Thing | Value |
|---|---|
| Parse error | −32700 |
| Invalid request | −32600 |
| Method not found (incl. unentitled tool) | −32601 |
| Invalid params (incl. schema violation) | −32602 |
| Internal error | −32603 |
| Server-defined range | −32000 … −32099 |
| Protocol version format | a date, e.g. 2025-06-18 |
| Clients per server | exactly 1 |
| 60 tools × 200-token descriptions | 12 000 tokens before the user speaks |
The three primitives, by who controls them
| Controlled by | Risk | |
|---|---|---|
| Tool | the model | highest — needs schema, scope, side-effect class |
| Resource | the application | the choice is trusted; the content never is |
| Prompt | the user | lowest |
The who-breaks rule
Callers break when the contract gets stricter.
| MAJOR | MINOR |
|---|---|
| add a required property | add an optional property |
| remove a property | remove a required property |
| change a type | — |
| narrow an enum | widen an enum |
| tighten a bound | loosen a bound |
close additionalProperties | — |
One-liners
- Notification — no
id, never answered. Half of all JSON-RPC bugs live here. - Lenient negotiation — an unknown protocol version offers a fallback; it never fails the handshake. A skew must degrade one connection, not the host.
- Filter before you list —
tools/listis a query with a principal, not a catalogue. - Unentitled == nonexistent — same error, same shape, or your estate is enumerable by diffing messages.
- Protocol error vs tool error — a schema violation is
-32602and never reaches the tool; a downstream 503 is a successful result withisError: true, so the model can adapt. - Repair syntax, never semantics — coerce
"42"→42; never invent an account number. - Immutable versions — a republish makes every pin and every reproducibility claim false.
- Deprecated ≠ retired — deprecated stays resolvable by pin. That is what a migration window is.
listChangedor the window is theatre — clients cache; without the notification they call retired tools.- Side-effect class is required, with no default — a default of
readmakes a payment tool retryable.
Vocabulary
Host / client / server · the app, one connection object per server, the tool provider.
Capability negotiation · both sides declare what they support at initialize. Sampling ·
server asks the client's model for a completion. Elicitation · server asks the user for
input. inputSchema · the tool's JSON Schema contract. Prompt surface · text the model
reads every turn — descriptions are this. Pin · a caller's version constraint (^1.2.0).
Deprecation window · the period a pinned old version keeps working.
War stories
The Tuesday that broke four teams. payments.lookup gained a required as_of argument,
released as 1.1.0 "because it's just one field." Every caller that omitted it started failing
within the hour. Three controls would each have stopped it: classification at publish, pins, and
listChanged.
The enumerable estate. Unentitled tools returned 403 forbidden; nonexistent ones returned
404. A red-team exercise mapped the entire tool catalogue — names, and therefore capabilities —
by diffing responses, without ever calling anything successfully.
The 12 000-token preamble. Every agent got every tool. Cost per turn was dominated by tool schemas, and selection accuracy was poor because the model had sixty choices. Filtering discovery by scope and task cut context 90% and raised task success more than a model upgrade had.
The retryable payment. side_effect defaulted to read. A payments tool was registered
without setting it. The gateway's retry-on-timeout logic did exactly what it was told.
The helpful server. A third-party MCP server's tool description contained instructions addressed to the model. It was, by construction, a prompt injection on every turn — and it was "just documentation," so nobody reviewed it.
Beginner mistakes
- Replying to a notification.
tools/listas a static list.- Filtering in the client after the model has seen the list.
- Different errors for unentitled vs nonexistent.
- Platform metadata in the description.
- Breaking change as a minor bump.
- Mutating a published version.
- No
listChanged. isinstance(v, int)acceptingTruefor an amount.- Repairing semantics — inventing values.
- Failing the handshake on an unknown version.
- Assuming "application-controlled" resources are safe content.
What "good" sounds like
"MCP is the transport. The estate is the work: immutable semver'd tools in a registry that classifies every schema change and refuses an under-bumped breaking one, discovery filtered by scope, tenant and classification before the list is built, an unentitled tool returning the same error as a nonexistent one so the catalogue isn't probeable, and every tool carrying a required side-effect class the platform derives retry policy from. Schema violations are protocol errors that never reach the tool and get one deterministic repair pass first; downstream failures come back as results with isError so the model can adapt. And I'd want workload identity on the servers themselves, because nothing in the protocol tells me this server is really the payments team's."