⌂ Home MCP Track — Building with the Model Context Protocol ⚡ MODULE 00 of 8 · MCP Track
~45 min Conceptual Protocol Foundations
MODULE 00 · Protocol Overview

What is MCP?

The Model Context Protocol explained from first principles — why it exists, how its three-layer architecture works, and what the JSON-RPC 2.0 wire format looks like under the hood.

After this module you will:
  • Explain the "integration hell" MCP solves and why a shared protocol beats per-app plugins
  • Describe the Host → Client → Server layered architecture and each layer's responsibilities
  • Read a JSON-RPC 2.0 tool-call round-trip and identify every field
  • Choose the right primitive (Tool / Resource / Prompt) for a given use case
  • Compare stdio vs HTTP/SSE transport and explain when each is appropriate

The Problem MCP Solves

Analogy — Before MCP

Imagine every USB device required its own custom port cut into your laptop chassis. Your keyboard needs a 6-pin oval port, your mouse needs a trapezoidal one, your monitor needs something proprietary, and every new device requires buying a new laptop.

That was the state of AI app integrations before MCP. Every IDE, every chat interface, and every AI coding assistant invented its own plugin API, its own authentication handshake, and its own schema for describing what tools were available. Teams at GitHub spent months building a GitHub Copilot integration that worked exclusively in VS Code. Notion built their own Claude integration. Salesforce built a separate Einstein integration. None of these could share server code, configuration, or tooling.

MCP is the USB-C of AI integrations. One open protocol, and any host that speaks it can connect to any server that speaks it — the same way USB-C lets your phone charge from your laptop, your monitor, or a hotel lobby kiosk.

Before MCP, the pattern was always the same:

  1. An AI app (Claude Desktop, Cursor, a custom agent) wants to access some external capability.
  2. The app team writes a bespoke adapter — usually REST HTTP calls with hand-rolled JSON schemas.
  3. The server team writes a bespoke counterpart that only works for that app.
  4. When a second app wants the same capability, the server team writes a second adapter.
  5. Six months later, there are 5 adapters, they all slightly disagree on schema formats, and updating the underlying service breaks all of them.
Why It Matters

The GitHub MCP server launched in March 2025. Within 48 hours, it worked with Claude Desktop, Cursor, Windsurf, and any agent built on the Anthropic SDK — with zero changes to the server. That's the payoff: build the server once, expose it everywhere. Real teams report 70–90% reduction in integration maintenance overhead after moving to MCP.

MCP was proposed by Anthropic in November 2024 and immediately adopted by major IDEs (Cursor, Windsurf, Zed) and cloud vendors (Cloudflare, Supabase, MongoDB). The spec is open, the reference implementations are MIT-licensed, and the protocol is LLM-agnostic — a Claude Desktop plugin works equally well as an OpenAI tool server.

Coming up next: the three-layer architecture that makes a single protocol work across such different deployment scenarios — from a local subprocess to a globally distributed cloud service.

The MCP Architecture

MCP defines three distinct roles. These are not the same as "client" and "server" in the REST sense — the naming is more specific:

Technical Definition — Host A Host is the application the end-user interacts with directly. It owns the LLM conversation context. When the LLM decides it needs to call a tool, the host is the entity that receives that decision. Examples: Claude Desktop, Cursor, VS Code with Copilot Chat, a custom agent you build.
Technical Definition — Client A Client is a module embedded inside the host that manages exactly one connection to exactly one MCP server. The host spawns a new client instance for each server it connects to. The client translates the host's internal tool-call requests into MCP protocol messages and routes the server's responses back. It handles capability negotiation, message framing, and transport details so the host doesn't have to.
Technical Definition — Server A Server is a standalone process (or service) that exposes a specific set of capabilities — tools, resources, and/or prompts — over the MCP protocol. It knows nothing about the host or the LLM. It only receives structured requests and returns structured responses.
Animation 1 — MCP Message Flow
HOST
Claude Desktop
LLM decides to call a tool
JSON-RPC 2.0
CLIENT
MCP Client
Routes, frames, negotiates
stdio / SSE
SERVER
Your Server
Executes tool, returns result

Notice the layering: the Host and Server never communicate directly. Every message is brokered by the Client. This is intentional — it means the host can connect to dozens of servers without writing transport code for each one, and the server never needs to know which host it's talking to.

