Architecture: Hosts, Clients, and Servers
- Distinguish the three MCP roles — host, client, and server — and understand what each one is responsible for
- Describe the three-phase MCP connection lifecycle: initialization, capability discovery, and operation
- Explain how JSON-RPC 2.0 underlies all MCP communication and what requests vs. notifications mean for server authors
Three Roles, One Protocol
Every MCP interaction involves three distinct roles. Confusing them is a common source of confusion for new MCP developers, so it is worth being precise from the start.
When you build an MCP server, you are writing code that fulfills the server role. You do not write client or host code — those are handled by Claude Desktop, Claude Code, or whichever AI application the user is working with.
The Communication Protocol: JSON-RPC 2.0
All MCP communication uses JSON-RPC 2.0 — a lightweight remote procedure call protocol that encodes requests and responses as JSON objects.
A JSON-RPC request looks like this:
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "get_weather",
"arguments": { "city": "London" }
}
}
And the corresponding response:
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"content": [
{ "type": "text", "text": "London: 14°C, partly cloudy" }
]
}
}
You will rarely write raw JSON-RPC yourself — the TypeScript and Python SDKs handle serialization and deserialization. But understanding that this is the underlying format helps when you need to debug a misbehaving server or read the MCP specification directly.
MCP also uses notifications — one-way messages with no expected response — for events like progress updates and resource change alerts.
The Connection Lifecycle
When a host connects to your server, it follows a precise three-phase lifecycle:
Phase 1: Initialization
The client sends an initialize request containing its protocol version and capabilities. Your server responds with its own protocol version, capabilities, and identifying information (name, version). If the versions are incompatible, the connection is rejected.
Client → Server: initialize { protocolVersion: "2024-11-05", capabilities: {...} }
Server → Client: { protocolVersion: "2024-11-05", capabilities: {...}, serverInfo: {...} }
Client → Server: notifications/initialized
Phase 2: Capability Discovery
After initialization, the client queries what your server offers. It may call tools/list, resources/list, and/or prompts/list to discover available capabilities. Your server responds with the full list of what it exposes.
This is how the AI host learns what tools are available to offer the model. The host does not hardcode this knowledge — it discovers it fresh on each connection.
Phase 3: Operation
The connection is now live. The client calls your server's tools, reads resources, and retrieves prompts as needed. Your server handles each request and returns results. This phase continues until the connection is closed.
Request vs. Notification
MCP messages fall into two categories:
- Requests — have an
idfield; the sender expects a response.tools/call,resources/read, andinitializeare all requests. - Notifications — have no
idfield; the sender does not expect a response. Thenotifications/initializedconfirmation and progress updates are notifications.
As a server author, you handle requests and may optionally send notifications (for example, notifying the client when a resource changes).
Capability Negotiation
Not every host supports every MCP feature, and not every server implements every capability. The initialization handshake allows both sides to declare what they support. Examples of capabilities a server might declare:
tools— the server exposes callable toolsresources— the server exposes readable resourcesresources.subscribe— the server supports resource change subscriptionsprompts— the server exposes prompt templateslogging— the server can send structured log messages to the client
The SDKs handle capability negotiation automatically. When you register a tool on your server, the SDK knows to advertise the tools capability during initialization. You do not need to manage this manually.
One Server, Multiple Clients
A single MCP server can serve multiple clients simultaneously. Each client gets its own independent session with its own initialization and lifecycle. This matters more for HTTP transport (covered in a later lesson) where multiple users might connect to the same remote server. For stdio servers running locally, there is typically one client per server process.
What This Means for You as a Server Author
The architecture shapes how you write server code:
- Your server is reactive — it responds to requests, never initiates them. You do not "push" data to the client; you wait for the client to ask.
- Your server declares its capabilities at startup. If you want to add a new tool, you add it to the server and reconnect — the host re-discovers it automatically.
- Your server is stateless per request by default. Each tool call is independent. If you need to maintain state across calls (authentication tokens, cached data), you manage that within your server's process memory.
- The JSON-RPC protocol means your server's logic is entirely separate from the transport. The same tool handler works over stdio or HTTP — only the transport layer changes.
- MCP defines three roles: the host (the AI app like Claude Desktop), the client (a connection manager inside the host), and the server (the program you build)
- All MCP communication uses JSON-RPC 2.0 — requests expect a response and carry an id; notifications are one-way with no id
- Every connection follows three phases: initialization (version + capability handshake), capability discovery (tools/list, resources/list), and operation (live request handling)
- As a server author, your server is purely reactive — it responds to client requests and never initiates communication
- The SDKs handle JSON-RPC serialization and capability negotiation automatically; you focus on writing tool handlers and resource definitions