Phase 5: Model Context Protocol (MCP) · 60 min · Python · MCP SDK · Anthropic SDK
The Problem
You build an AI assistant for a company. It needs to read Jira tickets, write to Slack, query a customer database, and call an internal deployment API. Without a standard, you write custom integration code for every tool in every agent. When you add a second agent six months later, you write the integrations again. When Claude upgrades its API, you update all of them.
This is the integration tax that every team building AI products paid from 2022 to 2025. Each model had its own function-calling format. Each tool integration was bespoke. The AI code was tangled with the business logic of each connected system.
Model Context Protocol (MCP) is Anthropic's answer: a standard protocol for connecting AI models to external tools and data. Instead of integrations that only work with one model or one framework, you build a tool once as an MCP server, and any MCP-compatible host — Claude Desktop, Cursor, your custom agent — can use it without modification.
By 2026, MCP had become the de facto standard: 13,000+ servers on GitHub, native support in Claude Desktop, Cursor, and Windsurf, and compatibility announcements from OpenAI and Google. It's the USB-C of AI tool integration.
Three primitives, one protocol
MCP standardizes three types of capabilities: tools (model-controlled function calls), resources (host-loaded context data exposed as URIs), and prompts (user-selected reusable templates). Understanding which primitive fits which use case is the first architectural decision in every MCP implementation.
MCP architecture
The protocol separates four distinct roles:
Host — the AI application. Claude Desktop, Cursor, your custom Python agent. The host is the process the user interacts with.
Client — embedded inside the host. The client manages the connection to one or more MCP servers, handles the protocol handshake, and routes messages.
Server — a separate process that exposes capabilities. A filesystem server exposes read_file. A GitHub server exposes search_repositories. Each server focuses on one domain.
Transport — how client and server communicate. Two options:
stdio: the server runs as a subprocess; communication happens over stdin/stdout. Used for local tools in Claude Desktop.- Streamable HTTP (Nov 2025 spec): the server runs as a web service. Used for shared infrastructure, remote servers, and production deployments.
The protocol is JSON-RPC 2.0: every message has jsonrpc: "2.0", a method (like tools/list or tools/call), params, and an id for matching responses to requests.
What is the difference between an MCP "tool" and an MCP "resource"?
Control is the key distinction. Tools are like function calls — the model invokes them when it decides they're needed. Resources are like context files — the HOST decides to include them in the context window. A database query is a tool (the AI decides when to query). A README file is a resource (the host loads it as context when relevant).
The three MCP primitives
Tools are model-controlled. The AI decides when to call them, just like function calling — but standardized. A read_file tool, a query_database tool, a send_email tool. The model sees tool definitions (name, description, input schema) and decides which to call based on the user's request.
Resources are application-controlled. They're context data exposed as URIs: file://project/README.md, db://customers/123, git://repo/main/src/app.py. The host (not the model) decides which resources to load into the context window based on what's relevant. Resources answer "what should the model know?" before it starts reasoning.
Prompts are user-controlled. They're reusable prompt templates that users select from the host interface — like slash commands. write-tests, explain-code, summarize-pr. The user picks one; the host fills it in with relevant context and sends it to the model.
Most production MCP servers primarily implement tools. Resources are used for background context loading. Prompts are the least common primitive.
| Primitive | Controlled by | Analogy | Example |
|---|
| Tools | Model | Function calls | query_database, send_email |
| Resources | Host | Context files | file://README.md, db://customers/123 |
| Prompts | User | Slash commands | /write-tests, /explain-code |
The MCP protocol uses what underlying message format?
MCP is built on JSON-RPC 2.0. Each message has: jsonrpc: "2.0", a method (like tools/call), params, and an id for matching responses. This makes MCP servers easy to implement in any language that can handle JSON — no special libraries required for the transport layer itself.
The MCP ecosystem
Anthropic ships first-party MCP servers for the most common integrations: filesystem, GitHub, Google Drive, Slack, Postgres, Brave Search, Puppeteer. Install them with npm and configure in claude_desktop_config.json.
The configuration file lives at ~/Library/Application Support/Claude/claude_desktop_config.json on macOS. It tells Claude Desktop which MCP servers to start and how:
{
"mcpServers": {
"filesystem": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/Users/me/Desktop"],
"env": {}
}
}
}
Restart Claude Desktop after editing. The tool icon in the chat interface shows which MCP tools are available.
Build
Build it
Before touching the real MCP SDK, it helps to see the core idea in plain Python: a registry that separates what a capability is called from how it runs, and a router that picks the right one by name — the same shape as an MCP server's tools/list and tools/call handlers.
# A minimal capability registry, mirroring how an MCP server routes tool calls.
registry = {}
def register(name, description, fn):
registry[name] = {"description": description, "fn": fn}
def list_tools():
return [{"name": n, "description": t["description"]} for n, t in registry.items()]
def call_tool(name, **kwargs):
if name not in registry:
return {"error": f"unknown tool: {name}"}
return {"result": registry[name]["fn"](**kwargs)}
# Register two toy "tools"
register("add", "Add two numbers", lambda a, b: a + b)
register("shout", "Uppercase a string", lambda text: text.upper())
print("tools/list ->")
for tool in list_tools():
print(" ", tool)
print("\ntools/call add ->", call_tool("add", a=3, b=4))
print("tools/call shout ->", call_tool("shout", text="mcp is neat"))
print("tools/call missing ->", call_tool("delete_everything"))
The code artifact lesson_05_01_mcp_intro.py demonstrates:
- The structure of JSON-RPC 2.0 messages that MCP uses under the hood (
tools/list request and response, tools/call request and response) - A minimal echo MCP server using the Python MCP SDK (
@server.list_tools(), @server.call_tool() decorators) - Claude Desktop config examples for mounting the echo server and the Anthropic filesystem server
- A snapshot of the MCP ecosystem: first-party servers, compatible hosts, and ecosystem scale
Run it:
pip install mcp anthropic
python lesson_05_01_mcp_intro.py