Chapter 7: MCP Fundamentals: The Model Context Protocol

Learning Objectives

Pre-Reading Check — Why MCP Exists

1. Connecting N AI applications to M data sources with bespoke point-to-point connectors requires roughly how many integrations, and what does MCP reduce that to?

N+M point-to-point; MCP raises it to N×M N×M point-to-point; MCP reduces it to N+M N×M point-to-point; MCP reduces it to a constant 1 N+M point-to-point; MCP keeps it at N+M

2. The "USB-C for AI" analogy is most useful for conveying which property of MCP?

That MCP transmits data faster than older protocols That MCP requires a proprietary cable from each vendor That one universal interface lets any compliant app plug into any compliant tool That MCP only works on machines with USB-C ports

3. How does MCP relate to the function calling covered in Chapter 5?

MCP replaces function calling entirely; the two cannot coexist They are the same mechanism under two different names Function calling is model-facing (choosing a capability); MCP is integration-facing (discovering and reaching capabilities) Function calling is the wire protocol; MCP is the model's decision layer

Why MCP Exists

Building a hyper-personalized assistant means connecting a language model to the messy, heterogeneous world where a user's data actually lives: their calendar, email, a CRM, a document store, an internal database, a payments API. Before MCP, every one of those connections was a bespoke engineering project. Anthropic introduced the Model Context Protocol as an open standard on November 25, 2024, describing it as an open standard for "secure, two-way connections between data sources and AI-powered tools," motivated by the fact that even capable models were "trapped behind information silos and legacy systems."

Key Points

The N×M Integration Problem

Imagine N AI applications (chat assistants, IDEs, agents) and M tools or data sources (Slack, Google Drive, GitHub, a Postgres database). Wire them directly and each application needs a custom connector for each data source — roughly N×M bespoke integrations. Add one new tool and you build N new connectors; add one new application and you build M new connectors. With MCP, each side implements the protocol once and any compliant client can talk to any compliant server.

Suppose you have 4 assistants and 5 data sources:

ApproachConnectors requiredFormulaCost of adding one new tool
Point-to-point (no standard)4 × 5 = 20N × MBuild 4 new connectors (one per app)
MCP (shared protocol)4 + 5 = 9N + MBuild 1 server; every app already speaks MCP

Figure 7.1: Point-to-point integrations (N×M) versus a shared MCP protocol (N+M).

graph TD subgraph P2P["Point-to-Point: N x M bespoke connectors"] A1[App 1] A2[App 2] D1[Slack] D2[Drive] D3[GitHub] A1 --> D1 A1 --> D2 A1 --> D3 A2 --> D1 A2 --> D2 A2 --> D3 end subgraph MCP["MCP: each side implements protocol once, N + M"] B1[App 1] B2[App 2] HUB{{MCP Protocol}} S1[Slack Server] S2[Drive Server] S3[GitHub Server] B1 --> HUB B2 --> HUB HUB --> S1 HUB --> S2 HUB --> S3 end
[Animation slot: the "add one new tool" moment — watch 4 new arrows sprout in the point-to-point graph while only a single server node appears in the MCP graph.]

MCP as a Universal Connector

Before USB-C, every device had its own proprietary cable. USB-C replaced that with a single port any peripheral could use. MCP does the same for software: one universal interface so any compliant model or app can connect to any compliant data source or service. A concrete benefit falls straight out: the ecosystem becomes shareable. The project ships not just a specification but language SDKs, tooling such as the MCP Inspector, and pre-built reference servers for Google Drive, Slack, and GitHub. Crucially the standard did not stay proprietary — cross-vendor adoption is exactly what makes the USB-C analogy hold, because a connector standard is only useful if everyone plugs into it.

Relationship to Function Calling

Function calling is the model-facing mechanism: presented with function definitions, the LLM decides which to call and produces a structured argument object — a capability of the model and its API. MCP is the integration-facing standard: how an application discovers what an external system offers, negotiates capabilities, and invokes them over a common wire format. MCP "focuses solely on the protocol for context exchange — it does not dictate how AI applications use their LLMs."

ConcernFunction Calling (Ch. 5)MCP (Ch. 7)
LayerModel ↔ applicationApplication ↔ external systems
Question it answers"Which capability should the model invoke, with what arguments?""How does my app discover, negotiate, and invoke capabilities from any external system?"
Owned byThe LLM and its APIAn open, cross-vendor protocol
AnalogyThe decision to plug something inThe universal socket you plug into

