Class 2: Anatomy of MCP
Duration: ~20 minutes | Level: Beginner | Prerequisites: Class 1: Why MCP Exists
Four Actors, One Protocol
An MCP interaction involves four distinct roles, and keeping them apart matters because "client" and "server" also name things at other layers.
The arrow between client and server carries JSON-RPC 2.0 messages: small JSON objects naming a method, its parameters, and an id the reply quotes back.
1. The AI Model
The model, Claude Opus 5, GPT-5, Gemini, or any other LLM, is the reasoning engine. It doesn't know about MCP directly: what it sees is a list of available tools, described in its context window, the text it can see while it answers. When the model needs a tool, it picks one, constructs the arguments, and produces a structured tool-call output. The host then handles the actual MCP communication.
2. The Host
The host is the application the user interacts with: Claude Desktop, Cursor, a custom agent you built, or any other AI-enabled application. The host is responsible for:
- Managing the conversation context
- Feeding tool definitions into the model
- Passing the model's tool calls to its MCP clients, which deliver them to the server that actually executes them
- Managing user permissions for sensitive operations
Users interact with the host and do not see the protocol.
3. The MCP Client
The client is the MCP-protocol component embedded inside the host. It:
- Establishes and maintains a connection to an MCP server
- Declares its own protocol version and capabilities, meaning the optional features it supports: once in an
initializehandshake through2025-11-25, and in every request it sends from2026-07-28 - Learns what the server offers by calling
server/discover, or by sending a request and handling the version error (see below) - Sends tool calls, resource reads, and prompt requests to its server
- Returns results to the host (which passes them to the model)
One host can contain multiple MCP clients, and each client maintains a connection to one MCP server.
4. The MCP Server
The server is what you build. It's a process that:
- Runs your business logic (querying a DB, calling an API, reading files)
- Exposes that logic as structured MCP primitives: Tools, Resources, and Prompts
- Handles requests from the client and returns results
The server is where you spend your implementation time; the model, the host and the client come from the AI application you're integrating with.
A Concrete Example: Database Query Tool
Suppose you build an MCP server that lets an AI query your PostgreSQL database.
What you build: An MCP server process that:
- Connects to PostgreSQL as a database user whose read access covers only the tables this tool needs
- Exposes a
run_querytool with asqlstring parameter - On a tool call: rejects anything that is not a single
SELECT, runs it with a row limit, and returns the results - Limits how often the tool can be called
The model writes that sql string, and any text that reaches the model can influence what it writes, so the database connection is where the limit belongs. The MCP tools specification requires servers to validate tool inputs, apply access controls and rate limit invocations, and OWASP LLM06:2025, Excessive Agency asks for read access only, without insert, update or delete.
What happens at runtime, with the user in Cursor and your server already connected:
Steps 5 and 8 are the only MCP messages here. Everything else happens inside the host or your server.
What the user sees: An accurate, data-backed answer without copy-pasting any query results into the chat.
Server Multiplicity: One Host, Many Servers
A single host can connect to several servers at once, and each server is a separate process with its own set of capabilities.
The model sees the tools from all four servers as one list. Routing is the host's job: it passes each call to the client connected to the server that exposes that tool. A server receives only the arguments of its own call, so it cannot read the conversation or see what another server holds, which the architecture specification states as a design principle.
Each server covers one system, so it can be reused by any host that speaks MCP.
Server Lifecycle
stdio is short for standard input and output, the two byte streams a process reads from and writes to. The two deployments differ in more than where the process runs:
| What you are choosing | Local stdio server | Remote HTTP server |
|---|---|---|
| Who starts the process | the client, as a subprocess | you, as a deployed service |
| How long it runs | across many unrelated requests, and not tied to one conversation | as long as the service is deployed, or for a single request on a serverless platform, where a process is created for one request and then discarded |
| Who can reach it | only the local user's client | many clients and many users, over the network |
| How it stops | the client closes the server's standard input, waits for it to exit, then terminates it | you redeploy it or scale it down |
The choice decides how the server authenticates its callers and what it costs to run. Class 5 works through both transports, the connections the messages travel over.
Statelessness: Why There Is No Session
Revision 2026-07-28 removed something most MCP material still takes for granted: the session.
What the protocol used to do. Through revision 2025-11-25, every connection had three phases.
Initialisation settled two questions once instead of on every message: which protocol version are we speaking, and which optional features does the other side support. notifications/initialized is a notification, a JSON-RPC message that does not carry an id and does not expect a reply. During operation, servers could also send requests back to the client.
What it does now. Settling those questions per connection makes the connection stateful. Such state is awkward behind a load balancer or on a serverless platform, where many identical copies of a server share the traffic and any request can go to any copy. So 2026-07-28 deleted the model. The specification's lifecycle page was replaced by one on versioning and compatibility, and the base protocol now opens with this:
The Model Context Protocol (MCP) is a stateless protocol: all the information needed to process a request is contained in the request itself. A server processes each request independently; no state should be inferred from previous requests, even those on the same connection or stream.
Protocol version and client capabilities travel in _meta, a reserved field any MCP message may carry. Both are required on every request, and io.modelcontextprotocol/clientInfo is recommended:
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "run_query",
"arguments": { "sql": "SELECT customer_id, SUM(amount) FROM orders GROUP BY 1" },
"_meta": {
"io.modelcontextprotocol/protocolVersion": "2026-07-28",
"io.modelcontextprotocol/clientCapabilities": { "elicitation": {} },
"io.modelcontextprotocol/clientInfo": { "name": "example-client", "version": "1.0.0" }
}
}
}
A server that does not implement the requested version answers that request with UnsupportedProtocolVersionError, code -32022, listing the versions it does support:
{
"jsonrpc": "2.0",
"id": 1,
"error": {
"code": -32022,
"message": "Unsupported protocol version",
"data": {
"supported": ["2025-11-25"],
"requested": "2026-07-28"
}
}
}
The client picks a version from data.supported and sends the call again. Any state that has to outlive a single request must be referenced by an explicit identifier the client passes back each time. The spec is blunt about the consequence: an open connection, a running stdio process included, is not a conversation and not a session.
Servers no longer send requests back to the client either. When a server needs an answer from the user, it returns resultType: "input_required" with the questions to ask and an opaque requestState. Only prompts/get, resources/read and tools/call may return that result.
The retry is a separate request with a new JSON-RPC id, carrying the answers in inputResponses and the same requestState. That state is the server's own record of where it stopped, and the client MUST NOT read or change it.
The same pattern carries the two older ways a server could reach the client: sampling, where the server asks the host's model to generate text, and roots, where it asks which directories it may work in. 2026-07-28 deprecates both: pass directories through tool parameters or server configuration instead of roots, and call an LLM provider's API directly instead of sampling.
Servers MUST implement one new method, server/discover, which reports their supported versions, capabilities, identity and an optional instructions string for the model. Clients MAY call it first, or invoke any request cold and handle the version error. The result looks like this:
{
"jsonrpc": "2.0",
"id": "discover-1",
"result": {
"resultType": "complete",
"supportedVersions": ["2026-07-28", "2025-11-25"],
"capabilities": { "tools": {}, "resources": {} },
"instructions": "Read-only SQL access to the sales database.",
"_meta": {
"io.modelcontextprotocol/serverInfo": { "name": "db-server", "version": "1.0.0" }
}
}
}
Legacy and modern, side by side:
| What the two sides have to agree | Legacy, through 2025-11-25 | Modern, 2026-07-28 |
|---|---|---|
| Protocol version | settled once in the initialize handshake | declared in _meta on every request |
| Client capabilities | sent once in initialize | sent in _meta on every request |
| How the client learns what the server offers | from the initialize response | from server/discover, or by sending a request and handling -32022 |
| How a server asks the client for input | it sends its own request | it returns input_required and the client retries |
| What an open connection means | a session | a byte stream carrying unrelated requests |
The older revisions have not been switched off. The spec calls them legacy, calls the per-request model modern, and defines how a dual-era implementation serves both. The Java MCP SDK 2.0.0 implements 2025-11-25, which is what the Java code on this site targets. Class 6 walks through the legacy handshake in detail.
Key Takeaways
- Host = the application users interact with (Claude Desktop, Cursor, your custom agent)
- Client = the MCP protocol component embedded in the host
- Server = what you build, your business logic wrapped in MCP
- Model = the AI reasoning engine, which sees only tool definitions and results
- One host can connect to multiple servers; each server is a separate process
- Servers expose three kinds of primitives: Tools, Resources, and Prompts
- Under
2026-07-28a connection is not a session: every request carries its own protocol version and capabilities
In the next class we take each of the three primitives in turn: what each is for and when to use which.
Further Reading
- MCP specification: Architecture: the four design principles behind the host, client and server split, and the responsibilities the specification assigns to each of them.
- MCP specification: Base Protocol: the shape of every JSON-RPC request, result, error and notification MCP uses, and the rules for which JSON Schema dialects a tool may declare.
- MCP specification: Versioning and Compatibility: the compatibility matrix for every pairing of legacy and modern client and server, and how a dual-era implementation works out which era it is talking to.
- MCP specification: Discovery: when a client should call
server/discoverfirst, and why a client must not base a security decision on theserverInfoa server reports about itself. - MCP specification: Multi Round-Trip Requests: the full server and client requirements, including how a server has to sign
requestStateand give it a short lifetime so a client cannot replay it or tamper with it. - MCP specification: Lifecycle (2025-11-25): the
initializerequest and response bodies, the timeout guidance, and the shutdown sequence for each transport. - modelcontextprotocol/servers: the reference server implementations, including the filesystem server that appears in the diagram above.
- Awesome MCP Servers: a community-maintained index of published MCP servers, for finding one that already covers a system you need.
Sources
- MCP specification: Base Protocol: the statelessness paragraph quoted above, and that protocol version and client capabilities are required in
_metaon every request whileclientInfois recommended. - MCP specification: Versioning and Compatibility:
UnsupportedProtocolVersionErrorwith code-32022and itssupportedlist, the client retry, and the legacy, modern and dual-era terms. - MCP specification: Discovery: that servers MUST implement
server/discover, and the fields it returns. - MCP specification: Multi Round-Trip Requests: that server-initiated requests were removed, and the
input_requiredresult withinputResponsesand the opaquerequestState. - MCP specification: Key Changes: the removal of sessions and of the
initializehandshake, and the deprecation of roots and sampling with their suggested migrations. - MCP specification: Lifecycle (2025-11-25): the three phases of the legacy connection and the
notifications/initializedacknowledgement. - MCP specification: Architecture: one host running many clients, each connected to exactly one server, and that a server cannot read the conversation or see into other servers.
- MCP specification: stdio transport: that the client launches the server as a subprocess, and the shutdown sequence in the lifecycle table.
- MCP specification: Tools: that servers must validate tool inputs, apply access controls and rate limit invocations.
- OWASP LLM06:2025 Excessive Agency: least-privilege database access for an LLM-driven tool, read access to only the tables it needs.
- Java MCP SDK release v2.0.0: that version
2.0.0tracks the2025-11-25specification. - Claude models overview: the current Claude model named in the first section.