Frontend Labs
⋯

Chapter 57 — How MCP works: hosts, clients, servers, and primitives

In one sentence

An MCP host application creates a client for each server it connects to, and the server offers tools, resources, and prompts over JSON-RPC 2.0.

The three roles

Get these right and the rest is easy — the naming trips everyone up at first.

   +--------------------------------------------------------------+
   |  HOST         The AI application you're using.                |
   |               (an IDE, a chat app, your own agent program)    |
   |               It talks to the LLM and decides what to do.     |
   |                                                               |
   |   +--------------------+      +--------------------+         |
   |   |  CLIENT            |      |  CLIENT            |          |
   |   |  one per server,   |      |  one per server    |          |
   |   |  lives inside      |      |                    |          |
   |   |  the host          |      |                    |          |
   |   +---------|----------+      +---------|----------+          |
   +-------------|-------------------------- |---------------------+
                 |                           |
          JSON-RPC 2.0                 JSON-RPC 2.0
                 |                           |
       +---------v---------+       +---------v---------+
       |  SERVER           |       |  SERVER           |
       |  "GitHub tools"   |       |  "our database"   |
       |  a separate       |       |                   |
       |  process/service  |       |                   |
       +-------------------+       +-------------------+
  • Host — the application. It owns the LLM conversation and the user relationship.
  • Client — a connector inside the host, one per server. It speaks the protocol.
  • Server — the thing exposing capabilities. Usually a small separate program.

The critical point: the server never talks to the LLM. It exposes capabilities; the host decides what to show the model and what to execute. That separation is why MCP can be a security boundary.

What a server offers (server features)

Three kinds of thing, and the distinction is about who is in control:

Primitive Controlled by What it is Example
Tools the model Functions the model can decide to call create_issue, run_query
Resources the application Data the host can read and put in context a file's contents, a database schema, a wiki page
Prompts the user Templated messages/workflows the user can invoke a /review-pr command, a "summarise this" template

That control axis is the clearest way to remember them: model-controlled, app-controlled, user-controlled.

What a client offers (client features)

The server can also ask things of the client:

  • Elicitation — the server requests additional information from the user mid-operation. ("Which repository did you mean?")

Extensions

Beyond the core, MCP defines opt-in extensions, negotiated at initialization. Notable ones include:

  • Tasks — asynchronous execution of long-running operations, with polling and durable handles (so a 20-minute job doesn't hold a connection open)
  • Skills over MCP — structured instructions for agent workflows
  • MCP Apps — interactive UI elements (charts, forms) rendered inline

The transports

   stdio        The server runs as a LOCAL SUBPROCESS.
                Messages go over standard input/output.
                -> local tools: filesystem, git, a local database
                -> simple, fast, no network, inherits your permissions

   HTTP         The server is a remote service with a single MCP endpoint.
                Optional Server-Sent Events for server-to-client streaming
                (Chapter 43 - the same SSE you already know).
                -> shared/hosted servers, SaaS integrations

The messages

MCP uses JSON-RPC 2.0. A tool call looks like this:

   --> {"jsonrpc": "2.0", "id": 1, "method": "tools/list"}

   <-- {"jsonrpc": "2.0", "id": 1, "result": {"tools": [
         {"name": "get_order",
          "description": "Look up an order by ID.",
          "inputSchema": {"type": "object",
                          "properties": {"order_id": {"type": "string"}},
                          "required": ["order_id"]}}
       ]}}

   --> {"jsonrpc": "2.0", "id": 2, "method": "tools/call",
        "params": {"name": "get_order", "arguments": {"order_id": "ORD-9931"}}}

   <-- {"jsonrpc": "2.0", "id": 2, "result": {"content": [
         {"type": "text", "text": "{\"status\": \"shipped\"}"}]}}

Notice inputSchema — it's the same JSON Schema you wrote by hand in Chapter 52. MCP just standardises how it's delivered.

The stateless change (spec 2026-07-28)

Earlier revisions kept a session between client and server (with an Mcp-Session-Id header). The current specification made the protocol core stateless:

   BEFORE:  client and server maintained a session; requests belonged to it
   NOW:     each request is SELF-CONTAINED.
           Protocol version, client identity, and capabilities travel
           with every request, typically in `_meta`.
           Servers must not rely on earlier requests for context.

Why it matters: any server instance behind an ordinary load balancer can answer any request. It scales like a normal web service instead of needing sticky sessions — the same lesson as stateless HTTP.

Practice (20 min) — use an MCP server before building one

The fastest way to understand MCP is to connect an existing server.

# The official SDKs and reference servers:
#   github.com/modelcontextprotocol/servers
# Many are runnable with no install via npx or uvx.

# Example: run a filesystem server directly and speak JSON-RPC to it by hand.
npx -y @modelcontextprotocol/server-filesystem /tmp

It will wait on stdin. Paste a request and press Enter:

{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{"_meta":{"io.modelcontextprotocol/protocolVersion":"2026-07-28","io.modelcontextprotocol/clientInfo":{"name":"manual-test","version":"0.1.0"}}}}

You will see the tool list come back as JSON. That is the entire protocol, visible. Try tools/call next.

Then connect a server to a real host (Claude Desktop, an MCP-capable IDE, or your own client) via its MCP configuration, and watch the tools appear in the UI.

Common confusions

  • "Is the server the LLM?" No. The server has no model. It exposes capabilities; the host owns the model.
  • "Client and server sound backwards." The host application contains clients; the tool provider is the server — same as LSP, and the opposite of what people often assume.
  • "Tools vs resources?" Tools are actions the model chooses; resources are data the application supplies. If the model decides, it's a tool.
  • "Do I need HTTP?" For local tools, stdio is simpler and safer.

Check yourself

  1. Name the three roles and which one talks to the LLM.
  2. What are the three server primitives, and who controls each?
  3. What message format does MCP use?
  4. What changed in the 2026-07-28 revision, and why does it matter?

Further reading

  • Docs: modelcontextprotocol.io/specification — read the Architecture and Server Features pages. Surprisingly readable.
  • Docs: the "Core concepts" tutorials on the same site.
  • Code: github.com/modelcontextprotocol/servers — read a small reference server (the filesystem or fetch one) start to finish.

Generated from real-life-ai-example-v1.md · 8 Oct 2026 16:36