In practice, an MCP server exposes tools; the model, through function calling, decides to invoke one; and MCP carries the invocation to the server and the result back. Function calling is how the model reaches; MCP standardizes what it reaches into.

Post-Reading Check — Why MCP Exists

1. Connecting N AI applications to M data sources with bespoke point-to-point connectors requires roughly how many integrations, and what does MCP reduce that to?

N+M point-to-point; MCP raises it to N×M N×M point-to-point; MCP reduces it to N+M N×M point-to-point; MCP reduces it to a constant 1 N+M point-to-point; MCP keeps it at N+M

2. The "USB-C for AI" analogy is most useful for conveying which property of MCP?

That MCP transmits data faster than older protocols That MCP requires a proprietary cable from each vendor That one universal interface lets any compliant app plug into any compliant tool That MCP only works on machines with USB-C ports

3. How does MCP relate to the function calling covered in Chapter 5?

MCP replaces function calling entirely; the two cannot coexist They are the same mechanism under two different names Function calling is model-facing (choosing a capability); MCP is integration-facing (discovering and reaching capabilities) Function calling is the wire protocol; MCP is the model's decision layer
Pre-Reading Check — Client and Server Roles

4. In MCP's host-client-server architecture, what is the relationship between clients and servers?

One client multiplexes connections to many servers at once Each client maintains a dedicated 1:1 connection to exactly one server Servers connect directly to each other, bypassing clients The host connects directly to servers without any client

5. Which participant owns security policy and access enforcement in MCP?

The server, because it holds the data The client, because it routes messages The host, which is the security boundary that mediates every interaction The LLM, because it decides what tools to call

6. What do the three steps of the MCP initialize handshake accomplish?

They transfer the user's credentials to every server They negotiate protocol version, discover each side's capabilities, and exchange identity They execute the first tool call to warm up the connection They compress messages so later traffic is smaller

Client and Server Roles

MCP's architecture is deliberately simple, but three words — host, client, server — sound interchangeable and are not. A host application establishes connections to one or more servers, creating one client per server.

Key Points

Host, Client, and Server

Think of the host as a restaurant's front-of-house manager, each client as a dedicated waiter assigned to exactly one kitchen station, and each server as a specialized station (the grill, the pastry counter). The manager coordinates the experience and decides what the guest may order; each waiter talks to only one station and never lets stations talk directly to each other or to the guest. The one-client-per-server (1:1) rule is a defining structural feature: a host connected to multiple servers runs multiple clients in parallel, which preserves security boundaries.

MCP is defined in two layers: a data layer (the JSON-RPC 2.0 protocol defining message structure, lifecycle, and the core primitives) and a transport layer (STDIO for local processes; Streamable HTTP with optional Server-Sent Events for remote, supporting bearer tokens, API keys, and recommending OAuth). The transport layer abstracts communication so the same JSON-RPC message format works across every transport.

Figure 7.2: The MCP host-client-server architecture with one client bound 1:1 to each server.

