Lesson 2 of 12Track 3

MCP fundamentals

MCP architecture: hosts, clients, servers

The three roles in MCP.

Video lesson ~10 min

Video coming soon

Three roles, clean boundaries

MCP has exactly three roles. Understanding them precisely is the difference between "MCP feels like magic" and "MCP feels like sockets." Let's make it sockets.

  • Host. The user-facing application. In our world, that's the agent (Claude Code, your custom agent, an IDE plugin).
  • Client. A library inside the host that speaks the MCP wire protocol. One client per server connection.
  • Server. A separate process or hosted endpoint that implements MCP and exposes capabilities (tools, resources, prompts).

A single host can have many clients, each talking to a different server. Each client has exactly one server.

                  +--------------------+
                  |      HOST          |
                  |   (the agent)      |
                  |                    |
                  |  ┌──────────────┐  |
                  |  │   Client A   │──┼───┐ stdio
                  |  └──────────────┘  |   v
                  |                    |  [Server A: filesystem]
                  |  ┌──────────────┐  |
                  |  │   Client B   │──┼───┐ HTTP
                  |  └──────────────┘  |   v
                  |                    |  [Server B: GitHub API]
                  +--------------------+

Each Client/Server pair is its own connection. They don't share state. The host is what knows about all of them.

What each role does

Host

The host:

  • Decides which servers to connect to (configuration).
  • Spawns or connects to those servers and creates a client per connection.
  • Aggregates the tools/resources/prompts each server exposes into something the agent can reason about.
  • Translates between the agent's internal representation (e.g., a tool call from a model response) and MCP messages.

In practice the host code is small but important. It handles startup, shutdown, error propagation, and the multiplexing of tool-call traffic to the right server.

Client

The client:

  • Speaks the MCP wire protocol with one server.
  • Handles capability negotiation on connect.
  • Sends requests and receives responses.
  • Detects disconnects and surfaces errors to the host.

You usually don't write a client from scratch; the MCP SDK provides one for your language. You configure it (transport, server endpoint) and call its methods (list_tools, call_tool, etc).

Server

The server:

  • Declares its capabilities at startup (which primitives it supports).
  • Lists the specific tools, resources, and prompts it offers.
  • Executes calls against those primitives.
  • Returns structured responses or errors.

Servers are usually small focused processes: "the filesystem server," "the GitHub server," "the SQL server." A server that does ten unrelated things is a smell; split it.

Capability negotiation

When a client connects to a server, the very first round of messages establishes what each side supports. The handshake answers questions like:

  • Does the server support tools? Resources? Prompts?
  • Does the server support notifications (server-pushed events)?
  • What protocol version is each side speaking?
client: "hello, I support tools, resources, and version 1.0"
server: "hello, I support tools and version 1.0"
client: "OK, I'll only ask about tools"

This is what makes the protocol forward-compatible. New capabilities can be added without breaking old clients/servers; both sides only use what the other one says it supports.

The three primitives in a sentence

Tools

Functions the agent can call with structured arguments. Each has a name, description, input schema, and output schema. This is the most-used primitive and the one you've effectively been using all of Track 1 and Track 2 for; MCP just standardizes the shape.

Resources

Read-only data the agent (or a human) can fetch. Identified by URIs (file:///path, db://table/row, github://owner/repo/issues/42). Resources are for content, not actions: "give me the user manual," "give me this customer's account record."

Prompts

Named, parameterized prompt templates that the server provides. The host can list them and present to the user; the user picks one and supplies parameters; the server returns a structured prompt the agent uses.

These are less common in agent-to-agent contexts and most useful when the host has a UI that surfaces them to humans. The next module's lesson 2 covers them in more detail.

Why a separate process?

It might seem heavy to put each tool registry in its own process. There are real reasons:

Isolation

A tool that crashes doesn't take the agent with it. A tool that leaks memory doesn't slow the agent's main loop. The OS process boundary is the strongest isolation you get for free.

Language independence

Want to use a Rust tool from a Python agent? Easy: the Rust tool runs in its own process, the Python agent has a Python client, both speak MCP wire protocol. No FFI, no bindings.

Distribution

A server can be a vendor-hosted endpoint thousands of miles away, accessed over HTTPS. Or it can be a local subprocess speaking stdio. The host doesn't care; both look the same to the client.

Permissions

Each server runs with its own permissions. The filesystem server can be sandboxed differently than the database server. The host doesn't have to inherit the union of all tool permissions.

The downside is latency: each tool call is now an IPC round-trip. For most agent work this is fine because LLM latency dominates. For latency-critical hot paths, in-process tools are still appropriate.

Where the protocol lines up with Track 2

If you read Module 2 of Track 2, the host/client/server split should look familiar. The agent uses the same handoff-style boundaries: each MCP server is essentially a specialist with its own tools and capabilities, and the host is the orchestrator dispatching work to whichever server is appropriate.

The difference is that MCP servers are external and standardized. Track 2's specialists were Python objects in your process; MCP servers can be from any language, any vendor, any host. The mental model is the same; the runtime model is wider.

Think 'three sockets in a trench coat'

A useful mental anchor: MCP isn't a new computing paradigm. It's three TCP sockets in a trench coat: a connection, a protocol, and a vocabulary. If you've built a JSON-over-WebSocket service or a JSON-RPC server, you've already done 90% of what MCP does. The other 10% is the standardized vocabulary for the agent-tool relationship.

Configuring MCP in Claude Code (a quick example)

To make this less abstract, here's what the user-facing side of MCP looks like in Claude Code, the tool you're probably using to read this. Claude Code stores its server config in .mcp.json:

{
  "mcpServers": {
    "filesystem": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-filesystem", "/workspace"]
    },
    "github": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-github"],
      "env": {"GITHUB_TOKEN": "ghp_..."}
    }
  }
}

Two servers: filesystem (a stdio server spawned as a subprocess) and GitHub (also stdio, with an env var carrying the auth token). Once these are configured, Claude Code's host code creates a client per server, negotiates capabilities, and exposes the tools to the model. You as a user just see "filesystem tools available" and "github tools available."

Key takeaway

MCP has three roles: hosts (the agent app), clients (the in-host library that speaks MCP), and servers (the external processes or endpoints that expose capabilities). One host, many clients, one server per client. Capability negotiation on connect makes the protocol forward-compatible. The three primitives are tools, resources, and prompts. Process isolation gives you fault tolerance, language independence, and per-server permissions. The next lesson covers the actual transports the wire protocol runs over.

Done with this lesson?