MCP: Hosts, Clients, Servers

P3
Deep Dive · Protocols & Interop

The Model Context Protocol: hosts, clients, servers, and three primitives.

MCP (introduced by Anthropic in November 2024, governed by an open specification) standardises the edge between an agent and the tools, data, and prompts it uses. This essay covers its participant model, its three server primitives — resources, tools, prompts — its JSON-RPC message layer, and its transports, with small on-the-wire examples.

STEP 1

The participant model: host, client, server.

MCP defines three roles. Getting them straight is the whole mental model:

  • Host. The application the user interacts with and that embeds the model — an IDE assistant, a desktop chat app, an agent runtime. The host manages the model, enforces user consent, and coordinates one or more clients.
  • Client. A connector inside the host, with a strict one-to-one relationship to a single server. If a host connects to three servers, it runs three clients. The client speaks the protocol and isolates that server's session.
  • Server. A separate program that exposes capabilities — tools, resources, prompts — over the protocol. A server wraps one system (a filesystem, a database, a SaaS API) and is reusable by any MCP-capable host.
┌─ Host (the agent app) ─────────────────────┐
│  model + consent + orchestration            │
│   ├── Client A ───stdio───>  Server: files  │
│   ├── Client B ───HTTP────>  Server: github │
│   └── Client C ───HTTP────>  Server: db     │
└─────────────────────────────────────────────┘
   one client  ⇄  one server  (1:1, isolated)

The 1:1 client-to-server isolation is deliberate: it bounds trust per connection. A malicious or buggy server cannot see another server's session, and the host decides which servers a model context is exposed to. This is the protocol expression of the M+N argument from the interop-problem essay — wrap each system once as a server, teach the host the client once.

STEP 2

Three server primitives: resources, tools, prompts.

An MCP server can offer three kinds of capability. The distinction is about who is in control:

Resources are application-controlled context: file-like data the host can read and place into the model's context — a file's contents, a database row, an API response. Each resource has a URI (for example file:///repo/README.md or a custom scheme). Resources are read-oriented and side-effect-free by intent; the host decides when to surface them.

Tools are model-controlled actions: functions the model may choose to invoke, each described with a JSON Schema input (the same substrate as the tool-calling-standards essay). Tools can have side effects — write a file, open a PR, run a query — so MCP expects host-mediated user consent before execution.

Prompts are user-controlled templates: reusable, parameterised prompt/workflow snippets a server publishes, which the host can surface as commands ("summarise this PR") and expand with arguments. They let a server ship expertise, not just raw capability.

Mnemonic for the control axis: resources = the application chooses what context to load; tools = the model chooses what action to take; prompts = the user chooses which workflow to run. Same protocol, three intents.

STEP 3

The wire: JSON-RPC 2.0, with a lifecycle.

MCP messages are JSON-RPC 2.0: requests with an id expecting a response, results, errors, and one-way notifications. Every session begins with an initialize handshake in which both sides exchange protocol version and a capabilities object — this is the version negotiation that lets the M+N graph evolve without a flag day.

# 1. Client opens the session and declares itself
{"jsonrpc":"2.0","id":1,"method":"initialize",
 "params":{"protocolVersion":"2025-06-18",
   "capabilities":{"roots":{},"sampling":{}},
   "clientInfo":{"name":"my-host","version":"1.0"}}}

# 2. Server responds with its own capabilities
{"jsonrpc":"2.0","id":1,"result":{
   "protocolVersion":"2025-06-18",
   "capabilities":{"tools":{"listChanged":true},
                    "resources":{},"prompts":{}},
   "serverInfo":{"name":"github","version":"2.1"}}}

# 3. Client confirms it is ready (a notification: no id)
{"jsonrpc":"2.0","method":"notifications/initialized"}

After the handshake the client uses listing methods to discover what the server offers, then invocation methods to use it:

tools/list        -> [{name, description, inputSchema}, …]
tools/call        -> run a tool by name with arguments
resources/list    -> [{uri, name, mimeType}, …]
resources/read    -> fetch a resource's contents by uri
prompts/list      -> [{name, arguments}, …]
prompts/get       -> expand a prompt template with args
notifications/*   -> listChanged, progress, cancelled, …

Capability discovery is therefore runtime, not build-time: a host learns a server's tools by calling tools/list, and a listChanged notification lets the server tell the host the set has changed. Capability discovery has its own dedicated essay; the point here is that MCP bakes it into the lifecycle.

STEP 4

Transports, and where security enters.

MCP separates the message format (JSON-RPC) from the transport that carries it. Two transports are defined by the specification:

  • stdio. The host launches the server as a subprocess and exchanges JSON-RPC over its standard input/output. Ideal for local tools (a filesystem or Git server on your machine): no network surface, lifecycle tied to the process.
  • Streamable HTTP. The server is a remote HTTP endpoint; the client POSTs requests and the server may stream responses and server-initiated messages (using server-sent events as the streaming mechanism). This is the path for hosted, multi-client servers and is where authentication — the spec aligns remote auth with OAuth 2-style authorization — and transport security live.

One more capability is worth naming because it inverts the usual direction: sampling. A server can ask the host to run a model completion on its behalf (declared in the initialize capabilities). This lets a server be "agentic" without shipping its own model, but it is also a control-flow inversion the host must mediate with user consent.

MCP standardises the channel, not trust. A connected server can return content that the model will read — a prompt-injection surface — and a tool call can have real side effects. The protocol's job is to make consent points explicit (the host gates tool execution and sampling); deciding what to grant is yours. The threat model, capability scoping, and provenance defenses are covered in the Safety & Agentic Security deep-dives. Treat "the server speaks MCP" as describing shape, never authorization.

The throughline: MCP is a participant model (host/client/server) plus three intent-typed primitives (resources/tools/prompts) plus a negotiated JSON-RPC lifecycle over a pluggable transport (stdio or streamable HTTP). That is the entire architecture; the rest of this track examines how its pieces — structured I/O, discovery, agent-to-agent extension — generalise.