MCP Architecture
Three components, one protocol. Understand how Hosts, Clients, and Servers form the MCP communication layer.
The Three Components
MCP has three components that work together:
The AI application the user interacts with — like Claude Desktop, Claude Code, or a custom app built with the Anthropic SDK. The Host initiates MCP connections and decides which servers to connect to. It contains one or more MCP Clients.
Role: Receives user input, decides when tools are needed, orchestrates the overall interaction.
A protocol bridge inside the Host that maintains a 1:1 connection with a single MCP Server. Each Client handles negotiation, capability exchange, and message routing for its server.
Role: Translates between the Host's internal format and the MCP protocol. One Client per Server connection.
A lightweight program that exposes tools, resources, or prompts to the AI through the MCP protocol. Servers are where the business logic lives — reading files, querying databases, calling APIs.
Role: Receives tool calls from the Client, executes them, and returns results.
Data Flow — Step by Step
Here is the complete request lifecycle when a user asks a question that requires external data:
Why JSON-RPC?
MCP uses JSON-RPC instead of REST, GraphQL, or gRPC. Here is why:
REST maps operations to URLs and HTTP verbs (GET /tools, POST /tools/call). JSON-RPC uses a single channel with method names ({"method": "tools/call"}). This works over any transport — stdio pipes, WebSockets, HTTP — without needing URL routing.
Local MCP servers communicate through stdin/stdout — no network stack needed. REST requires an HTTP server, a port, and URL parsing. JSON-RPC is just JSON messages on a stream, making local servers trivially simple to build.
JSON-RPC supports both request/response AND notifications (one-way messages). This lets servers send progress updates, log events, or signal resource changes back to the client without being asked. REST is strictly request/response.
Transport Layer
MCP supports two transport mechanisms. The transport is separate from the protocol — the same server code works with either:
Claude launches the server as a child process. Messages flow through stdin/stdout. Zero configuration — no ports, no TLS, no firewall rules. Ideal for personal tools and local development.
The server runs on a remote machine and exposes an HTTP endpoint. Uses Server-Sent Events (SSE) for streaming responses. Required for team-shared servers, cloud deployments, and multi-user setups.
Capability Negotiation
Before any tools are called or resources accessed, the Client and Server perform an initialize handshake. This is the first message in every MCP connection — nothing else can happen until it completes.
initialize request
The Client tells the Server which protocol version it supports and what capabilities it has (like roots for filesystem access or sampling for LLM inference). This lets the Server know what it can ask the Client to do.
The Server declares exactly what it supports: tools (callable functions), resources (data the AI can read), and prompts (reusable prompt templates). It also confirms the protocol version it will use. If the versions are incompatible, the connection fails gracefully.
initialized notification
Once the Client processes the Server's capabilities, it sends an initialized notification (a one-way message, no response expected). Only after this notification can the Client begin calling tools or reading resources.
Here is what the initialize handshake looks like on the wire:
{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": "2025-03-26",
"capabilities": {
"roots": { "listChanged": true },
"sampling": {}
},
"clientInfo": {
"name": "Claude Desktop",
"version": "1.5.0"
}
}
}
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"protocolVersion": "2025-03-26",
"capabilities": {
"tools": { "listChanged": true },
"resources": { "subscribe": true },
"prompts": { "listChanged": true }
},
"serverInfo": {
"name": "filesystem-server",
"version": "0.3.1"
}
}
}
Notice the Server declares it supports tools, resources, and prompts. The listChanged flag means the Server can notify the Client if its available tools change at runtime — enabling dynamic tool discovery without reconnecting.
What Happens When Something Goes Wrong
The protocol has built-in error handling for every failure mode:
initialize, client and server exchange protocol versions. If incompatible, the connection is rejected gracefully with a clear message.
Architecture in Practice: A Real Example
Theory makes more sense with a concrete scenario. Here is how Claude Desktop looks when connected to three MCP servers — a common real-world setup for a developer:
Contains 3 MCP Clients — one per server connection
stdio transport
Tools: read_file, write_file, list_directory, search_files
stdio transport
Tools: create_issue, search_repos, create_pr, list_commits
Streamable HTTP
Tools: query, list_tables
Resources: schema://tables
Key things to notice in this architecture:
search_files, the Host routes the call to Client A, which forwards it to the Filesystem Server. The LLM does not need to know which server provides which tool.
resources capability (exposing database schemas as readable resources). Clients A and B did not — their servers only expose tools. The Host adapts to each server's capabilities.
This is the power of MCP's architecture — the protocol handles all the complexity of multiple connections, different transports, and varying capabilities. You configure which servers to connect to, and the Host handles the rest.