Chapter 2 of 9 · Model Context Protocol
Three roles, one connection
MCP isn't peer-to-peer. it's a host that spawns one client per server.
Before we define anything, try this. You open Cursor and ask the AI to grep your codebase. Three things have to happen on the wire — and most readers can't name which piece is which. The gap between your guess and the truth is the rest of this chapter.
"MCP is like an API." It isn't.#
Almost every reader walks in with the same wrong mental model: MCP is just an API the LLM calls. It's the closest thing they have to an analogy, so it sticks. The trouble is: APIs are stateless. You hit an endpoint, the server answers, the conversation is over. Two requests in a row don't know about each other unless you carry the state yourself.
MCP is the opposite. An MCP exchange is a stateful session between three named roles — host, client, and server — that lasts as long as your editor is open. The session begins with a handshake, the two sides agree on a protocol version and what each is capable of, and from then on every message is interpreted in the context of that agreement. Drop the connection and you've lost the agreement; you can't pick up where you left off without re-handshaking.
The host is what the human looks at.#
A host is the application sitting in front of a human user — Claude Desktop, VS Code, Cursor, Zed, Replit, Codeium, Sourcegraph. It owns the LLM call. It owns the chat UI. It owns the user's trust posture: when a tool wants to write to disk, it's the host that asks "are you sure?". Whatever runs the model and renders the conversation — that's the host.
The reader's first wrong guess almost always lives here: people suspect the LLM itself is the host. It isn't. The LLM is a callable resource the host owns; from MCP's point of view, the LLM sits on the host's side of the wall, not on the wire. MCP doesn't define how the host talks to the model. That's the host's business. MCP only defines how the host talks to everything else.
The server is a separate process or service.#
A server is anything outside the host that exposes a capability the host wants. A filesystem server is a small Node process that knows how to read and write files. A postgres server speaks SQL to a local database. A sentry server is a remote service that knows how to fetch error events from your account. Local or remote, in-house or third-party — from the host's view it's just "a server".
Servers don't run inside the host. That's deliberate. A server is a different process, possibly on a different machine, often written by a different team. It crashes independently. It's upgraded independently. It can be replaced without touching the host — which is how Anthropic's marketing line "USB-C for AI" earns its keep: change the peripheral, the device doesn't care.
The client is the connection, living inside the host.#
Here's the piece readers most often miss. The client is not a separate program; it's not the LLM; it's not a network library. The client is a small connection-manager object the host instantiates — one per server. If the host has three servers configured, the host spins up three clients on launch. Each client owns one connection, one session, one negotiated set of capabilities, one live tool list. Two servers, two clients. Five servers, five clients.
This is the load-bearing claim of the chapter and it's worth saying once more: one client per server, exactly. Not one shared client that fans out. Not a pool. A dedicated, in-memory connection manager per configured server, all of them living inside the same host process.
Why one client per server?#
Three reasons, all paid off in later chapters. First, state isolation: each connection negotiates its own protocol version and capabilities at startup. The filesystem server might support prompts; the Postgres one might not. Mixing those into a single client would force the host to track per-server flags everywhere — the per-client object is that state.
Second, capability negotiation. We'll dissect the handshake in chapter 4; for now: each session begins with the client and server telling each other what they can do, and the result of that exchange becomes the rules for the rest of the session. Sharing one client across two servers would mean two different rule-sets fighting over the same object.
Third, transport isolation. One server might run locally over a Unix pipe; another might run remotely over HTTP. Pooling them would force one side to know about the other's wire format. Keeping them separate lets the host treat both the same way: here is a client, ask it things.
The widget below makes this concrete. There's a host card with no servers attached. Tap a server in the palette: a client badge appears inside the host, an arrow connects it to the server, and the status badge updates. Toggle the server between local and remote — notice the host doesn't move. Transport is the server's concern, not the host's.
Three servers on the canvas, three clients inside the host, three live connections. Mark that picture: it's the picture every chapter from here forward draws on top of. When chapter 5 introduces tools, resources, and prompts, those primitives all live on one of these arrows. When chapter 7 chooses a transport, it's choosing what one of these arrows is made of.
What happens on a single connection, in time.#
We've drawn the topology — where the pieces are. Now zoom in on one of those arrows and ask: what does it carry, and when? Every client–server connection moves through five ticks. Step through them.
Tick three is the load-bearing one and it's where chapter 4 will live. What gets agreed during initialize is what the rest of the session is allowed to do. Skip the handshake and nothing else works; corrupt the handshake and chapter 9's attack classes get their opening. For now, it's enough to see that exchange sits on top of initialize — the order is not optional.
The mental model fits in a config file.#
If the topology builder is the picture, this is the same picture in text. Most hosts read a JSON config keyed by server name; each block is one server, and on launch the host instantiates one client per block. Two entries means two clients. The shape is the model:
{
"mcpServers": {
"filesystem": { "command": "npx", "args": ["-y", "@modelcontextprotocol/server-filesystem", "/Users/me/code"] },
"postgres": { "command": "npx", "args": ["-y", "@modelcontextprotocol/server-postgres", "postgres://localhost/dev"] }
}
}Two servers configured, two clients spawned at startup, two independent sessions live for as long as the host is open. The config doesn't mention transports because the host figures that out from the entry's shape — a command means a local process over stdio; a url would mean a remote service over HTTP. Same JSON shape; different wire on the other side of the client.
Check your read.#
One question to lock the topology in. Predict, then reveal.
We've drawn the topology. Three roles — host, client, server — and one connection between each client–server pair. Carry that picture forward; chapter 3 starts on top of it.
Now: what flows across each of those connections, and in what shape?
chapter 3 · JSON-RPC, the wire that carries it