MCP: the tool-and-resource protocol
Trace an MCP exchange from tool list to tool result, put a capability in the right primitive, and size what connected servers cost before a task starts.
On this page
The Model Context Protocol is a contract between an application that runs a model and a process that offers it tools and data. Without one, every pairing of agent and system needs its own adapter; with it, each system ships one server, each agent ships one client, and any client can use any server. The price is paid in the window: every connected server's tool list is input on every call, and eight servers put about 18,000 tokens in front of a task before it starts.
N × M adapters, or N + M
A team runs a model in four places — a coding agent, a chat assistant, a review bot, a nightly job — and wants each to reach ten internal systems. Written directly, that is forty integrations, each with its own definitions, auth and bugs, and the eleventh system costs four more. Behind a protocol it is fourteen pieces, ten servers and four clients, and a fix to one server reaches every agent at once. It is the argument of designing to an interface applied across processes: callers depend on a contract, implementations vary behind it.
Hosts, clients and servers over JSON-RPC
The Model Context Protocol "Specification" (opens in a new tab) names three roles. The host is the application the user runs: an IDE, a chat app, your own agent. Inside it, a client holds the connection to one server. A server is a process that offers capabilities. Messages are JSON-RPC 2.0: a request carries an id, a method and parameters; a response carries the same id and a result or an error; a notification has no id and gets no reply.
{"jsonrpc": "2.0", "id": 7, "method": "tools/call",
"params": {"name": "create_issue", "arguments": {"title": "Login loops on Safari", "team": "web"}}}
{"jsonrpc": "2.0", "id": 7,
"result": {"resultType": "complete",
"content": [{"type": "text", "text": "Created WEB-4182"}], "isError": false}}
How the two sides agree on what they speak has changed between revisions of the specification. The current revision is stateless: every request carries its protocol version and the client's capabilities, and a client may ask up front which versions a server supports. Earlier revisions opened each connection with an initialize request and an initialized notification. An SDK handles either.
Three primitives, three controllers
A server offers three kinds of thing, and what separates them is who decides when each is used.
Tools are model-controlled. A name, a description and an inputSchema in JSON Schema: the same object as in tool use and the tool-call loop, called when the model decides.
Resources are application-driven. Data named by a URI — a file, a table's schema, a runbook — which the host lists, reads, and decides whether to put in front of the model, through a picker or an automatic attachment. The model sees a resource only if the host places it there.
Prompts are user-controlled. Templates with arguments that a person invokes, typically as a slash command.
So: if the model should decide, it is a tool; if the application or the user selects context, a resource; if a person starts a workflow, a prompt. The mistake is to make everything a tool. A 30,000-token database schema exposed as get_schema() gets called on half the tasks and lands in the history each time; as a resource the host attaches it during migration work, and a narrow describe_table(name) tool covers the rest at a few hundred tokens a call.
Two transports
stdio. The client launches the server as a subprocess and they exchange newline-delimited JSON-RPC over standard input and output. It is local, and the specification says a stdio server takes its credentials from its environment, so it runs as whoever launched it; an MCP server, end to end deals with what that implies.
Streamable HTTP. Each message is a POST to one endpoint, answered with a JSON body or a stream of server-sent events for that request — the HTTP you already run. It suits a server shared by many users, with an authorization framework so the server can check each caller's scopes on each request.
The exchange, and its two kinds of error
The client calls tools/list and gets each tool's name, description and schema. The host renders them into the model's tool list, prefixing an identifier it gives each server where two servers both offer search; the specification leaves that to the client, and warns that a server's self-reported name is not guaranteed to be unique. The model returns a call, the client sends tools/call, and the result comes back as content blocks with an isError flag.
The errors split as module 2 taught. An unknown tool or a malformed request is a protocol error, a JSON-RPC error the client handles. A date in the wrong format or an API timeout is a tool execution error: a normal result with isError: true and text written for the model, which the specification says clients should pass on so the model can correct itself. The specification also asks servers to list tools in the same order every time, for prompt caching: tools render first in the request, and a reordered list is a miss on everything after it.
What eight servers cost before the task starts
A platform team connects their coding agent, which has the course's 200k-token window from the four numbers, to eight servers of about fifteen tools each, and a definition with a useful description is about 150 tokens:
8 servers × 15 tools × 150 tokens ≈ 18,000 tokens on every call
Nine per cent of the window looks affordable; the window is not where the cost lands. A twelve-call task reads the definitions twelve times, 216,000 tokens; at 1,000 tasks a day that is 216M tokens at P_in, or at C_r × P_in if the list stays byte-identical and cached. A typical task uses three or four of the 120. The second cost is accuracy: three servers each offering search is the overlapping surface writing tool descriptions the model can use measured on twelve tools, at ten times the scale.
Deferred loading and tool search
The first fix is to connect less: a list of servers per task class. A review bot needs the code host and CI, not paging and the cloud console.
The second is to stop sending definitions nobody asked for. With deferred loading, most definitions stay out of context and the model gets a search tool over the catalogue; it searches, and the matches are added. Anthropic's "Tool search tool" (opens in a new tab) page describes one implementation: deferred tools are left out of the prompt prefix, a search returns up to five matches by default, the matches are expanded inline in the conversation so the prefix stays untouched, and the page suggests it past about ten tools or 10,000 tokens of definitions. Keep the five most-used tools loaded:
always: 300 (search tool) + 5 × 150 ≈ 1,050 tokens per call
per search: 5 definitions × 150 ≈ 750 tokens, plus one round trip
two searches: 1,050 + 1,500 ≈ 2,550 tokens, against 18,000
Where loaded definitions go decides whether it pays. Appended to the tool list, which renders first, they change the opening bytes and every cached token after them misses; inserted in the messages, they leave the prefix alone. Each search still costs a round trip, and a search in words no description contains finds nothing, so descriptions need the users' vocabulary as much as before.
Connecting a server also means its descriptions are text your model reads and follows; the specification tells clients to treat a server's tool annotations as untrusted unless the server is. Which tools a company's own server should expose, with what auth and what it must refuse, is designing the tool surface in the agents course.
In an interview
The interviewer wants three things: why a protocol (N × M adapters become N + M), who controls each primitive (model, application, user), and what connecting servers costs. Say the arithmetic — "eight servers of fifteen tools at 150 tokens is 18k on every call, 216k over a twelve-call task" — then the fixes in order: fewer servers per task class, then deferred loading with a search step, with the cache placement stated.
The mistake that loses points is "connect every server and let the model choose". It pays 18k tokens a call for tools the task never uses and hands the model 120 overlapping descriptions. The second is putting large reference data in a tool or a description when it is a resource the host should attach.
Check yourself
1. A team wants the agent to "have" a 30,000-token API reference through MCP. Tool, resource or prompt?
A resource the host attaches when the user works against that API, plus a narrow
lookup_endpoint(path)tool returning a few hundred tokens. As aget_reference()tool it lands 30k in the history whenever the model reaches for it; in a description it is paid on every call.
2. Five servers of 25 tools at 200 tokens, eight calls a task, 2,000 tasks a day. What do the definitions cost, and what do you ship?
5 × 25 × 200 = 25,000 tokens a call, 200,000 a task, 400M a day at
P_in, orC_r × P_incached. Ship a per-task server list first, then tool search with the four most-used tools loaded: about 300 + 800 = 1,100 a call plus about 1,000 per search. Check the trace for searches that find nothing before claiming the saving.
3. Your coding agent and your support assistant both need the issue tracker. One server or two integrations, and which transport?
One server: the second client costs nothing to add, and a schema fix reaches both. Run it over Streamable HTTP as a shared service that checks each caller's scopes on every request.