In practice, the Client lives inside the same process as the Host (it's typically a library import, not a separate binary). The Server is almost always a separate process — either a local subprocess (stdio) or a remote service (HTTP/SSE).

With the architecture mapped, let's look one level deeper: the actual bytes on the wire. Understanding the wire format makes debugging MCP issues straightforward.

JSON-RPC 2.0 Wire Format

Technical Definition — JSON-RPC 2.0 JSON-RPC 2.0JSON-RPC 2.0 — a lightweight remote procedure call protocol encoded in JSON. Every request carries a jsonrpc version field, a numeric id, a method string, and a params object. Responses mirror the same id and carry either a result or an error field. No HTTP required — works over any transport. is a transport-agnostic RPC protocol. MCP uses it for every message — initialization, capability negotiation, tool calls, resource reads, and prompt fetches. Each message is a JSON object terminated by a newline (\n).

Here is a complete tool-call round-trip. Every field shown here is part of the official MCP spec:

Step 1 — Client sends a tool_call request

JSON-RPC Request (client → server)
{
  "jsonrpc": "2.0",
  "id": 42,
  "method": "tools/call",
  "params": {
    "name": "search_documents",
    "arguments": {
      "query": "MCP transport options",
      "limit": 5
    }
  }
}

Step 2 — Server sends the result

JSON-RPC Response (server → client)
{
  "jsonrpc": "2.0",
  "id": 42,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "Found 3 results for 'MCP transport options':\n1. stdio: local subprocess, lowest latency\n2. HTTP/SSE: remote service, sharable across clients\n3. WebSocket: bidirectional streaming (experimental)"
      }
    ],
    "isError": false
  }
}

Step 3 — If something went wrong

JSON-RPC Error Response
{
  "jsonrpc": "2.0",
  "id": 42,
  "error": {
    "code": -32602,
    "message": "Invalid params: 'limit' must be between 1 and 100"
  }
}
✓ What Just Happened?

The id field is how the client matches a response to a request — both sides must mirror it. The method is always a namespaced string like tools/call, resources/read, or prompts/get. When errors occur, they come back in the error field (never in result), and the standard JSON-RPC error codes (-32700 to -32603) apply.

You will almost never write these JSON messages by hand — the SDK handles all serialization. But seeing the wire format demystifies what the SDK is doing, and it's essential for debugging with tools like mcp dev (MCP Inspector).

Knowing the envelope format, now let's look at what can be inside it — the three primitives that MCP servers can expose.

The Three Primitives

Every MCP server exposes its capabilities using exactly three building blocks. Choosing the right one determines how the LLM interacts with your data or logic.

Animation 2 — Tools vs Resources vs Prompts
🔨
Tool
Active: causes side effects. LLM calls it to take an action.
e.g. send_email(), update_record()
📄
Resource
Passive: read-only data. LLM reads it into context.
e.g. file://config.json, db://users/42
💬
Prompt
Template: server-managed. Host injects into conversation.
e.g. "analyze_pr", "summarize_ticket"

Tools (Actions)

A Tool is a function the LLM can invoke that may have side effects. The LLM describes what it wants to do, the host routes it through the client to the server, and the server executes it. Use tools for: API calls, database writes, sending messages, running computations, triggering workflows.

Resources (Data)

A Resource is a URI-addressed piece of data the LLM can read into its context. Resources are always read-only — they have no side effects. They can be static (a file) or dynamic (a live database row). Use resources for: files, database records, API snapshots, configuration, documentation. Think of them as "managed context injection."

Prompts (Templates)

A Prompt is a reusable prompt template stored on the server. The host can ask the server to list its prompts, then inject one into the conversation. This moves prompt engineering to the server side, where it can be versioned, tested, and deployed without touching client code. Use prompts for: standardized analysis patterns, domain-specific instructions, workflow openers.

Primitive Side effects? Who initiates? Typical use
ToolYes (or pure compute)LLM decideswrite_file(), query_db(), send_slack()
ResourceNo — read onlyLLM or userfile://report.pdf, db://order/1234
PromptNoUser / host UI"Review this PR", "Summarize ticket"
Design Tip When in doubt, start with Tools. Resources and Prompts are powerful for specific scenarios (large static data, standardized workflows), but most MCP servers in production today are tool-only. MCP01 focuses entirely on building tool servers for exactly this reason.
The primitives define what can be sent. But the protocol also needs to physically transmit messages — that's what transports do.

Two Transports

MCP is transport-agnostic at the protocol level, but the spec defines two official transports. Your choice between them determines where your server can run and who can connect to it.

Property stdio (local) HTTP + SSE (remote)
How it worksServer is a subprocess; host writes to its stdin, reads its stdoutServer is an HTTP service; host POSTs requests, server streams responses via Server-Sent Events
Best forLocal tools: file system, shell, local DB, dev toolsShared services: APIs, cloud DBs, team-wide resources
LatencyLowest (same machine, no network)Network-dependent
Multi-clientNo — one host spawns one subprocessYes — multiple hosts can connect to one server
AuthOS-level (process ownership)OAuth 2.0 (required for public servers)
DeploymentLocal binary or scriptAny HTTP host: VPS, Cloud Run, Lambda, Fly.io
StreamingNewline-delimited JSON on stdoutSSE: data: events on the event stream
Analogy — stdio vs HTTP/SSE

Imagine you're building a customer service hotline for a bank. If you need one agent who handles calls only for the internal dev team on one floor, you give them a desk phone and an extension — fast, simple, not accessible from outside. That's stdio.

If you need the same agent to handle calls from any branch office, any ATM, and the mobile app simultaneously, you deploy a proper call center with a published number, authentication, and queuing. That's HTTP/SSE.

The underlying agent (your server code) is identical in both scenarios — only the telephony infrastructure changes. MCP works the same way: switch from mcp.run() to mcp.run(transport="sse") in FastMCP, and the server suddenly accepts HTTP connections without changing a single tool function.

