MCP vs REST API: Fifty Years of the Same Question
How programs learned to explain themselves, and why the 2026 revision turned MCP back toward REST
Written 5 August 2026, eight days after the specification revision that removed sessions from MCP. Five revisions in under two years, and this one rewrote the foundations — so check the primary source before you build on any of it.

How programs agree on what they can do
In July 2026 the Model Context Protocol threw out sessions and became stateless — the one constraint REST has been built on since 2000. The argument everyone was having, "MCP or REST API?", got its answer while it was still going on, and the answer was not that either side won.
REST is an architectural style for the interface between two programs, described in 2000. The Model Context Protocol, published in November 2024, is a way of telling a language model, while the conversation is running, what a program can do. They are stacked far more often than they are chosen between: MCP on the outside, the same REST API underneath.
But there is a real question underneath the argument, and the real question is more interesting: how does one program explain to another what it is able to do? We have rewritten the answer four times in fifty years — in the 1970s, in the 1990s, in 2000, and again in 2024 — and each time it looked like the last.
This article is about how we got here. There is no code in it, because none of the decisions worth arguing about are visible in code: what each generation of this answer gave up, what it got back, and who paid the difference. If you are not a programmer but want to follow what the people around you are arguing about, it was written for you too.
A problem fifty years old
A problem fifty years old
Two programs, two machines. One of them needs the other to do something: read a record, charge a card, hand back a file. Everything in this article follows from that sentence, and for fifty years it has been harder than it sounds.
Three difficulties, none of which have ever gone away. The network fails, and it fails in a particular way — not by saying no, but by saying nothing. The machines differ: byte order, the size of a number, the encoding of a string. And the agreement has to exist before either program starts, because a program cannot answer a question it was never built to hear.
The first serious answer arrived in the 1970s and it was elegant. RFC 707, published in 1976, proposed treating a request sent across a network as an ordinary procedure call: the caller names a function and passes arguments, and where that function executes is somebody else's problem. Xerox PARC turned that into working practice — Birrell and Nelson's Cedar RPC, described in 1984 — with a compiler that generated the plumbing at both ends.
The idea is beautiful because it deletes an entire category of work. A programmer writes balance(account) and thinks about accounts, not about sockets, timeouts and byte order; code written for one machine can be cut in half and spread across two with barely an edit. For most of the code, most of the time, the disguise holds.
Why RPC did not survive: partial failure
Why RPC did not survive: partial failure
Reality broke it at the seam where the disguise is thinnest. In 1994 four engineers at Sun — Waldo, Wyant, Wollrath and Kendall — wrote the paper that settled the argument, A Note on Distributed Computing, listing what does not survive the costume: latency, memory access, concurrency, and partial failure. Partial failure is the one that matters. A local call either returns or fails so completely that the process dies with it. A remote call has a third outcome, and the third is silence. Silence does not tell you whether the card was charged. The code that copes with it — retry, reconcile, ask a human — is precisely the code the abstraction was built to hide. RPC made the easy ninety-five per cent invisible and left the remaining five per cent invisible too.
The age of heavy contracts
The age of heavy contracts
The 1990s answered a different question. Not "how do we make a remote call look local", but "how do we write the agreement down so precisely that neither side can get it wrong". If that sounds like a solved problem, hold the thought: it is the exact question MCP answered in 2024, and the exact answer it had to withdraw in July 2026.
CORBA went first. The Object Management Group formed in 1989 and published the specification in 1991, and its centrepiece was IDL — Interface Definition Language, a language for nothing but describing interfaces. You wrote the interface in IDL, a compiler generated a client stub and a server skeleton from it, and an Object Request Broker carried the calls. SOAP repeated the shape in XML: XML-RPC in 1998, SOAP 1.1 submitted to the W3C as a Note in 2000 (the W3C made SOAP a Recommendation only with 1.2, in 2003), and a contract called WSDL — a Note of its own, a year later — naming the operations, their message shapes and types, and the address to send them to. Around it grew the WS-* family — WS-Security, WS-Addressing, WS-ReliableMessaging — dozens of specifications, each solving a genuine problem, together larger than most of the systems built with them.
A contract in this sense is a machine-readable description of everything one program will accept from another: names, arguments, types, errors. It lived in a language of its own because neither side's source code could serve as the agreement — the two sides were usually written in different languages by different companies, and the point was that neither had to read the other. The benefit was real: a mismatch between caller and callee became a build error on a Tuesday afternoon instead of an outage at three in the morning.
"Bulk and rigidity" is what the retrospectives say, and it explains nothing. Here is the bill. Adding one optional field to a message meant regenerating the stubs on both sides and deploying both, in step — a small change turned into a scheduling problem between two companies. The WSDL for a modest service ran to thousands of lines no human read. And two vendors' toolkits, reading the same WSDL, would generate clients that could not talk to each other; the industry's answer was the WS-I Basic Profile in 2004, specifying which subset of the standards everyone should use. A contract about how to write contracts.
The stricter the agreement, the harder it is to change. That comes back before this article is over. Strictness is never free. It is paid for later, by whoever needs to add a field.
The year 2000: a dissertation that reshaped the web
The year 2000: a dissertation that reshaped the web
The next answer did not try to write a better contract. It arrived as a description of something that had already won. In 2000 Roy Fielding defended a doctoral dissertation at the University of California, Irvine. He was not proposing a technology and had no product to sell: he had co-authored HTTP/1.1 and the URI specification, and chapter 5 explains, after the fact, why the web had scaled when the carefully engineered architectures around it had not. He gave the explanation a name: REST.
The method is what makes it unusual. Fielding starts with a system that has no constraints at all and adds them one at a time, each taking a capability away and giving a property back. Five are required. The optional sixth, code on demand, has stayed a footnote.
The six constraints of REST
The six constraints of REST
Two of the five decide everything that follows in this article. The rest are here so the list is honest. Separate the sides, so client and server can be rewritten and redeployed independently. Keep no memory between calls, so that everything needed to understand a request travels inside it. Say whether an answer may be reused, so something other than the origin can serve it. Keep the interface uniform: a small fixed vocabulary — addressable resources, a handful of verbs, messages that describe themselves — instead of a bespoke set of operations per service. Allow a middleman, so a proxy, gateway or cache can sit in the path without either end knowing.
Why REST is stateless
Why REST is stateless
Statelessness is the constraint people misread most often. If the server remembers nothing between requests, any request can be answered by any server. Put forty machines behind one address and a load balancer can hand each request to whichever is free; lose one of them mid-traffic and you lose that request and nothing else, because there was nothing else on it. A cache in front of them answers without the origin ever hearing about it. This is not a limitation the web tolerates, it is the property the rest of the web's infrastructure is built on, and every CDN in existence is a consequence of it. The price, which Fielding states outright, is repetition: credentials on every request, context on every request, larger requests than a stateful protocol would need. A quarter of a century of infrastructure is the evidence that the trade was worth making.
REST won for a reason that has nothing to do with elegance: it demanded nothing new. No IDL compiler, no broker, no registry, no code generation step, no vendor. Where CORBA and SOAP asked an organisation to adopt a stack, REST asked it to notice what it was already doing and do more of it. The machine-readable contract came back later, as OpenAPI — born as Swagger in 2011, standardised as OpenAPI 3.0 in 2017 — but voluntary, optional, and, importantly for what follows, read before the program runs. Nobody discovers an endpoint mid-conversation.
Hold on to that. Everything up to here shares one assumption, and the assumption is about to break.

