Every application solving the same problem alone

Before this protocol existed, an AI application that wanted to read a file, query a database, or call an external API had to write its own connector for that source, and every source had to accommodate however many applications showed up asking in however many ways. Anthropic’s own announcement of the protocol names the problem directly: “Every new data source requires its own custom implementation, making truly connected systems difficult to scale” [7]. Multiply M applications by N tools and data sources, and the number of connectors needed is not M plus N, it is M times N — and each one independently has to solve the same small problems: what is on offer, how is an attempt to use it described, and how does the caller know whether it worked.

MCP does not add a capability that did not exist before; applications could already call APIs, and APIs could already be documented. What it supplies is agreement — one shared answer to those recurring questions, published once, so neither side of a connection has to guess how the other will behave. The rest of this briefing works through that answer mechanically, reading only the specification itself.

Three roles wired to one message format

MCP names three roles rather than two. A host is the application a person is actually using — a chat interface, an IDE, an agent runtime. A client is the connector living inside that host, one per server it talks to. A server is the separate program that actually holds the tools, files, or data being connected to [1]. Splitting host from client lets one application hold many independent connections open at once, since each client speaks for exactly one server.

ADVERTISEMENT

All three exchange messages in a single wire format, JSON-RPC 2.0 — the specification says MCP “takes some inspiration from the Language Server Protocol,” which “standardizes how to add support for programming languages across a whole ecosystem of development tools” [1]. A JSON-RPC request names a method and carries parameters and a caller-chosen identifier; a response carries that identifier back with either a result or an error, never both [6]. Nothing about that shape is specific to language models, which is the point: nothing about “understanding a tool” has to be invented at the wire level. The base protocol stays thin on top of it — requests are “stateless, self-contained,” and what a client may ask for is negotiated per request rather than assumed from a long session [1].

Finding out what a server can do

To use a tool, a client first discovers it. It sends a tools/list request, and the server answers with the tools “currently available to the requesting client” — a set that must not vary “as a side effect of other requests on the connection,” though it may vary with whatever authorization arrived attached to the request [2]. Each tool carries a name, an optional title, a plain-language description, and an inputSchema: a JSON Schema object stating which arguments it accepts and which are required [2]. A server may also publish an outputSchema, in which case it “MUST provide structured results that conform to this schema” [2]. None of this is prose a model has to interpret loosely — a schema is a checkable contract, and a call that does not match it can be refused before anything runs.

One detail reveals who this protocol was built for. The specification asks servers to “return tools in a deterministic order” because a stable ordering “improves LLM prompt cache hit rates when tools are included in model context” [2] — a wire-format rule written for the economics of a model reading the list, not a human reading documentation.

Making the call, and trusting the answer

Once a tool is chosen, the client sends tools/call naming the tool and its arguments, and the server answers with a result [2]. That result can carry unstructured content — text, an image, a link to a resource — and, where an output schema exists, a parallel structuredContent value the client can check mechanically instead of parsing prose [2].

MCP keeps two separate channels for failure. A malformed request or an unknown tool name comes back as an ordinary JSON-RPC error, which the specification says models “are less likely to be able to fix” [2]. A failure inside the tool’s own logic — a bad date, a missing record — comes back inside a normal successful response with isError: true, carrying “actionable feedback that language models can use to self-correct and retry with adjusted parameters” [2]. Its own worked example names the broken rule and the fact needed to satisfy it: “Invalid departure date: must be in the future. Current date is 08/08/2025” [2]. A protocol that only reported “call failed” would leave a caller nothing to do next; this one hands back a reason.

ADVERTISEMENT
A bench-mounted message-exchange console with a small brass shuttle caught mid-travel along a rail between two panel stations, one indicator lamp lit and the other still dark
Figure 1. Every exchange has the same shape whether it asks what a server can do or asks it to act on that: a request travels out, and a matching response has to travel back before anything is settled.Image prompt and art direction by Brecht Corbeel; generation pending.

Not everything on offer is something to call

Not every piece of context a server exposes is meant to be invoked. A resource is data the server offers for a person or a model to read — a file, a database schema, an application record — identified by a URI and retrieved with resources/read, not called with arguments [3]. The distinction is about who decides. Tools are “model-controlled,” discovered and invoked by the model on its own initiative. Resources are “application-driven”: the host decides how to surface them, through a picker, a search box, or automatic inclusion based on its own heuristics [3]. Routing every kind of context through the same tool-calling path regardless of who ought to be deciding is the sort of ad hoc choice a one-off integration tends to make differently each time.

Capability-scoped access, not blanket trust

A server that can read a filesystem or call a payment API cannot reasonably grant every caller the same access, and MCP does not invent its own scheme to decide who gets what — it adopts one that already exists. Where authorization applies, the MCP server acts as an OAuth 2.1 resource server and the client as an OAuth 2.1 client, with a separate authorization server issuing bearer tokens scoped to particular permissions [4]. A server publishes what it requires through OAuth 2.0 Protected Resource Metadata, and when a caller’s token lacks a needed permission, it can name the missing scope directly rather than refuse without explanation [4].

Scoping matters because the failure on the other side is concrete. MCP’s own security guidance describes “token passthrough” as an anti-pattern in which a server forwards a caller’s token to a downstream API without checking it was issued for it, and states that servers “MUST NOT accept any tokens that were not explicitly issued for the MCP server” [5]. What a server exposes and what a given caller may use are two separate facts the protocol keeps checkable, rather than one assumption of trust.

A card-cage of frosted-acrylic capability modules on the server station, with one module part-drawn from its slot and a small brass keyed gate latch swung open beside it
Figure 2. A server does not hand every caller everything it can do; which modules a given request may draw on depends on the key that request is carrying.Image prompt and art direction by Brecht Corbeel; generation pending.

The one thing the protocol admits it cannot police

There is a limit to what any specification can enforce, and MCP says so about itself. A tool’s description is ordinary text handed to the model as context, and the specification’s own trust principles warn that “descriptions of tool behavior such as annotations should be considered untrusted, unless obtained from a trusted server” [1]. That is the seam where prompt injection lives: a server, or data a tool returns, can phrase its description to read as an instruction rather than documentation, and nothing at the protocol level tells the two apart. The answer is procedural, not technical — it recommends there “always be a human in the loop with the ability to deny tool invocations” and that clients show a tool’s inputs to the user before a call goes out [2]. Its own summary is blunt: “MCP itself cannot enforce these security principles at the protocol level” [1] — an honest admission, and a more useful one than a specification that implied otherwise.

What the standard part actually buys

Nothing described above is exotic on its own; schemas, bearer tokens, and request-response pairs all predate MCP by decades. What the protocol supplies is agreement — one description format, one discovery call, one calling convention, and one authorization model every server and client can rely on the other side having implemented the same way. That is the difference between an ecosystem where each new tool needs its own bespoke connector and one where a client written once can discover, call, and check permissions against a server it has never met, because both sides already agreed on the shape of the conversation before either one existed.