When to Use Each Start with stdio. It requires no auth, no network config, and no deployment. When you're ready to share the server across your team or deploy to a cloud environment, switch to HTTP/SSE — covered in MCP03.
Choosing a transport answers "how do messages get there?" — but the first message on any connection has a specific purpose: capability negotiation. That happens before any tool can be called.

Capability Negotiation Handshake

Before the LLM can call any tool, the client and server must complete a structured handshake. This is how the host discovers what a server can do — and it happens automatically every time a connection is established.

Animation 3 — Capability Negotiation Handshake
CLIENT SENDS
initialize →
tools/list →
resources/list →
initialized → (notification)
✓ Ready to call tools
SERVER RESPONDS
← {protocolVersion, serverInfo, capabilities}
← [{name, description, inputSchema}, ...]
← [{uri, name, mimeType}, ...] or []
 
 

The capabilities object in the server's initialize response tells the client which primitives the server supports — tools, resources, prompts, and optional extensions like sampling and logging. If the server doesn't advertise a capability, the client won't request it.

Server capabilities object (from initialize response)
{
  "protocolVersion": "2024-11-05",
  "serverInfo": {
    "name": "my-document-server",
    "version": "1.0.0"
  },
  "capabilities": {
    "tools": { "listChanged": true },
    "resources": { "subscribe": false, "listChanged": false },
    "prompts": {}
  }
}

The listChanged field tells the client whether the server can notify it when the tool list changes at runtime (useful for servers that load plugins dynamically). subscribe on resources means the client can ask to be notified when a specific resource URI changes.

✓ What Just Happened?

The handshake is fully automatic when you use the MCP SDK — your tool functions are registered with the framework, which builds the capabilities object and handles all initialize/list messages before your code even runs. You only write the tool implementations.

The protocol is fully defined. Now let's ground it in reality — which major products have deployed MCP and what transport do they use?

Real-World Deployments

Product Role Transport Notes
Claude DesktopHost (+ client manager)stdioReads claude_desktop_config.json; spawns each server as a subprocess
CursorHost (+ client manager)stdio / HTTPSupports both; project-level .cursor/mcp.json config
WindsurfHoststdioEarly MCP adopter; global + workspace config
VS Code (Copilot Chat)Hoststdio / SSEMCP support added in VS Code 1.96 (Dec 2024)
GitHub CopilotHostHTTP/SSERemote servers; auth via GitHub OAuth
Cloudflare Workers AIServer deployment platformHTTP/SSEDeploy MCP servers to the edge with Workers
SupabaseServerHTTP/SSEOfficial Supabase MCP server (DB + auth tools)
GitHubServerHTTP/SSESearch repos, read files, create issues/PRs as tools
Claude CodeHoststdioConfig via .claude/settings.json mcpServers
Why It Matters

The GitHub MCP server is a production example worth studying. In a single server it exposes ~30 tools (search_repositories, create_pull_request, get_file_contents, list_issues, etc.) and ~20 resources (repository files, issue bodies, PR diffs). Any host that speaks MCP can use all of them — that's one server codebase serving Claude Desktop, Cursor, Windsurf, VS Code, and any custom agent simultaneously.

The pattern across all these deployments is the same: configure the host with the server's location (command + args for stdio, or URL for HTTP/SSE), and the capability negotiation handshake takes care of the rest. The host discovers the tools automatically — there's no manual "register this tool" step on the host side.

Knowledge Check

Five questions to verify you've got the protocol concepts solid before building your first server in MCP01.

1. In the MCP architecture, which component manages the connection to a specific server and handles capability negotiation?

A
The Host — it spawns and manages all server subprocesses
B
The Client — it is embedded in the host and manages exactly one server connection
C
The Server — it initiates the connection and registers itself with the host
D
The LLM — it calls tools directly using function calling

2. What is the correct JSON-RPC 2.0 method name for calling a tool?

A
tool.invoke
B
call_tool
C
tools/call
D
mcp.tools.execute

3. You want to expose the contents of a large SQL database table so the LLM can reference it as background context — but you don't want the LLM to write to it. Which MCP primitive is most appropriate?

A
Tool — because the LLM needs to actively fetch the data
B
Resource — read-only URI-addressed data injected into context
C
Prompt — server-managed template that loads the table data
D
There is no correct answer — MCP cannot expose database content

4. Your team wants to share one MCP server across 12 developers and also deploy it to a CI/CD pipeline. Which transport should you use?

A
stdio — it's simpler and has lower latency
B
HTTP/SSE — it supports multiple simultaneous clients and can be deployed to any HTTP host
C
Both are equally suitable — the MCP protocol makes them interchangeable
D
stdio supports multiple clients if you use the --multi-client flag

5. During the capability negotiation handshake, what does the listChanged: true field inside the tools capability object tell the client?

A
The server has more than one tool available
B
The client should re-fetch the tool list every 30 seconds
C
The server can send a notifications/tools/list_changed notification when its tool list changes at runtime
D
The tools list has changed since the client last connected