The N×M problem
What broke when models arrived
What broke when models arrived
The assumption is that a human read the documentation before the program ran. RPC stubs, IDL, WSDL, OpenAPI — all of them are consumed at build time, by a programmer or by a generator working on his behalf. By the time the first request is sent, both sides know everything they will ever know about each other. A language model has no build time.
- The difference is fundamental rather than one of degree: at the other end of a REST call sits a programmer who read the documentation beforehand and encoded the result into the source
- A model reads its instructions where it reads everything else — in the conversation, while the conversation is happening. What it can do has to be described to it then, in words, in the request
- Every vendor solved that its own way and the solutions did not fit together: function calling, plugin manifests, bespoke tool formats, each with its own schema for the same idea
- The "N times M" problem follows immediately. Three models and three services is nine separate adapters, and each new service on either side multiplies rather than adds
November 2024: MCP appears
November 2024: MCP appears
On 25 November 2024 Anthropic published the Model Context Protocol. The first specification revision carries the date 2024-11-05, which is why the version strings and the announcement disagree by three weeks — worth knowing before you read the revision list below. Unusually for a protocol launch, Anthropic named its model openly rather than claiming novelty.
- The model was the Language Server Protocol, which eight years earlier had solved the same shape of problem for code editors. Before LSP every editor needed a plug-in for every language; after it, a language wrote one server and every editor could use it. N times M became N plus M
- Three things an MCP server can offer: tools (actions the model may invoke), resources (data it may read), prompts (ready-made requests a user can pick)
- The key departure from everything above: the list of capabilities is requested while the program is running, not read in advance. The client asks a live server what it can do and hands the answer to the model as text
- What that bought was leverage. A service writes one server; Claude Desktop, Cursor and VS Code all talk to it knowing nothing about it in advance. One socket instead of nine adapters
Five revisions in twenty months
Five revisions in twenty months
A protocol's changelog is usually the dullest document it has. This one is a plot summary.
- November 2024 — the base: JSON-RPC, stdio and HTTP+SSE
- March 2025 — OAuth 2.1, a new transport, batching
- June 2025 — structured output; batching is removed before it is three months old
- November 2025 — icons, default values in schemas, JSON Schema 2020-12
- July 2026 — the largest rework in the protocol's history
This is the changelog read end to end, not a summary of anybody's commentary on it. Read the dates rather than the entries. A capability that was added in March and deleted in June was never load-bearing, but a rework of the foundations in month twenty means the foundations were wrong, and that is a different kind of news. This protocol is not settled. I have a server running against it, so I have already agreed to rewrite.
The 2026 reversal
The 2026 reversal
The revision of 28 July 2026 removed sessions from MCP, and with them the handshake that opened every connection. In the original design a client connected, announced its protocol version and capabilities, received the server's in return, and from then on both ends referred back to a conversation they were jointly remembering. That conversation is gone. Every request now carries its own version and its own context, and a server may answer it without having seen anything that came before.
In plain words: MCP became stateless. That is Fielding's second constraint, adopted twenty months late and under pressure, by a protocol that had started from the opposite premise. What follows from statelessness followed here too, in the same revision — response caching, so a client can be told it need not ask again yet, and headers carrying enough routing information that a proxy can direct a request without parsing the body. These are the features you add when your protocol has to survive contact with ordinary infrastructure.
What the 2026-07-28 revision deprecated
What the 2026-07-28 revision deprecated
Three of the protocol's own capabilities went obsolete in the same revision, and the three have one thing in common: each assumed a durable relationship between two processes. The server calling back to the model, folder access, protocol-level logging. (The revision deprecates more than these three — the third article walks the full list — but the rest are housekeeping. These are the ones that follow from the removal of sessions.)
The reversal is late and right, and I say that as somebody who will have to rewrite a server because of it. Sessions in a protocol that lives between two processes owned by different people were a mistake from the beginning: the two ends are deployed, restarted, scaled and upgraded on separate schedules, and a shared memory between them is a liability one side always ends up holding. This is the lesson of the heavy contracts arriving on cue — the stricter the agreement, the harder it is to change — except that here the strict part was not a schema but the requirement that both ends remember the same conversation, and unwinding it took a rewrite of the foundations rather than a version bump.
None of this is a catastrophe for anyone shipping today. The 2026-07-28 revision arrived as a Release Candidate rather than a final standard, the SDKs implementing it are in beta, everything it deprecates has a year of grace, and nothing broke on 28 July. The other side of that same fact is less comfortable: servers written against the session model still work, tutorials still teach the handshake, and the gap between what the specification says and what the tools do is the widest it has been in the protocol's history.

