ONE QUESTION · 7 JSON-RPC MESSAGES · 94 TOOLS DEFINED · 1 CALLED
MCP is JSON-RPC 2.0 on a pipe. This is one nine-word question riding it end to end: the subprocess your editor spawns, the three messages that make the connection legal, the 94 tool definitions that fill a fifth of the context window before you type, and the single tool that runs. Two counters follow it the whole way — tokens in the window, messages on the pipe. are tappable. You are the question.
The config line that connects an AI client to an MCP server names a command, not a URL. At 09:41:02 on Tuesday 14 July your editor runs that command, gets a child process and two pipes, and has not yet sent a single byte. Two counters run through this tutorial — tokens in the model’s context window, and JSON-RPC messages on the pipe. Both read zero for this whole chapter.
You type nine words into a client with one server configured. Before anything reaches a model, the client reads a JSON config file, and the entry it finds there is a shell command rather than a URL: npx -y @modelcontextprotocol/server-github, plus its arguments and environment. The client runs it and gets a child process back. No port, no socket, no DNS lookup — the standard way to connect to an MCP server is to launch it on your own laptop.
MCP’s wire format is , a specification dated 2010 — older than Docker, older than Kubernetes, older than the transformer paper. It defines exactly three message shapes. A request carries an id and expects an answer. A response carries the same id. A carries no id, receives no reply, and has no way to report failure. Everything MCP does is one of those three, and there is no fourth.
On the a message is one line of UTF-8 JSON on standard output, terminated by a newline. There is no length header — the Language Server Protocol, which MCP is modelled on, prefixes every message with Content-Length; MCP dropped it. Messages must not contain embedded newlines. Standard output belongs to the protocol and to nothing else, which is why a stray console.log in a server breaks the connection outright, and why stderr exists as the logging channel.
MCP names three roles. The is the application you are looking at. It runs one per connection, and each client talks to exactly one . That is the whole cast, and the model is not in it. An MCP server never receives a token, never sees a prompt, and cannot tell which model is on the other end, because the specification does not mention models at all. Each server translates one system into the same JSON-RPC methods — the position a device driver occupies in an operating system.
The design is not new. ODBC standardised one interface for many databases in 1992; the Language Server Protocol did the same for editors and languages in 2016, and MCP’s design documents name LSP as the model it followed. Anthropic published MCP on Monday 25 November 2024, with the specification revision dated 2024-11-05, two SDKs, and reference servers for Git, GitHub, Slack, Postgres, and Puppeteer. OpenAI adopted it in March 2025 and Google DeepMind in April. The economics of that collapse — many clients times many tools becoming many plus many — belong to the agent-patterns tutorial.
The feature that lasted 84 days. Revision 2025-03-26 added JSON-RPC batching, letting a client send an array of messages in one payload. Revision 2025-06-18 removed it again: 84 days of support, for a saving nobody was measuring. SDKs that shipped in between still carry the parsing code.
Stricter than JSON-RPC itself. Base JSON-RPC allows a null request id. MCP forbids it, and additionally requires that an id is never reused within a session — both so that a response can always be matched to exactly one request without bookkeeping the transport does not provide.
The _meta field. Every request, response, and notification may carry a _meta object, namespaced by reverse-DNS keys. It began as an extension point for implementations. Revision 2026-07-28 turned it into load-bearing structure: the protocol version and client capabilities now travel inside it on every single request.
Why not gRPC, or plain REST. A subprocess with two pipes needs no port, no TLS, no service discovery, and no code generation step — the server author writes a program that reads lines and writes lines. That is also why the first transport was stdio and the HTTP one arrived second.
Custom transports. The specification explicitly allows them, and almost nobody writes one. Two transports covered the two deployment shapes that existed — a program on your machine and a service behind a URL — and a third has never had a constituency.
A date string is the first thing an MCP client and server exchange. Three messages settle which revision of the protocol is in force and which halves of it each side implements, and until they are done neither end may call anything. The message counter moves for the first time, from 0 to 3. The token counter does not move at all.
The first field of the first message is "protocolVersion": "2025-11-25". Not a semantic version, not a number — a calendar date, and specifically the last day a was made. Compatible changes do not move it. Five revisions exist so far: 2024-11-05, 2025-03-26, 2025-06-18, 2025-11-25, and 2026-07-28, which is 610 days from launch to the fifth break. If the server cannot speak the version it is asked for, it answers with one it can.
Both sides send a object, and it works as a contract: a method nobody declared is a method nobody may call. Servers declare some of tools, resources, prompts, logging, and completions; clients declare some of roots, sampling, and elicitation. Sub-flags refine each one — listChanged: true promises a notification when a list changes. This is why a client written in 2025 can hold a connection to a server written in 2026 without knowing what was added in between.
The client answers the server’s result with notifications/initialized — a notification, so no id, no reply, and no way to fail. Until it is sent the server should refuse everything except a ping. Three messages: one request, one response, one notification. That is the minimum viable MCP conversation, and at the end of it the connection can still do nothing useful, because nobody has asked what the server offers.
Three messages in, the token counter reads zero. The handshake runs inside the host process, before any model is called and regardless of whether one ever is. Version negotiation, capabilities, the server’s name and version — none of it is shown to a model. Everything MCP has done so far is plumbing that costs nothing, which is worth holding onto for exactly one more chapter.
Names for machines, titles for people. Both sides send an info block. Revision 2025-06-18 split the machine-readable name from a human-readable title, because clients had been rendering package identifiers in their user interfaces for eighteen months.
The instructions field. A server may return a block of text that the host is invited to put into the model’s system prompt. It is the second of two channels by which server-authored prose reaches a model, and chapter 07 is about what that means.
The same date, in a header. Over HTTP the negotiated version is repeated on every request in an MCP-Protocol-Version header, so a load balancer or gateway can route on it without parsing a JSON body.
What happens on mismatch. The specification lets a server reply with a version it does support and lets the client decide. In practice clients mostly disconnect rather than downgrade, because supporting two revisions at once means maintaining two sets of assumptions about what a result may contain.
And it is being deleted. Revision 2026-07-28 removes this handshake entirely: every request carries its own version and capabilities in _meta, and servers must implement a server/discover method instead. The ping above goes with it — ping, logging/setLevel, and notifications/roots/list_changed were all removed in the same revision, and a version mismatch now returns an UnsupportedProtocolVersionError rather than a counter-offer. Chapter 08 covers what that changes and what it does not.
Three primitives, and the difference between them is not what they contain but who picks. One tools/list request follows, 47 bytes long, and its answer is the largest thing that will enter the model’s context window all morning: the token counter goes from 0 to 42,000 in a single message, and the message counter reaches 5.
A server may expose three kinds of thing, separated by who does the choosing. are model-controlled: the model decides when to call one. are application-controlled: the host decides what to attach, the way an editor decides which file is open. are user-controlled: a person picks one deliberately, usually from a slash-command menu. The line is not data against action — a tool may be read-only, and a resource may be expensive to fetch. It is which of the three parties holds the choice.
The request is one line of JSON naming the method tools/list, with no parameters — 47 bytes in total. GitHub’s official server answers with 94 tool objects. Each one carries a name, a human-readable title, a description written in prose, and an inputSchema that is ordinary JSON Schema, plus an optional outputSchema and annotations. Long lists paginate with an opaque nextCursor. One short line of JSON returns roughly a quarter of a megabyte.
The description on a tool is not documentation. It is concatenated into the model’s system prompt verbatim, and it is the only instruction the model ever receives about when to use that tool. That is why the guidance for writing them reads like onboarding a new engineer rather than writing an API reference. Whoever shipped the server wrote text that goes straight into your model’s system prompt, and no MCP client displays it by default.
Those 94 definitions have to sit in the window for the model to pick one, so they go into the system prompt. Measured against a 200,000-token : roughly 42,000 tokens, about a fifth of the budget, spent before your first character. Your nine-word question is 12 tokens. Published figures for this one server run from 17,600 to 55,000 depending on version and which toolsets are switched on — and that spread is itself the finding, because nobody’s context bill is a fixed number.
Resources are addressed by URI — file:///…, repo://owner/name/readme — listed with resources/list, fetched with resources/read, and parameterised with RFC 6570 URI templates. Prompt templates are named, take arguments, and surface in clients as slash commands. Both shipped in the first revision in November 2024. Tools took over anyway: servers overwhelmingly expose tools and stop there. The changelogs carry the evidence — resources/subscribe and its unsubscribe twin were replaced wholesale in 2026-07-28 by a single subscriptions/listen stream that clients opt into by notification type, while tools/call has not changed shape since launch.
Which JSON Schema, exactly. Revision 2025-11-25 adopted JSON Schema 2020-12 as the default dialect, and 2026-07-28 loosened inputSchema to allow any keyword from it, with rules for resolving $ref and bounds on composition keywords. Before that, what a server could put in a schema was defined mostly by what the SDKs happened to accept.
Two returns from one call. Revision 2025-06-18 added outputSchema and structuredContent, so a result can be schema-checked machine data and human-readable text at the same time. Older clients read the text and ignore the rest, which is why it could be added without a version break.
Hints that cannot be trusted. A tool may carry readOnlyHint, destructiveHint, idempotentHint, and openWorldHint. The specification then says a client must not trust them from an untrusted server — which removes them from use in the one situation where they would decide anything.
Lists that change and lists that paginate. nextCursor pages through a long tool list; notifications/tools/list_changed tells the client to fetch it again. Chapter 07 uses the second one as an attack and chapter 08 uses it as a fix.
Order now matters. Revision 2026-07-28 asks servers to return tools in a deterministic order. The reason is cache economics: a tool list that reshuffles between calls changes the prompt prefix, and a changed prefix is a prompt cache miss on every turn.
Between the model’s output and the MCP request there is a translation step that almost no documentation names. Your twelve tokens go in, a tool-use block comes out, the host rewrites it as JSON-RPC, and two messages later the answer exists. The counters finish this chapter at 43,292 tokens and 7 messages — and the model has still never seen the protocol.
The model’s output names a tool and its arguments and then stops — and that block is not MCP. It is the model vendor’s own function-calling format: a at Anthropic, an entry in tool_calls at OpenAI, neither of which knows MCP exists. The host reads it and writes a JSON-RPC request. That translation is the seam where the two halves of the stack meet, and neither specification describes the other. What the model does with the result is the subject of the agent-patterns tutorial.
The client shows you the tool name and its arguments and waits. The specification says applications should require human approval, and gives no way to enforce it. There is no permission field in tools/call, no signature, no capability meaning “this one needs a person”. The annotations that could carry the signal — readOnlyHint and destructiveHint — come with the specification’s own warning not to trust them from an untrusted server. Approval is entirely the host’s invention, which is why every client’s approval screen is different.
One request: method tools/call, with params.name set to list_pull_requests and params.arguments matching the tool’s schema. The server receives a line of JSON, calls GitHub’s REST API, and waits. The whole round trip takes 340 ms, of which MCP accounts for about 2 ms of JSON parsing — the rest is somebody else’s API over the network. MCP is not where the time goes, and never was.
A protocol error — unknown method, arguments that fail the schema — comes back as a JSON-RPC error object with a numeric code, and it never reaches the model; the host handles it. A tool error — the repository returned 404, the token expired — comes back as an ordinary successful result with set and the failure written into the content. The split is deliberate: the model has to see the second kind to recover from it, and cannot see the first kind at all.
A tool result is a list of , each one of text, image, audio, resource_link, or an embedded resource. Here it is text: 1,180 tokens of pull-request JSON, landing in the context window exactly like a message. The model reads it and writes 60 tokens — PR #4412 is failing, on the lint job. Seven JSON-RPC messages, 43,292 tokens, one answer.
Results that are not text. A resource_link block hands back a URI instead of pasting a whole file into the window, letting the host decide whether to fetch it. It is the cheapest fix in the protocol for results that would otherwise cost thousands of tokens.
Progress and cancellation. A long call can emit notifications/progress against a token supplied in _meta, and a client can send notifications/cancelled to abandon it. Cancellation is advisory: the work may already have happened, and the protocol has no way to undo it.
Calls that outlive the connection. Revision 2025-11-25 added an experimental Tasks primitive, turning any request into a handle you fetch later. Revision 2026-07-28 moved it out of the core protocol into an official extension and redesigned it around polling.
Parallel calls. A model may emit several tool-use blocks in one turn, and hosts routinely run them at once. MCP has no opinion on this whatsoever — each call is an independent request with its own id, and the ordering guarantees are whatever the host decides to provide.
Timeouts. Clients set a deadline per request and should reset it on progress notifications, which is the one place where a well-behaved long-running tool differs from a hung one on the wire.
A result is about to grow a field. Revision 2026-07-28 requires every result to carry resultType: complete for an ordinary answer, or input_required for the interim result that asks the client for more information. Clients must read a missing field from an older server as complete, which is how a required field ships without breaking every server that predates it.
Requests run the other way too. A server can ask the client for a model completion, for the directories it is meant to work in, and for information from the person at the keyboard. In this morning’s event none of the three happened, so both counters hold at 43,292 tokens and 7 messages — and the reason they hold is the most interesting thing in the chapter.
sampling/createMessage runs from server to client. The server sends messages and gets a model completion back, , without a bill, and without knowing which model answered. It cannot ask for one by name either: it sends modelPreferences carrying three sliders — cost, speed, and intelligence — and the client picks. A server can say “cheap and fast”, and is structurally unable to say “Claude”.
roots/list runs server to client as well. The client answers with a list of URIs — usually — describing where the server should operate, and updates it with notifications/roots/list_changed. It is advisory and nothing more. A local MCP server is a process running as you, with your file permissions and your network access, and no root list changes that. Roots tell a server where to look; they do not stop it looking anywhere else.
, added in revision 2025-06-18, lets a server ask the person at the keyboard. elicitation/create carries a deliberately restricted JSON Schema — flat, primitives only, no nesting — and the client renders a form from it. Revision 2025-11-25 added a URL mode: the server sends a link instead, and you finish an OAuth flow or a payment in a browser. One rule is written in capitals: servers MUST NOT use elicitation to ask for passwords or API keys.
Zero sampling requests, zero roots requests, zero elicitations. Sampling in particular is the least implemented feature in MCP: it asks the client to own model access, a billing story, and a human-approval screen, and most clients never built one. On 2026-07-28 all three were deprecated together. The suggested replacements are to pass paths as ordinary tool arguments instead of roots, and to call a provider’s API directly instead of sampling — which is to say, do it by hand.
Why sampling was expensive to implement. The specification puts a human in the loop: the user should be able to see and edit the prompt a server asks to run, and see the completion before it is returned. That is a whole approval surface, built for a feature almost no server used.
The includeContext hedge. A sampling request could ask for context from the calling server, or from every connected server. The second value let one server read another’s context, which is precisely the composition problem chapter 07 is about. Both were soft-deprecated in 2025-11-25 and formally deprecated in 2026-07-28.
Improved, then retired. Revision 2025-11-25 added tool calling to sampling, so a borrowed completion could itself use tools. Eight months later the whole feature was deprecated. The specification kept improving something the ecosystem had already declined to build.
What replaces the back channel. Revision 2026-07-28 introduces Multi Round-Trip Requests: instead of the server sending its own request, it returns a result marked input_required listing what it needs, and the client retries the original request carrying the answers. The connection never has to be bidirectional at all.
What that says about specifications. Three features were designed, shipped, refined, and then removed because implementers did not build them. The parts of MCP that survived are the parts that were cheap for a client to support on the first afternoon.
Move the same server to a URL and the seven JSON-RPC messages do not change by a single field. What changes is everything around them: one endpoint instead of two, a session header, and four HTTP requests of authorization before the first message. The token counter does not move for any of it, and holds at 43,292.
gives the server a single URL. The client POSTs a JSON-RPC message, and the server chooses per request how to answer: application/json for one response and done, or text/event-stream to hold the line open and send several messages before closing. Same JSON-RPC bodies as stdio, same three message shapes. The transport changed; the protocol did not.
Revision 2024-11-05 shipped HTTP with server-sent events: two endpoints, one for posting messages and one held permanently open by a GET, which meant a connection per client whether or not anything was happening. Revision 2025-03-26 replaced it with Streamable HTTP and deprecated it the same day — 141 days after MCP was published. It is still in the specification in 2026, still deprecated, now under a policy that gives it twelve months’ notice before removal.
Mcp-Session-Id comes back on the first response and is echoed on every request after it. It gave a remote server somewhere to keep per-connection state, and in exchange it meant two requests from the same client had to reach the same process. Sticky routing, shared session stores, and the whole apparatus of a stateful service — for one header. It is the first thing revision 2026-07-28 deleted.
Authorization arrived in 2025-03-26 and was rebuilt in 2025-06-18, which reclassified MCP servers as OAuth 2.1 — they check tokens, they do not issue them. The discovery walk is four HTTP requests before the first JSON-RPC message: an unauthenticated call returns 401 with a WWW-Authenticate header, the client fetches /.well-known/oauth-protected-resource, fetches the authorization server’s metadata, and runs the authorization code flow with PKCE to get a token. None of it costs the model a token.
The client must send a naming the canonical URI of the MCP server it is authenticating to, which binds the issued token’s audience to that one server. Without it, a server you connect to could take the token you handed it and spend it at a different service that trusts the same authorization server. That is an ordinary confused-deputy problem, and it is why an OAuth extension published in February 2020 became a requirement in an AI protocol five years later.
Why a local server checks the Origin header. A page in your browser can reach a server bound to localhost. The specification therefore requires Origin validation and binding to 127.0.0.1 rather than 0.0.0.0 — the exact hole that CVE-2025-49596 opened in MCP Inspector, where DNS rebinding to a local port gave unauthenticated code execution at CVSS 9.4, fixed in 0.14.1.
Registering a client that has never met the server. Dynamic client registration (RFC 7591) let a client obtain credentials on first contact, which is the only way an editor can connect to a server its author never heard of. Revision 2026-07-28 deprecates it in favour of Client ID Metadata Documents, where the client is identified by a URL it controls.
Token passthrough is forbidden. A server must never forward the token it received to an upstream API. Doing so makes the upstream service unable to tell who is actually calling it, and turns every MCP server into a credential relay.
More hardening in 2026-07-28. Authorization servers should return an iss parameter per RFC 9207, and clients must validate it before redeeming an authorization code. Client credentials are also now explicitly bound to the issuer that minted them.
Why sessions had to go. Removing Mcp-Session-Id was the cheapest available way to make remote MCP servers ordinary horizontally-scaled web services. Servers that still need cross-call state now mint their own handles and pass them as ordinary tool arguments, where a load balancer never has to know about them.
A broken stream is now just a lost request. The same revision removed SSE resumability — the Last-Event-ID header and the event ids that let a client rejoin an interrupted stream. A dropped connection loses the in-flight request, and the client must re-issue it with a new id. Resumption was the last piece of per-connection state, and it went with the rest.
Two of the things in the model’s context window this morning were written by people who are not you: the tool descriptions, and the pull-request text that came back in the result. Adding one more server pushes the token counter from 43,292 past 61,000, and completes a combination that neither server is dangerous without.
Two channels put server-authored text into the model’s system prompt: every tool description, and the instructions string returned by initialize. Both are prose the model treats as guidance. Invariant Labs named the consequence in April 2025 — the instructions live in a description the user never reads, because clients render tool names and models read descriptions. The attack lives entirely in that gap.
notifications/tools/list_changed lets a server tell the client its tool list has changed, and the client re-fetches. Which means the definition behind a name you approved last week is not necessarily the definition running today. The is not theoretical: the postmark-mcp npm package behaved correctly for fifteen versions, and version sixteen, published in September 2025, silently copied every outbound email to an address the maintainer controlled. It was the first malicious MCP server found in the wild.
Invariant Labs, May 2025: GitHub’s own MCP server, an agent with access to both public and private repositories, and one public issue whose body contained instructions. The agent read the issue through a legitimate tool call, followed what it said, and pushed private repository contents into a public pull request. No vulnerability in the server, no malformed message, no failed schema check. The mechanics of that turn are the subject of the prompt-injection tutorial; the attacker’s entire exploit here was filing an issue.
Simon Willison’s June 2025 formulation of the : access to private data, exposure to untrusted content, and the ability to communicate externally. Any two are survivable. GitHub’s server brings the first two on its own. Add a Slack server — about 18,000 more tokens of definitions, pushing the window past 61,000 — and the third arrives. Every server you add can only add capability; none of them can take one away.
The things that work: pin server versions and diff tool definitions between fetches, and run servers in containers with no ambient credentials. Scope the OAuth token to the smallest set of operations, and use the server’s own read-only and toolset flags. Treat every tool result as untrusted text rather than as data. And the honest part — none of that is in MCP. The specification standardised the transport, the message shapes, and the method names, and left the trust model to whoever writes the host.
Why the annotations cannot be the answer. A tool can declare itself read-only. The specification then tells clients not to trust that declaration from an untrusted server — so the flag is reliable exactly when you did not need it, and worthless exactly when you did.
Approved once, edited later. CVE-2025-54136 in Cursor, disclosed August 2025 and nicknamed MCPoison: an MCP configuration approved by the user could be edited afterwards and would re-execute without a second prompt. The approval was recorded against the name, not the contents.
Ordinary software bugs, too. The referencemcp-server-git carried a three-CVE chain (CVE-2025-68143, 68144, 68145) allowing code execution. MCP servers are programs that parse untrusted input and shell out, and they fail in all the ways such programs have always failed.
Names in a public registry. A registry of servers addressable by short name invites the same squatting that npm and PyPI have lived with for a decade, which is why namespace verification arrived rather than being designed in.
None of these are protocol bugs. Every incident in this list is a trust-boundary failure in the layer MCP declined to define. That is a real design choice with a real cost, and it is worth being clear-eyed about which half of the problem the specification solved.
One tool ran. Ninety-four were paid for, on every turn of the conversation rather than once. This chapter re-costs the same nine-word question three ways — narrowed toolsets, a shrinkable list, and code the model writes itself — and ends with the revision that deletes the handshake from chapter 02, which chapter 09 then walks end to end.
The 42,000 tokens described 94 tools; the model called one. Worse than the ratio is the billing: the system prompt is re-sent with every message, so those definitions are charged on every turn rather than once at connection. Prompt caching makes a repeated prefix far cheaper — which is exactly why revision 2026-07-28 asks servers to return tools in a deterministic order, since a list that reshuffles invalidates the cache. Cheaper is not free, and the definitions occupy the window regardless of price.
GitHub’s server takes a --toolsets flag and a read-only mode, and its January 2026 release added filtering by the scopes the OAuth token actually holds — worth about 23,000 tokens on its own. Narrow the server to pull_requests and issues and 94 tools become roughly 14, or about 6,000 tokens. The client can push the other way too: some hosts send only tool names and fetch a schema when it is needed, which the Claude Code tutorial measures in detail.
notifications/tools/list_changed also runs forwards. A server can expose two or three tools at connection — a search over its own capabilities, say — and reveal the rest only once the model asks, re-fetching the list mid-session. The same notification that makes a rug pull possible is the one that makes tool lists shrinkable, which is a fair summary of the whole protocol: one mechanism, and the host decides what it means.
Anthropic’s approach, published November 2025, stops putting tool definitions in the prompt at all. The MCP servers are presented to the model as a tree of typed code files. The model writes a short program that imports only the two or three it needs, and runs it in a sandbox. Large intermediate results are filtered inside that sandbox and never enter the context window. On their example workflow the cost fell from about 150,000 tokens to about 2,000 — the same servers, the same protocol underneath.
Revision 2026-07-28 was published the day before this tutorial went up. It deletes the initialize and notifications/initialized handshake from chapter 02 — every request now carries its own version and capabilities in _meta, and servers must implement server/discover instead. It deletes Mcp-Session-Id. It deprecates roots, sampling, and logging, which is most of chapter 05. It also brings the first deprecation policy MCP has had: twelve months’ notice before anything is removed. Chapter 09 runs this same morning on that wire.
Why deterministic ordering became a rule. Prompt caching charges a fraction of the price for a prefix the provider has seen before. Tool definitions sit at the front of that prefix, so a server that returns its tools in a different order each call turns a cache hit into a cache miss on every turn of every conversation.
The server now says how long its list keeps. Revision 2026-07-28 requires ttlMs and cacheScope on the results of tools/list and its siblings: how many milliseconds the answer stays fresh, and whether a shared intermediary may cache it. It is the same problem as prompt caching, one layer down — the protocol had a way to say “this changed” and no way to say “this has not”.
The MCP-against-CLI benchmarks. Published comparisons put MCP at anywhere from 4× to 35× the token cost of driving the same tool from a command line. The direction is real and the multiplier is not portable: the two sides are usually doing different amounts of work, and the ratio moves with how many tools were loaded that nobody called.
How many servers exist. A May 2026 pull of the official registry counted about 9,600 current server records. Wider datasets claim near 90,000 servers in the wild. Both numbers are correct and they measure different things — published-and-named against discovered-anywhere.
Who owns the specification now. Anthropic donated MCP to the Agentic AI Foundation under the Linux Foundation on 9 December 2025, with AWS, Google, Microsoft, OpenAI, Block, Bloomberg, and Cloudflare as platinum members. At the donation the SDKs were seeing more than 97 million downloads a month.
What that governance buys. A specification with eight stewards can deprecate a feature three of them ship, and can commit to a twelve-month window before removing it. Both of those happened for the first time in 2026-07-28, and neither was possible when one company owned the document.
Every request now carries its own version, and that one change removes three messages from this morning. Revision 2026-07-28 makes MCP stateless: no handshake, no session header, no server-initiated requests, and no resumable stream. The same nine-word question runs on it end to end — the message counter falls from 7 to 4, and the token counter does not move at all.
The three messages from chapter 02 are gone. In their place every request carries its own _meta block: io.modelcontextprotocol/protocolVersion and io.modelcontextprotocol/clientCapabilities, with the client identifying itself the same way and the server answering in kind. A version the server cannot speak comes back as UnsupportedProtocolVersionError rather than a counter-offer. Nothing is negotiated once, because nothing is remembered between requests.
A server one RPC that returns its supported protocol versions, its capabilities, and its identity in a single response. A client may call it before anything else to pick a version up front, or skip it entirely and handle an error if one comes back. It is also the backward-compatibility probe on stdio: ask a subprocess what it speaks, and an older server answering nothing tells you it wants the old handshake.
Chapter 05’s three server-to-client requests are replaced by one pattern. Instead of sending sampling/createMessage or elicitation/create down the pipe, a server returns a marked resultType: "input_required" whose inputRequests field lists what it needs. The client answers by re-sending the original request with inputResponses attached. The conversation still happens; it just never requires the server to start one.
The permanently open HTTP GET and the resources/subscribe pair are both replaced by subscriptions/listen: a single long-lived response stream the client opts into by naming the notification types it wants — tools, prompts, resources, or per-resource subscriptions. The server acknowledges and tags what it sends. Request-scoped notifications such as progress still travel on the response stream of the request they belong to, which is the distinction the old design never drew.
Re-run this morning on the newer wire and the handshake is gone: two messages for the tool list, two for the call, and the answer. Seven messages become four, and any of them may land on any server instance, because there is nothing to be sticky about. Now the number that did not move: 43,292 tokens, exactly as before. The revision rewrote the protocol and left the bill untouched, because the bill was never the protocol’s.
Why statelessness was the point. Sessions, the held GET, and stream resumption were three separate reasons a remote MCP server had to remember a caller. Removing all three at once is what lets any request land on any instance, which is the difference between a service you scale and a service you pin.
What a list result now promises. Because list endpoints no longer vary per connection, their answers became cacheable — hence the required ttlMs and cacheScope fields from chapter 08. A stateless protocol can afford to say how long an answer keeps; a per-connection one cannot.
Extensions, and where tasks went. Capabilities grew an extensions field, and the experimental Tasks primitive moved out of the core into an official extension with its own versioning. The core protocol shrank; the interesting parts moved somewhere they can change without moving the date on the specification.
Twelve months, in writing. The feature lifecycle policy defines Active, Deprecated, and Removed, a registry listing everything currently deprecated, and a minimum twelve-month window before removal. Roots, sampling, logging, and the old HTTP+SSE transport are all sitting in it right now.
What a migration actually costs. Tool definitions, tools/call, content blocks, and isError — the parts this tutorial spent chapters 03 and 04 on — are untouched. Almost everything that changed is connection management, which is the half of MCP most server authors let an SDK write for them.
Seven seconds, seven messages, and one sentence about a failing lint job. The right margin says which chapter earned each row. The last line is the one worth keeping: the model read a system prompt and a tool result, and never saw a byte of the protocol that fetched them.