graph TD HOST["MCP Host
(Claude Desktop / VS Code)
coordinates LLM, enforces security"] CA[Client A] CB[Client B] CC[Client C] SA["Server A
(GitHub)"] SB["Server B
(Filesystem)"] SC["Server C
(Sentry)"] HOST --> CA HOST --> CB HOST --> CC CA -->|1:1| SA CB -->|1:1| SB CC -->|1:1| SC
[Animation slot: highlight one client-server 1:1 pair at a time to show isolation — each waiter serves exactly one kitchen station.]

Capability Negotiation and Initialization

MCP is a stateful protocol. The first thing a client and server do is negotiate the capabilities they both support, through a three-step handshake:

  1. initialize request (client → server) — carries protocolVersion (e.g., "2025-06-18"), capabilities (e.g., sampling, elicitation), and clientInfo.
  2. initialize response (server → client) — returns the selected protocolVersion, the server's capabilities (e.g., "tools": {"listChanged": true}, "resources": {}), and serverInfo.
  3. notifications/initialized (client → server) — a notification (no id) acknowledging readiness. The session is now live.

Figure 7.3: The three-step initialize capability-negotiation handshake.

sequenceDiagram participant C as MCP Client participant S as MCP Server C->>S: initialize request (protocolVersion, capabilities, clientInfo) S-->>C: initialize response (selected version, capabilities, serverInfo) C->>S: notifications/initialized (no id, no response) Note over C,S: Session live; declared capabilities fix what the session can do

Reading a real exchange top to bottom: both sides agree on version 2025-06-18; the server advertises tools (and will notify on tool-list changes) and resources, but says nothing about prompts — so the client knows not to attempt prompt operations. The initialization exchange serves three critical purposes:

PurposeWhat it doesFailure mode
Protocol version negotiationEnsures both sides speak a compatible versionIf no compatible version exists, the connection should be terminated
Capability discoveryEach party declares which primitives and features it supportsThe session avoids attempting unsupported operations
Identity exchangeclientInfo / serverInfo carry name and versionUsed for debugging and compatibility

Who Owns What

ResponsibilityOwned byNotes
User experience and LLM coordinationHostDrives the conversation and decides how context is used
Security policy and access enforcementHostThe host is the security boundary; servers do not trust each other
Protocol negotiation and message routingClientOne client per server; routes messages both directions
Server isolationClientEach 1:1 binding keeps servers from interacting
Providing tools, resources, and promptsServerServes context; does not manage the LLM

Because the host is the security boundary, a compromised or misbehaving server cannot reach into another server or silently exfiltrate context — the host mediates every interaction.

Post-Reading Check — Client and Server Roles

4. In MCP's host-client-server architecture, what is the relationship between clients and servers?

One client multiplexes connections to many servers at once Each client maintains a dedicated 1:1 connection to exactly one server Servers connect directly to each other, bypassing clients The host connects directly to servers without any client

5. Which participant owns security policy and access enforcement in MCP?

The server, because it holds the data The client, because it routes messages The host, which is the security boundary that mediates every interaction The LLM, because it decides what tools to call

6. What do the three steps of the MCP initialize handshake accomplish?

They transfer the user's credentials to every server They negotiate protocol version, discover each side's capabilities, and exchange identity They execute the first tool call to warm up the connection They compress messages so later traffic is smaller
Pre-Reading Check — MCP Primitives

7. What is the single most important way to distinguish MCP's three primitives?

By the transport they use (STDIO vs. HTTP) By their control model — who decides when each gets used By how many bytes they consume on the wire By which programming language the server is written in

8. A read-only database schema exposed by a server, pulled into context by the host, is best modeled as which primitive?

A tool (model-controlled) A prompt (user-controlled) A resource (application-controlled) A sampling request (server-controlled)

9. What does the client-side sampling primitive (sampling/createMessage) let a server do?

Request an LLM completion from the host, staying model-independent Sample a random subset of the server's tools to reduce cost Read a portion of a large file before loading the whole thing Embed its own LLM SDK so it never needs the host

MCP Primitives

Anthropic's documentation calls the primitives "the most important concept within MCP." They define what clients and servers can offer each other. MCP defines three core primitives that servers expose: tools, resources, and prompts.

Key Points

Tools, Resources, and Prompts

Tools are "executable functions that AI applications can invoke to perform actions" — a tool takes JSON Schema arguments (inputSchema) and does something. They are model-controlled and, because they have side effects, most demand careful permissioning by the host (e.g., create_calendar_event).

Resources are "data sources that provide contextual information" — read-only content the application pulls in. They are application-controlled. A tool is a verb (do something); a resource is a noun (here is some data), e.g., the contents of the user's latest invoice.

Prompts are "reusable templates that help structure interactions with language models." They are user-controlled — a human explicitly selects one, typically as a slash command — and often act as the "handoff artifact" packaging an expert's workflow.

The Control Model

The three primitives are best understood by who decides when they get used — three distinct control planes:

PrimitiveControl modelWho decides invocationNatureTypical trigger
ToolsModel-controlledThe LLM decides, based on the conversationExecutable action (side effects)Model emits a function call
ResourcesApplication-controlledThe host decides when to pull it into contextRead-only data / contextApp injects context
PromptsUser-controlledThe human explicitly selects itReusable interaction templateSlash command / menu selection

A concrete illustration ties all three together: a server providing context about a database can expose tools for querying it, a resource containing the schema, and a prompt with few-shot examples. The model calls the tools, the app supplies the schema, and the user invokes the prompt.

[Animation slot: three lanes — Model, Application, User — each lighting up the primitive it controls, converging on one database server.]

Prompts and Sampling — Client-Side Primitives

For completeness, MCP also defines primitives that clients expose so servers can build richer interactions:

Sampling inverts the usual direction of control: instead of the model reaching out to the server, the server reaches back to borrow the host's model.

Post-Reading Check — MCP Primitives

7. What is the single most important way to distinguish MCP's three primitives?

By the transport they use (STDIO vs. HTTP) By their control model — who decides when each gets used By how many bytes they consume on the wire By which programming language the server is written in

8. A read-only database schema exposed by a server, pulled into context by the host, is best modeled as which primitive?

A tool (model-controlled) A prompt (user-controlled) A resource (application-controlled) A sampling request (server-controlled)

9. What does the client-side sampling primitive (sampling/createMessage) let a server do?

Request an LLM completion from the host, staying model-independent Sample a random subset of the server's tools to reduce cost Read a portion of a large file before loading the whole thing Embed its own LLM SDK so it never needs the host
Pre-Reading Check — The Message Lifecycle

10. In JSON-RPC 2.0, what distinguishes a notification from a request?

A notification carries an id; a request does not A notification has no id and expects no response; a request has an id and expects a matching response A notification always contains an error field A notification uses HTTP while a request uses STDIO

11. Only one MCP primitive is executed rather than merely retrieved. Which, and with what method?

Resources, via resources/read Prompts, via prompts/get Tools, via tools/call All three, via a shared */call method

12. Why does capability negotiation happen before a server can push a notifications/tools/list_changed message?

Because only servers that declared listChanged at initialization may send such notifications Because notifications require the client to send an id first Because tool lists can never change once discovered Because the transport must switch from STDIO to HTTP first

The Message Lifecycle

Everything in MCP travels as JSON-RPC 2.0 messages, and every primitive is reached through a small, predictable set of methods.

Key Points

JSON-RPC Message Flow

Every JSON-RPC message begins with "jsonrpc": "2.0". The id field pairs a response to its originating request; its absence is precisely what marks a message as a fire-and-forget notification (e.g., notifications/initialized, notifications/tools/list_changed).

Discovery and Listing

After the handshake, a client discovers what a server offers via the */list methods. Crucially, listings are dynamic and can change over time. A server whose tools depend on runtime state (say, which repository is open) can add or remove tools mid-session. This is where the listChanged capability earns its keep: a server that declared "tools": {"listChanged": true} can push a notifications/tools/list_changed message, prompting the client to call tools/list again. Only servers that declared this capability send such notifications — which is exactly why capability negotiation happens first.

Invocation and Results

OperationMethod(s)Applies to
Discoverytools/list, resources/list, prompts/listAll three primitives
Retrievalresources/read, prompts/getResources and prompts (read data)
Executiontools/callTools only (perform an action)

Note the asymmetry: only tools are executed via tools/call. Resources and prompts are retrieved — nothing is done in the world. This maps precisely back to the control model: the executable primitive (tools) is the one the model drives, and it is the only one with a call method.

Figure 7.4: The full JSON-RPC message lifecycle for a database assistant, from initialization to dynamic updates.

sequenceDiagram participant C as Client / Host participant S as Server Note over C,S: 1. INITIALIZE C->>S: initialize (negotiate version + capabilities) S-->>C: capabilities { tools, resources } C->>S: notifications/initialized Note over C,S: 2. DISCOVER C->>S: tools/list S-->>C: [ run_query ] C->>S: resources/list S-->>C: [ db_schema ] Note over C,S: 3. RETRIEVE (application-controlled) C->>S: resources/read (db_schema) S-->>C: schema text Note over C,S: 4. INVOKE (model-controlled) C->>S: tools/call run_query {sql} S-->>C: result: query rows Note over C,S: 5. UPDATE (dynamic listing) S->>C: notifications/tools/list_changed C->>S: tools/list (refresh)
[Animation slot: step through the five lifecycle phases (Initialize → Discover → Retrieve → Invoke → Update) one arrow at a time.]

Beyond these core methods, MCP defines cross-cutting utilities. Notifications enable the real-time updates just described, and experimental Tasks provide durable execution wrappers for long-running requests that cannot complete in a single quick round trip.

Post-Reading Check — The Message Lifecycle

10. In JSON-RPC 2.0, what distinguishes a notification from a request?

A notification carries an id; a request does not A notification has no id and expects no response; a request has an id and expects a matching response A notification always contains an error field A notification uses HTTP while a request uses STDIO

11. Only one MCP primitive is executed rather than merely retrieved. Which, and with what method?

Resources, via resources/read Prompts, via prompts/get Tools, via tools/call All three, via a shared */call method

12. Why does capability negotiation happen before a server can push a notifications/tools/list_changed message?

Because only servers that declared listChanged at initialization may send such notifications Because notifications require the client to send an id first Because tool lists can never change once discovered Because the transport must switch from STDIO to HTTP first

Your Progress

Answer Explanations