Convergence
MCP vs REST API: what is genuinely different
MCP vs REST API: what is genuinely different
If MCP has adopted statelessness, caching and middlemen, a fair question is what is left. Discovery is the honest answer, but it needs restating, because the version people repeat — "a server can announce a new tool in the middle of a conversation" — sits badly with a protocol that no longer has conversations. There is no session for a server to push into, so the durable mechanism is pull rather than push: the client asks a live server what it can do, and the 2026 revision added a dedicated call for exactly that, plus a cache hint saying when it need not ask again. A server may still signal that its list has changed while a stream happens to be open, but nothing requires a stream to stay open, so no client can depend on being told. The announcement is not the thing without a REST equivalent. The thing without a REST equivalent is that the list of available actions is a runtime value, fetched during the work, where OpenAPI is a document read before the work starts.
- Typed responses: text, image, audio, or a link to a resource, declared as such — instead of a response body whose meaning the caller must know in advance
- Tool descriptions are untrusted input. If the server is someone else's, its self-description is a claim rather than evidence — and the model reads that claim as instructions. That is the shape of a prompt injection: instructions arriving as data
Side by side, once the 2026 revision is taken into account:
| Question | REST | MCP |
|---|---|---|
| Who makes the call | A programmer who read the documentation before the code was written | A model choosing a tool while the conversation is happening |
| When the list of actions is known | Before the program runs; OpenAPI is read once, at integration time | At runtime; the client asks a live server what it can do |
| Message format | HTTP verbs, one address per resource | JSON-RPC; every call goes to one endpoint as `tools/call` |
| Where the meaning sits | In the address: `GET /articles/17` | In the body; from outside every envelope looks the same |
| State between calls | None since 2000 (stateless), by design | None since 2026-07-28 (stateless), after sessions were removed |
| Caching | HTTP caching, ETag, conditional requests, CDNs | Response cache hints, added in the 2026-07-28 revision |
| Errors | An HTTP status code, addressed to a programmer who is not present | Two levels: the protocol broke, or the tool ran and reported bad news to the model |
| Authorization | Usually an API key in a header | OAuth 2.1, PKCE mandatory, the token bound to its recipient |
| Cost per call | Predictable; you can put it in a spreadsheet | Tool descriptions billed in tokens on every single request |
| Human in the loop | No API standard has ever needed the clause — no API caller was ever improvising | The specification asks outright for the ability to see a call and refuse it |
Where this leaves us
Where this leaves us
MCP did not replace REST, and was not trying to. In almost every real system the two are stacked rather than opposed: the MCP server is a thin translator the model talks to, and behind it sits the same REST API that was already there, unchanged, still serving the mobile app and the web front end. Replacing the API was never on the table. Giving a new kind of caller a way to find out what the API offers was, and that turned out to need its own layer.
The direction of travel is the more interesting part. REST began stateless, cacheable and uniform, and spent a quarter of a century acquiring description — WSDL's ghost returning as OpenAPI, endpoints written down in machine-readable form so tools could consume them without a human in the middle. MCP began with description as its whole reason for existing and spent twenty months acquiring statelessness, caching and the ability to put a proxy in the path. They started at opposite ends and are meeting in the middle. That is what the 2026 reversal recorded: not a defeat for MCP, but a convergence arriving from the direction nobody was watching.
This is the normal course of events rather than anybody's mistake. The constraints that survive are not the ones a designer prefers, they are the ones the network imposes. Between two processes owned by different people, deployed and restarted and upgraded on separate schedules, only a few arrangements work at all. Carry your context with you, and assume the other end may vanish and come back as a different instance that has never heard of you. Everything else is negotiable — including, as MCP has just discovered, whether anything in the middle is allowed to help. RPC ignored partial failure, and the bill came in as a decade of retry logic nobody had budgeted for. CORBA and SOAP ignored what change costs; the WS-I Basic Profile is the receipt. MCP's bill arrived on 28 July 2026, and it was cheaper than either, because the protocol was twenty months old and almost nothing had been built on it yet. Each generation learned the same thing in its own vocabulary, and each thought its answer was the last one.
So the next time an article announces an API killer, the useful question is not which technology wins. Ask instead which of these constraints the new thing has given up, and who pays for that. Sometimes the answer is nobody, for a while. It has never been nobody for long.
What that costs in practice, in tokens and in money, is the subject of part two: what MCP costs in tokens. What an MCP server is actually made of — transports, tool descriptions, authorization — is part three.
Sources
Sources
The MCP specification is published at modelcontextprotocol.io; this used revisions 2024-11-05, 2025-03-26, 2025-06-18, 2025-11-25 and 2026-07-28, plus the official changelog for the last of them. Roy Fielding, Architectural Styles and the Design of Network-based Software Architectures (2000), chapter 5. Jim Waldo, Geoff Wyant, Ann Wollrath and Sam Kendall, A Note on Distributed Computing (Sun Microsystems Laboratories, 1994). Andrew Birrell and Bruce Nelson, Implementing Remote Procedure Calls (1984). RFC 707. The Language Server Protocol specification.