Chapter 5: The Three Layers of MCP¶
5.1 Use three layers to organize the concepts¶
For a newcomer, MCP's vocabulary can be overwhelming: Host, Client, Server, Tools, Resources, Prompts, JSON-RPC, stdio, Streamable HTTP, sampling, elicitation, roots…
This chapter organizes those concepts into three perspectives: roles, capabilities, and transports. These are teaching categories, not three separate processes or a mandatory dependency hierarchy.
%%{init: {"flowchart": {"nodeSpacing": 12, "rankSpacing": 18, "padding": 8, "subGraphTitleMargin": {"top": 6, "bottom": 22}}}}%%
flowchart TB
subgraph L1["1 · Roles"]
direction TB
A["Host ── Client<br/>── Server"]
end
subgraph L2["2 · Capabilities"]
direction TB
B["Tools /<br/>Resources /<br/>Prompts"]
end
subgraph L3["3 · Transports"]
direction TB
C["JSON-RPC 2.0<br/>message format"]
end
L1 --> L2 --> L3
Details of the illustrated steps and components:
- Layer 1 · Roles: who communicates with whom
- Layer 2 · Capabilities: what a Server can provide
- Tools / Resources / Prompts + Client input capabilities
- Layer 3 · Transports: how messages travel
- JSON-RPC 2.0 message format + stdio / Streamable HTTP transports
Decoupling means that a transport can change without changing core capability semantics, and capabilities can expand without rewriting the role model.
5.2 Layer 1: roles¶
5.2.1 What each role does¶
| Role | What it is | Core responsibilities |
|---|---|---|
| Host | The AI application itself: Claude Desktop, Cursor, or your agent | Starts and manages Clients, selects Servers, enforces security policies, handles user authorization, and coordinates LLM calls |
| Client | A connection module inside the Host | Communicates, discovers capabilities, and forwards requests/results; usually corresponds to one Server connection |
| Server | The tool provider's independent process or service | Exposes capabilities and verifies callers and resource permissions |
The Host chooses providers, the Client encapsulates protocol interactions, and the Server supplies capabilities. The Server must care about authenticated identity and tenancy, but does not need to know which model or orchestration framework the caller uses internally.
5.2.2 One-to-one connections help isolation but do not guarantee security¶
A Host often creates a separate Client/connection for each Server to manage lifecycle, authentication, and failures independently. The specification does not thereby isolate the Server's filesystem, network, or process permissions.
Separate Clients / connections
%%{init: {"flowchart": {"nodeSpacing": 10, "rankSpacing": 12, "padding": 6}}}%%
flowchart LR
H1["Host"] --> C1["Client 1"] --> S1["Financial data<br/>Server"]
H1 --> C2["Client 2"] --> S2["Third-party tool<br/>Server"]
Actual security boundaries
%%{init: {"flowchart": {"nodeSpacing": 10, "rankSpacing": 12, "padding": 6}}}%%
flowchart LR
P["Host policies"] --> R["Process permissions"]
P --> N["Network controls"]
P --> A["User authorization"]
These boundaries include process and container permissions, network egress and data filtering, and user authorization with auditing. Separate connections make versions, authentication, failures, and lifecycles easier to manage independently; they do not establish those security boundaries by themselves.
Details of the illustrated steps and components:
- Manage versions, authentication, failures, and lifecycles separately
For third-party Servers, the Host should also apply least privilege, process/container isolation, network egress controls, and argument filtering. These runtime policies are what restrict what a Server can read and execute.
5.2.3 Host and Server enforce authorization separately¶
People often conflate Host and Client. The Client handles communication and forwarding; the Host retains authorization and policy decisions.
In particular, the Host decides:
- Whether the user has authorized a tool call.
- Which Servers' tools may be exposed to the model.
- Whether a sensitive operation needs another confirmation.
- How context from multiple Servers is assembled into the prompt.
Host confirmation cannot replace Server permission checks. The Server should verify identity, scopes, tenant, and object ownership for each operation. Likewise, a Client must not skip local policy merely because a Server describes an operation as “read-only.”
5.3 Layer 2: capabilities¶
5.3.1 Three capabilities provided by Servers¶
The key distinction is the default control path, not “read versus write” or the presence of side effects:
| Capability | Control | Side effects | Typical uses |
|---|---|---|---|
| Tools | A model or Host workflow may select them; the Host gives final permission | May read, write, or cause side effects | Search, create issues, send messages, write files |
| Resources | The Client/Host decides when to read or inject them | Usually readable context; not a security promise | Read logs, documents, or database records |
| Prompts | Retrieved by the user or Host | Return templates/messages | Code-review or weekly-report templates |
Tools, Resources, and Prompts are not security levels. The Host controls exposure and sharing; the Server controls actual resource access.
Their corresponding JSON-RPC methods are:
{"method": "tools/list"} // Discover available tools
{"method": "tools/call"} // Call a tool
{"method": "resources/list"} // List available resources
{"method": "resources/read"} // Read a resource
{"method": "prompts/list"} // List prompt templates
{"method": "prompts/get"} // Expand a template
These are method names, not complete requests. */list operations may be paginated. Cacheable results in 2026-07-28 carry ttlMs and cacheScope; Clients can subscribe to list changes through subscriptions/listen. Caches must be isolated by authorization context: one tenant's tool list cannot be reused for another tenant.
The Tools specification【285】 distinguishes requirements of different strengths:
- The tool set MUST NOT vary with connection state or the side effects of other requests on that connection. It MAY change over time or according to the authorization supplied with the current request.
- If the set has not changed, the Server SHOULD return tools in deterministic order to support list caching and model-prefix caching. Alphabetical order is not required.
- The
tools.listChangedcapability still exists. A Server declaring it SHOULD sendnotifications/tools/list_changedto Clients that requestednotifications.toolsListChanged: truethroughsubscriptions/listen, not broadcast to every connection.
The subscription specification【289】 requires notifications/subscriptions/acknowledged first. Its filter describes the subset the Server actually accepted. The Client should check it rather than equate submitting a subscription with success. Notifications on the stream carry _meta.io.modelcontextprotocol/subscriptionId for correlation. Change notifications do not contain the complete new list: call tools/list again and review new or changed definitions.
5.3.2 An easily missed fourth category: the Server needs Client input¶
The three capabilities above are what the Server provides to the Client. During execution, however, a Server may need model inference or additional information from the user. In 2026-07-28, the Server no longer initiates a reverse JSON-RPC request. Instead, it returns InputRequiredResult in the current response; the Client handles it and resends the original request with the input.
| Capability | What the Server needs | Use |
|---|---|---|
| Sampling (deprecated) | Controlled model inference on the Host side | Described for compatibility only; new implementations should consider direct model-API integration |
| Elicitation | Structured additional information from the user | Fills missing parameters; does not replace separate approval for high-risk actions |
| Roots (deprecated) | Context hints such as working directories | Not a filesystem sandbox; migrate to parameters, resource URIs, or Server configuration |
These statuses follow the 2026-07-28 changelog【277】: Sampling, Roots, and Logging are deprecated, not removed. A Host supporting Sampling for compatibility must still constrain models, budgets, visible context, and returned data.
5.3.3 The InputRequiredResult round-trip pattern¶
This pattern is called MRTR: Multi Round-Trip Requests. The direction remains Client request → Server response. When Client input is needed, the Server returns the input requirements and ends that response. The Client then supplies the input in a new request; the Server need not keep the original call stack suspended:
%%{init: {"flowchart": {"nodeSpacing": 10, "rankSpacing": 12, "padding": 6}}}%%
flowchart TB
S0["tools/call"]
S1["Input required"]
S2["Host collects input"]
S3["Resubmit request"]
S4["Tool result"]
S0 --> S1 --> S2 --> S3 --> S4
Complete exchange, including phase notes:
| Participants | Message or action |
|---|---|
| Note: Client, Server | Server needs additional user information |
| Client → Server | tools/call |
| Server → Client (return) | InputRequiredResult(elicitation/create) |
| Client → Client | Host displays the request and collects user input |
| Client → Server | New request ID, original params + inputResponses + requestState |
| Server → Client (return) | Tool result |
Not every Server uses these capabilities. The Host should declare permitted Client capabilities on each request and include user interaction, model access, budgets, and data boundaries in its authorization policy.
InputRequiredResult.resultType is input_required. The Client supplies inputResponses matching the keys in inputRequests and echoes requestState unchanged. That state is not trusted authorization: bind it to the caller, arguments, and expiry, and prevent repeated side effects when an input flow is replayed. An ordinary completed result has resultType: complete.
5.4 Layer 3: transports¶
5.4.1 Message format and transport are decoupled¶
The design separates how a message is represented from how it travels:
%%{init: {"flowchart": {"nodeSpacing": 12, "rankSpacing": 18, "padding": 8}}}%%
flowchart TB
MSG["JSON-RPC 2.0"]
MSG --> T1["stdio"]
MSG --> T2["Streamable HTTP"]
MSG --> T3["Custom<br/>transport"]
Details of the illustrated steps and components:
- JSON-RPC 2.0 Message format: unchanged
- stdio Local subprocess
- Streamable HTTP Remote service
- Custom transport Extensions permitted by the specification
Tool semantics can be reused across transports. Moving to a remote service still changes authentication, deployment, cancellation, isolation, and connection-failure handling; it is not necessarily a configuration-only change. The current HTTP and stdio cancellation mechanisms also differ.
5.4.2 The two main transports¶
| stdio | Streamable HTTP | |
|---|---|---|
| Server form | Local subprocess | Independent HTTP service |
| Communication channel | Operating-system pipes: stdin/stdout | HTTP POST |
| Latency | No network round trip, but serialization and scheduling remain | Depends on deployment, network, and service processing |
| Sharing across Clients | Standard subprocess pipes are normally exclusive to one Client; backend services can still be shared | An independent service can accept multiple Clients |
| Authentication | Credentials generally come from the environment or controlled configuration; process isolation is separate | Protocol authorization is optional; protected HTTP services use the relevant OAuth specification |
| Typical uses | Filesystem, local Git, local databases | Shared team services and SaaS tools |
One practical detail: in stdio mode, stdout is reserved for protocol messages. Any debug print can contaminate the stream and cause parsing failures. Write logs to stderr—this is one of the most common mistakes when writing a first MCP Server.
For full transport details, including the transition from HTTP + SSE to Streamable HTTP, see Chapter 12.
5.5 Putting the three layers together: a complete call¶
%%{init: {"flowchart": {"nodeSpacing": 10, "rankSpacing": 12, "padding": 6}}}%%
flowchart TB
S0["Discover tools"]
S1["Filter schemas"]
S2["Model proposes call"]
S3["User approves"]
S4["Host calls server"]
S5["Return answer"]
S0 --> S1 --> S2 --> S3 --> S4 --> S5
Complete exchange, including phase notes:
| Participants | Message or action |
|---|---|
| Note: Host, Server | Optional discovery phase |
| Host → Client | Create a Client connected to the GitHub Server |
| Client → Server | server/discover |
| Server → Client (return) | Supported versions and capabilities |
| Client → Server | tools/list |
| Server → Client (return) | [create_issue, search_repos, ...] |
| Client → Host | Consolidated tool list |
| Note: Host, LLM | Execution phase |
| User → Host | File a bug issue for me |
| Host → LLM | messages + tool schemas filtered by authorization and task |
| LLM → Host (return) | tool_calls: create_issue(...) |
| Host → User | Confirm creation of the issue |
| User → Host | Approve |
| Host → Client | Forward the call |
| Client → Server | tools/call |
| Server → Client (return) | {"issue_url": "..."} |
| Client → Host | Result |
| Host → LLM | Supply the tool result in the model API format |
| LLM → Host (return) | Issue created, here is the link... |
| Host → User | Final answer |
Three points matter in this complete call:
- If the Host uses model function calling, it can convert MCP Tools to the model's schema. Rules, structured output, or human actions can also invoke MCP; MCP does not mandate a model interface.
- User authorization happens at the Host layer, before the actual call is sent.
- Capabilities may be discovered in advance, but negotiation is per request. Every request still carries its version and Client capabilities; startup caches alone are insufficient.
5.6 Common mistakes¶
5.6.1 Mixing all three layers together¶
An interview answer that mixes Host/Client/Server, Tools/Resources/Prompts, and stdio/HTTP sounds like a list of memorized terms. Three layers make the structure clearer, with one question per layer: who communicates, what is provided, and how messages travel.
5.6.2 Mistaking connection mappings for a security sandbox¶
Separate Clients/connections help management. Security boundaries still depend on Host authorization, runtime isolation, and network policy—not object relationships alone.
5.6.3 Assigning the Host's responsibilities to the Client¶
The Host makes application-level policy and lifecycle decisions. The Client implements protocol connections, version and capability handling, and possibly OAuth flows. Treating a Client as a byte pipe with no validation duties overlooks protocol and authentication checks.
5.6.4 Knowing only the three Server capabilities and missing input requirements¶
Sampling and Elicitation let a Server request model or user input from the Host during execution. The current specification handles those round trips with InputRequiredResult; do not copy the legacy Server→Client request flow.
5.6.5 Ignoring stdout contamination¶
Writing logs to stdout in stdio mode directly breaks the protocol stream. The resulting error is often an obscure JSON parsing failure. All logs go to stderr.
5.6.6 Ignoring per-request capability negotiation¶
Every request must carry its version and Client capabilities. Do not discover once at startup and trust that cache forever, or assume every Host permits sampling or elicitation.
5.7 Chapter summary¶
- Three layers organize the concepts. Roles answer “who communicates,” capabilities answer “what is provided,” and transports answer “how messages travel.”
- Keep the layers as decoupled as possible, so transports and capabilities can evolve separately.
- The Host decides, the Client connects, and the Server provides. Separate connections do not replace runtime security isolation.
- The three Server capabilities differ by default control path. Models/workflows can select Tools, Clients load Resources, and users/Hosts retrieve Prompts.
- Input requirements use MRTR round trips. Elicitation obtains user information; Sampling and Roots are retained as deprecated compatibility features.
- The current specification uses
InputRequiredResult, not reverse Server requests. Client capabilities accompany each request and remain subject to Host policy. - Switching transports still requires engineering work. Authentication, cancellation, and deployment constraints do not disappear.
- With stdio, stdout is exclusively for protocol messages. Logs must use stderr.
References¶
See the central bibliography for this chapter’s sources, reading suggestions, and source notes.