Introduction

MCP's architecture is built on three distinct roles: Host, Client, and Server. Understanding how these roles relate to each other is fundamental to working with MCP, whether you are building servers, integrating clients, or configuring hosts. The architecture follows a clear hierarchy: one host manages multiple clients, and each client maintains a one-to-one connection with exactly one server.

Key Concepts

  • Host: The application that runs the LLM and provides the user interface. Examples include Claude Desktop, Cursor, VS Code with GitHub Copilot, and custom AI applications. The host orchestrates everything — it creates clients, manages connections, and decides which tools the LLM can access.
  • Client: A protocol-level component that maintains a stateful, one-to-one connection with a single MCP server. The host creates one client for each server it wants to connect to. The client handles the JSON-RPC communication, capability negotiation, and message routing.
  • Server: A program that exposes capabilities to the LLM through the MCP protocol. Servers can provide tools (executable functions), resources (data sources), and prompts (reusable templates). Each server typically focuses on one domain — file system operations, database queries, or API access.
  • Capability Negotiation: When a client connects to a server, they exchange capability declarations. The server announces which features it supports (tools, resources, prompts, subscriptions), and the client announces its features (sampling, roots). This ensures both sides know what the other can do.

Real World Context

When you open Claude Desktop and configure it to use the filesystem and GitHub MCP servers, here is what happens behind the scenes: Claude Desktop (the host) creates two clients. Client A connects to the filesystem server. Client B connects to the GitHub server. Each client negotiates capabilities with its server independently. When the LLM needs to read a file, the host routes the request through Client A. When it needs to create a pull request, the request goes through Client B. The host merges the tool lists from both servers and presents them to the LLM as a unified set of available actions.

Deep Dive

The Host-Client-Server Hierarchy

The relationship between hosts, clients, and servers follows a strict pattern:

text
┌─────────────────────────────────────┐
│           HOST                      │
│   (Claude Desktop, Cursor, etc.)    │
│                                     │
│   ┌──────────┐    ┌──────────┐      │
│   │ Client A │    │ Client B │      │
│   └────┬─────┘    └────┬─────┘      │
└────────┼───────────────┼────────────┘
         │               │
    1:1 connection   1:1 connection
         │               │
   ┌─────▼─────┐   ┌─────▼─────┐
   │ Server A  │   │ Server B  │
   │ (GitHub)  │   │ (Postgres)│
   └───────────┘   └───────────┘

Key rules of this hierarchy:

  • One host can create multiple clients.
  • Each client connects to exactly one server.
  • A single server can accept connections from multiple clients (from the same or different hosts).
  • The host is responsible for merging capabilities from all connected servers.

What Each Role Does

The host is the orchestrator. It manages the LLM, creates clients, routes tool calls to the correct client, enforces security policies, and handles user consent for sensitive operations.

text
Host Responsibilities:
├── Run the LLM
├── Create and manage MCP clients
├── Merge tool lists from all servers
├── Route tool calls to the correct client
├── Enforce security policies
└── Handle user consent for sensitive operations

The client is the protocol handler. It manages the connection lifecycle with a single server — initialization, capability negotiation, request/response routing, and shutdown.

The server is the capability provider. It exposes tools, resources, and prompts through the MCP protocol and executes tool calls when requested.

Capability Negotiation

When a client first connects to a server, they exchange capability declarations. This is how each side learns what the other supports:

json
{
  "serverCapabilities": {
    "tools": { "listChanged": true },
    "resources": { "subscribe": true, "listChanged": true },
    "prompts": { "listChanged": true },
    "logging": {}
  },
  "clientCapabilities": {
    "roots": { "listChanged": true },
    "sampling": {}
  }
}

The listChanged flag indicates that the server can notify the client when its list of tools, resources, or prompts changes dynamically. The subscribe flag for resources means the client can subscribe to updates on specific resources.

Capability negotiation ensures that clients do not try to use features a server does not support, and servers do not send notifications a client cannot handle.

Common Pitfalls

  1. Confusing Host and Client — The host is the application the user interacts with (Claude Desktop, Cursor). The client is an internal protocol component that the host creates to manage a single server connection. Users configure hosts; developers build clients (or use SDKs that provide them).
  2. Assuming one client connects to multiple servers — Each MCP client connects to exactly one server. If a host needs three servers, it creates three clients. This one-to-one relationship simplifies protocol handling and error isolation.
  3. Forgetting capability negotiation — Not all servers support all features. A server might expose tools but not resources. Always check the negotiated capabilities before trying to use a feature.

Best Practices

  1. Design servers around a single domain — A server that handles both file system operations and database queries is harder to maintain and configure. Build focused servers and let the host compose them.
  2. Declare capabilities accurately — Only advertise capabilities your server actually implements. Declaring support for resource subscriptions without implementing them will cause client errors.
  3. Handle multiple client connections gracefully — Your server may receive connections from multiple hosts simultaneously. Ensure your server handles concurrent clients without state conflicts.

Summary

  • MCP uses a three-tier architecture: Host (runs the LLM), Client (one-to-one protocol connection), and Server (exposes capabilities).
  • One host creates multiple clients, each connecting to exactly one server.
  • Capability negotiation during connection ensures both sides know what features are available.
  • Hosts merge tool lists from all connected servers and route calls to the correct client.

Code Examples

json
{
  "mcpServers": {
    "filesystem": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-filesystem", "/Users/me/projects"]
    },
    "github": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-github"],
      "env": {
        "GITHUB_PERSONAL_ACCESS_TOKEN": "ghp_xxx"
      }
    }
  }
}
✓ Completed