Who can talk to whom?
MCP looks complicated until you ask who holds access. Five machines below answer it, one boundary each.
There are four characters in this story. You. An agent working on your behalf; MCP's spec calls it the host, and it is the only character that holds connections. Some servers, one per capability: weather, flights, a calendar. And sometimes an app, a scrap of UI a server ships to your screen. A fifth thing hides inside the agent and never touches the wire at all: the model. It only ever writes text. Keep that one in mind.
Almost everything confusing about the protocol untangles once you track a single fact: who is allowed to talk to whom. That fact is what the machines test. I'll need your help, because none of them runs on its own.
With a nod to Sam Rose's queueing essay.
01One connection
Start with the only line that exists at the beginning: the agent holds a connection to a server. The agent sends a request; the server answers with a result. Nobody else is on the wire.
That's the entire protocol: request out, result back. The line itself is ordinary JSON-RPC. The agent opens the session, asks tools/list to learn what the server can do, then calls tools by name. When the machine above ran, the traffic looked like this:
→ agent to weather
{
"jsonrpc": "2.0", "id": 7,
"method": "tools/call",
"params": {
"name": "get_forecast",
"arguments": { "city": "Lisbon" }
}
}
← weather to agent
{
"jsonrpc": "2.0", "id": 7,
"result": {
"content": [
{ "type": "text", "text": "sunny · 22°" }
]
}
}The spec's own vocabulary is a little stricter than mine. The agent application is the host, and inside it runs one client per server; a connection is always one client talking to one server. Five servers means five clients, all in the same host, none aware of each other. Everything else in MCP is about what happens when this one line isn't enough.
02Servers are strangers
Add a second server and the shortcut is tempting. Surely flights can just tell the calendar about your trip? Click and see.
There is no wire between servers. Not a slow one, not a hidden one. None. Flights cannot call the calendar and never learns it exists. Each server knows its own job and nothing else.
The reason is not tidiness. A server is code you trusted with one job, written by someone you have never met. If flights could reach the calendar, then whoever compromises flights inherits your calendar too, and a poisoned tool description becomes a burglary kit. MCP's answer is topology instead of vigilance: the call fails because the wire does not exist, and no amount of clever prompting invents a wire.
It also kills a quieter tax. Five servers that could all call each other would be ten integrations somebody maintains. Five servers that can't is five.
03Facts travel through the middle
So how does the arrival time get to the calendar? It rides through the agent. The agent asks flights and gets 14:05 back. It holds the calendar connection too, so it hands the time over.
Try the toggle. With the agent gone, the time is stranded. There is no path from one server to the other. The workflow doesn't live in the servers and isn't wired between them. It exists only in the middle.
Here is what the machine hides: who decided to make the second call? The model did. The host handed it your goal and the flight result, the model wrote "add 14:05 to the calendar" as text, and the host turned that text into a real call. The result goes back into the model's context, the model reads it, and decides what happens next. That loop is the entire trick behind the word agentic.
The loop has a price. Every fact that crosses between servers rides through the model's context window, so the middle is not free: a two-line arrival time travels well, a forty-megabyte spreadsheet does not. The protocol's newer answer, passing IDs around instead of payloads, gets its own essay later.
04Apps have to ask
Now the character this article was written for. A server can ship UI to your screen: a seat map, a price ticker. That app runs in a sandbox, and here is the part people miss: the app has no connection to its own server. It can't fetch. It can't call home. When it needs data, it asks the agent, and the agent decides.
The permission you flipped is real. An app declares which tools it wants and the host enforces that list. Same app, same server, same button. The only thing that changed is what the middle allows.
Mechanically the sandbox is boring, which is the point. The app is an iframe with no network. Its clicks become messages to the host, the host checks the tool against the declared list, and only then does a real client make a real call. The app never holds a token, a URL, or a connection. Give it fetch and you have handed the server a browser inside your session. Refuse, and the worst a malicious app can do is ask.
The declared list holds more than proxy permissions, because a tool picks its audience: the model, the app, or both. The seat map calls get_seat_availability every time you hover a row; the model never needs that tool, so it never sees it. The tool that opened the seat map in the first place belongs to the model, and the app can't call it. Same server, two audiences, one host enforcing both lists.
Traffic also flows the other way, into the conversation. What you do inside the app doesn't stay there. When the app's call comes back, the host writes the result into the model's context; that is the note under the machine above, and it changed before the app's display did. Refresh again and watch the order. Without that line the model would keep quoting a price you refreshed away two clicks ago. It is the same context window from section 03, fed from a second direction.
05The wire runs both ways
So far every request started on the left. The protocol is more symmetric than that, and this is the part most explainers skip. A server can ask the agent to run the model for it; the spec calls this sampling. Summarize this diff. Pick the likelier duplicate. The server never sees the model and never holds an API key. It files a request, and the host decides whether the model answers and what the server gets back.
A server can also ask you a question; the spec calls that elicitation. Which of these three flights did you mean? The question arrives on the same wire its results do and lands in the agent's UI, not in some popup the server owns.
Both features would be alarming as direct lines. As hops through the middle they are just more traffic, subject to the same veto as everything else.
06You are a boundary too
One connection is missing from every machine so far: the one to your money. When a side effect matters, the agent doesn't get to decide alone. The request parks, and the question comes to you.
Deny it and notice what happened: nothing. No half-booked flight, no pending charge to unwind. The request never crossed the line, because waiting for you is a first-class state, not a failure.
Approval is not a courtesy dialog bolted on top. It is the same mechanism as every other boundary in this article: a hop the agent refuses to make until someone with authority says yes. The authority happens to be you.
07The map
Each section above wrote a rule. Here they are together.
Memorize the table and you've memorized the architecture. One process holds the connections. Servers, apps, even the model all go through it. That one rule is most of MCP.
08What this costs
Fairness requires the other column. A single mandatory middle is a bottleneck: every byte pays the toll, every hop adds latency, and the agent sees everything, which makes it the one component you must trust completely. Centralizing access also centralizes failure; when the host is down, all of it is down.
MCP takes that trade with open eyes. One place to log, one place to revoke, one place to say no. Distributed trust sounds nicer until you try to audit it.
09Keep reading
- The MCP specificationwhere every rule above actually lives
- Samplingthe server-asks-the-model flow from section 05
- SEP-1865 · MCP Appsthe sandbox, and which tools an app may request
- The MCP roadmapwhere the boundaries go next
fin · thanks for clicking