« Phase 02 · Warmup · Track Overview
Core Contributor Notes — How the Real Thing Works
The MCP specification and its SDKs, the transports, the authorization annex, and what our miniature simplifies.
Table of Contents
- 1. The spec is dated, and that is the design
- 2. The two transports
- 3. The authorization annex, and what it actually covers
- 4. Things the SDKs do that the lab does not
- 5. Schema validation in production
- 6. Registries in the wild
- 7. Sharp edges
- 8. What the miniature simplifies
- 9. References
1. The spec is dated, and that is the design
MCP versions are dates, not semver — 2024-11-05, 2025-03-26, 2025-06-18. That choice
tells you the maintainers expect the protocol to move and expect implementations to disagree about
which revision they speak.
The mechanism that makes disagreement survivable is the one the lab implements: the client proposes a version, the server responds with one it supports. If they cannot agree, the client disconnects that server — not the host. A host talking to twelve servers may legitimately speak three different revisions at once, one per connection, which is exactly why the one-client-per- server rule exists.
The practical consequence for a platform team: pin nothing, negotiate everything, and log the negotiated version per connection. When a vendor's server upgrades and something changes, the first diagnostic question is "which revision were we speaking?", and if you did not record it you are guessing.
2. The two transports
stdio. The server is a subprocess; messages are newline-delimited JSON on stdin/stdout. Stderr is free for logging — and writing anything non-protocol to stdout corrupts the stream, which is the single most common stdio bug. Authentication is implicit: you spawned the process, so it runs with your credentials and your environment. That implicitness is convenient locally and completely unacceptable for a bank's shared platform, because "the tool inherits the kernel's identity" is the opposite of what Phase 08 asks for.
Streamable HTTP. A single endpoint handling POST (client→server) and GET (an SSE stream for
server→client messages), with an optional Mcp-Session-Id header for session continuity and
resumability via SSE event ids. This replaced the earlier HTTP+SSE two-endpoint transport, and the
migration is a good illustration of why negotiation matters: both shapes existed in the wild
simultaneously.
Two details that bite:
- Resumability is per-session, not per-request. If your infrastructure load-balances the GET stream away from the POST handler, session state must be shared — which is a Phase 01 problem (externalized state) wearing a different hat.
- Long-lived SSE connections interact badly with API gateways. Idle timeouts, buffering proxies, and connection limits all show up here. Expect to configure APIM/Envoy explicitly.
3. The authorization annex, and what it actually covers
MCP's HTTP transport defines an authorization framework built on OAuth 2.1 and the standard resource-server pattern:
- The MCP server is an OAuth resource server. It publishes protected-resource metadata (RFC 9728) naming its authorization servers.
- Clients discover the authorization server, obtain a token, and present it as a bearer token.
- Servers must validate the token's audience and must not accept tokens minted for someone else — the confused-deputy defence, and the reason RFC 8707 resource indicators appear.
- A
401carriesWWW-Authenticatepointing at the metadata, so discovery is automatic.
What this covers: is this caller allowed to talk to this server at all?
What it does not cover, and what the lab exists to build: which of this server's tools may this particular agent, acting for this user, in this tenant, see and call right now? The spec has no notion of an agent registry, a scope-per-tool, a data classification, or tenant visibility — and it should not. That is a bank's control model, and encoding it in a wire protocol would make the protocol un-adoptable.
The design consequence: you need both. Transport-level authorization (the annex) authenticates the connection; catalogue-level authorization (the registry) decides the view. Teams that implement only the first believe they have an authorization model and have an authentication model.
4. Things the SDKs do that the lab does not
| Feature | What it is | Why it matters |
|---|---|---|
| Pagination | cursor / nextCursor on the */list methods | a 400-tool server cannot return one page; and a client cache must be page-aware or it will hold a partial list |
| Cancellation | notifications/cancelled with a request id | a long tool call must be abortable when the user leaves; without it, budget burns after nobody is listening |
| Progress | notifications/progress with a token | the difference between a 90-second tool that looks hung and one that reports |
| Logging | notifications/message at RFC 5424 levels | server logs surfaced to the host, which is how you debug a server you cannot attach to |
| Completion | completion/complete for argument autocompletion | mostly a human-UX feature; also a discovery surface worth thinking about |
| Roots | client-declared filesystem/URI boundaries | the client telling the server what it is allowed to look at — the closest thing in the spec to a sandbox |
| Subscriptions | resources/subscribe + notifications/resources/updated | live context without polling |
| Structured content | typed structuredContent alongside text, with an outputSchema | validated tool output, not just input — worth adopting, because output validation is where "improper output handling" is caught |
outputSchema deserves special note for a bank: our lab validates inputs only, which is the
common case. Validating outputs catches a different failure — a downstream system returning
something the agent will misread — and it gives the action gateway a contract to check on the way
back.
5. Schema validation in production
Nobody hand-writes a validator. Three approaches, with different failure modes:
jsonschema (Python), Ajv (JS) — full dialect support including $ref, oneOf, allOf,
format. Slower than a targeted validator, and $ref resolution can reach the network if you are
careless ($ref to an http:// URI is a real SSRF vector — disable remote refs).
Pydantic / dataclass-derived schemas — you define the model, the schema is generated, and validation and parsing are the same step. The nicest developer experience and the one that most easily drifts from the published schema if the two are generated at different times. Pin the generation to the publish step.
Provider-side structured outputs / constrained decoding — the model is constrained to emit
valid JSON for the schema. This attacks the problem one layer earlier and raises per-step p
substantially. It does not remove the need for server-side validation: the model may be
constrained, but the request arriving at your server may not have come from that model.
The rule that survives all three: validate at the boundary you control, regardless of what the caller promises.
6. Registries in the wild
There is an official MCP Registry for publishing and discovering community servers, and several vendor catalogues. For an enterprise, the useful pattern is a private registry that mirrors and pins: public servers are vetted, their schemas and descriptions snapshotted, and agents resolve against your copy — never against a URL that can change under you.
The closest mature analogues to what the lab builds are not in the MCP ecosystem at all:
- Confluent Schema Registry — compatibility modes (
BACKWARD,FORWARD,FULL, and their_TRANSITIVEvariants) are exactly the who-breaks rule, and its vocabulary is worth borrowing wholesale.BACKWARD(new schema can read old data) is what a tool caller needs. - Azure API Management — versions and revisions (a revision is non-breaking, a version is breaking), products and subscriptions as the authorization view, and policies as the enforcement point. If your bank already runs APIM, your tool estate probably lives behind it.
- Package registries — npm/PyPI immutability and yanking. "You cannot republish a version" is a lesson those ecosystems learned expensively.
7. Sharp edges
stdout is the protocol. A stray print() in a stdio server corrupts the stream. Every SDK
warns about it; it happens anyway.
Tool-name collisions across servers. The host aggregates; two servers export search. The
host must namespace, and the namespacing appears in prompts and audit records, so it must be
stable and chosen deliberately (payments.search, not server3_search).
Descriptions drift from behaviour. The schema is checked; the description is not. A tool whose description says it returns real-time data and whose implementation reads a nightly extract will mislead the model indefinitely, and nothing will error.
$ref and remote schemas. Disable remote reference resolution. A schema that fetches a $ref
over the network is an SSRF and a supply-chain dependency in one.
Idle SSE connections. Gateways, load balancers and proxies will close them. Configure timeouts explicitly and implement reconnection with the resumability header, or long tool calls will fail in ways that look random.
Session id as a security boundary. Mcp-Session-Id identifies a session; it does not
authenticate it. Treat it as a correlation id, not a credential.
8. What the miniature simplifies
| Miniature | Reality |
|---|---|
| Direct method call | stdio framing or streamable HTTP with SSE, sessions and resumability |
| No pagination | cursors on every list method |
| No cancellation or progress | notifications/cancelled, notifications/progress |
No outputSchema | typed structured content, validated both ways |
| No sampling/elicitation/roots | implemented, opt-in, and security-relevant |
| One principal per server | per-request identity from a validated bearer token |
| In-memory registry | a service with storage, an API, approvals and an impact-analysis query |
| Schema subset | full dialect, with $ref, oneOf, formats — and the SSRF caveat |
| No server identity | mTLS/workload identity, and a registry that binds names to servers |
| Discovery computed per call | a precomputed, cached view invalidated by registry events |
Everything the real stack adds is plumbing, with one exception that is not: authorization is still yours to build. The spec's annex authenticates the connection. The catalogue view — which agent sees which tool — has no standard, and will not, because it is where each organization's control model lives.
9. References
- MCP specification — modelcontextprotocol.io: protocol revisions, transports (stdio, streamable HTTP), authorization, and the client/server feature lists (sampling, elicitation, roots, completion, logging, progress, cancellation, pagination).
- MCP SDKs — the Python and TypeScript reference implementations; read the transport modules, which is where all the sharp edges are.
- JSON-RPC 2.0 — jsonrpc.org/specification.
- OAuth 2.1, RFC 9728 (protected resource metadata), RFC 8707 (resource indicators), RFC 7636 (PKCE) — the annex's building blocks, covered properly in Phase 08.
- Confluent Schema Registry compatibility types — the best-documented statement of the who-breaks rule.
- Azure API Management — versions vs revisions, products, subscriptions, policies.
- OWASP Top 10 for LLM Applications — Supply Chain and Excessive Agency.