Module 7: Advanced MCP and Tool Integration
2. What is MCP (Model Context Protocol)
Overview
In the previous capsule you saw the problem: when an agent needs dozens of tools, hardcoding every integration doesn't scale. Every new API demands custom code, every update breaks something, and the agent turns into a fragile monolith. You need a standard — a protocol that lets any agent use any tool without a bespoke integration.
That standard is MCP (Model Context Protocol). Created by Anthropic in November 2024 and backed by Microsoft, OpenAI, and a growing community, MCP defines how an agent discovers and uses external tools through an open client-server protocol. The most precise analogy: MCP is to AI agents what USB is to computer peripherals. Before USB, every device needed its own port and driver. After USB, one standard port connects any device. MCP does the same for tools: one standard protocol connects any agent to any tool.
Connection with the module: This capsule is conceptual — you'll understand MCP's architecture, primitives and end-to-end flow without writing a full server. In capsule 03, you'll create your first MCP server. In capsule 04, you'll build an MCP client that discovers and uses that server's tools. By capsule 05, you'll integrate MCP into a LangGraph agent. Deep understanding first, hands on the code after.
The USB Analogy: Why a Standard Matters
Before USB (circa 1996), connecting a device to your computer was an adventure:
- Printer → parallel port (DB-25)
- Mouse → serial port (RS-232) or PS/2
- Keyboard → DIN or PS/2 port
- Modem → another serial port
- Scanner → SCSI (with its own card)
Every device needed its own connector, its own cable, its own driver. Peripheral makers had to support multiple ports. Computer makers had to include multiple connector types. It was unsustainable.
USB changed everything: one standard protocol, one connector type, plug-and-play. Peripheral makers only need to implement USB. Computer makers only need to include USB ports. Any device works with any computer.
Now look at the world of AI agents before MCP:
- GitHub access → custom integration with the GitHub API
- Web search → custom integration with Tavily or SerpAPI
- Reading files → custom integration with the filesystem
- Querying a database → custom integration with PostgreSQL
- Sending emails → custom integration with SendGrid
Every tool requires its own integration. If you switch from Tavily to Brave Search, you rewrite the tool. If the GitHub API changes, you update the agent's code. The agent is coupled to every tool it uses.
MCP is USB for agents. A standard protocol that defines how an agent discovers and uses tools. Tool providers implement an MCP server. Agent frameworks implement an MCP client. Any agent uses any tool — with no custom integration.
BEFORE MCP (direct coupling)
────────────────────────────────────────────
┌─────────┐ custom ┌──────────┐
│ │──────────────│ GitHub │
│ │ custom ├──────────┤
│ Agent │──────────────│ Tavily │
│ │ custom ├──────────┤
│ │──────────────│ Files │
│ │ custom ├──────────┤
│ │──────────────│ DB │
└─────────┘ └──────────┘
Every integration is custom code.
Changing a tool = changing the agent.
AFTER MCP (standard protocol)
────────────────────────────────────────────
┌─────────┐ ┌──────────────┐
│ │ MCP │ MCP Server │
│ Agent │◄────────────►│ (GitHub) │
│ (MCP │ MCP ├──────────────┤
│ Client)│◄────────────►│ MCP Server │
│ │ MCP │ (Search) │
│ │◄────────────►├──────────────┤
│ │ MCP │ MCP Server │
│ │◄────────────►│ (Files) │
└─────────┘ └──────────────┘
One standard protocol for every tool.
Adding/changing a tool = deploying a new server.
One important detail: MCP is a standard, not a product. It doesn't belong to Anthropic in the same way USB doesn't belong to Intel. Intel led the creation of USB, but the standard is open and any manufacturer can implement it. Anthropic created MCP, but the protocol is open (MIT license, public spec) and any framework can adopt it — and they are. OpenAI integrated it into their SDK, Microsoft adopted it in Copilot, LangChain has native support. That broad adoption is what makes it valuable: investing in learning MCP isn't betting on a vendor, it's learning an industry standard.
The MCP Architecture
MCP defines four components that interact in a client-server model:
┌───────────────────────────────────────────────────────────────┐
│ HOST │
│ (your application: Claude Desktop, IDE, your custom app) │
│ │
│ ┌───────────────┐ ┌───────────────┐ ┌─────────────┐ │
│ │ MCP Client 1 │ │ MCP Client 2 │ │ MCP Client 3│ │
│ └───────┬───────┘ └───────┬───────┘ └──────┬──────┘ │
│ │ │ │ │
└───────────┼────────────────────┼────────────────────┼──────────┘
│ Transport │ Transport │ Transport
│ (stdio) │ (HTTP) │ (stdio)
▼ ▼ ▼
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ MCP Server │ │ MCP Server │ │ MCP Server │
│ (Filesystem)│ │ (GitHub) │ │ (Database) │
│ │ │ │ │ │
│ Tools: │ │ Tools: │ │ Tools: │
│ - read_file │ │ - list_repos│ │ - query │
│ - write_file│ │ - create_pr │ │ - insert │
│ Resources: │ │ - get_issue │ │ Resources: │
│ - file:// │ │ Resources: │ │ - db:// │
└──────────────┘ │ - repo:// │ └──────────────┘
└──────────────┘
Host
The host is the application that hosts the agent. It's the main process where your application's logic lives. Examples:
- Claude Desktop — Anthropic's desktop app that connects Claude to local MCP servers
- Cursor / VS Code — IDEs that use MCP to give tools to the AI assistant
- Your custom application — A Python script, a web service, a CLI that runs an agent
The host is responsible for:
- Creating and managing MCP client instances
- Controlling permissions (which servers each client can use)
- Handling the lifecycle of the connections
A host can have multiple clients, each connected to a different server. Your application can connect to the filesystem server, the GitHub server and the database server at the same time.
Client
The client is the intermediary between the host and a server. Each client maintains a 1:1 connection with a specific server. Its responsibilities:
- Connect to the server through the configured transport (stdio or HTTP)
- Discover which tools, resources and prompts the server offers (
list_tools,list_resources,list_prompts) - Invoke tools and resources when the agent requests it (
call_tool,read_resource) - Keep the session alive with the server
The client doesn't decide which tools to use — the LLM does. The client only facilitates communication between the agent and the server.
Server
The server is where the tools live. It's a process (local or remote) that exposes capabilities through the MCP protocol. A server can expose three kinds of primitives:
- Tools — Functions the agent can invoke (e.g.
read_file,web_search,create_pr) - Resources — Data the agent can read (e.g. file contents, DB schemas, documents)
- Prompts — Reusable templates for common interactions
The server is independent of the agent. You can build an MCP server for PostgreSQL that any agent can use — it doesn't matter whether the agent is in LangGraph, CrewAI, AutoGen, or is a custom script. The server only knows MCP; it doesn't know or care which framework the agent uses.
Transport
The transport is the communication layer between client and server. MCP defines two standard transports:
- stdio — Communication over stdin/stdout (local processes)
- Streamable HTTP — Communication over HTTP (remote servers)
You'll see each transport in detail in the Transports section.
The Three MCP Primitives
MCP defines three kinds of capabilities a server can expose. Each primitive has a specific purpose and a distinct interaction model.
Tools: Actions the Agent Executes
Tools are functions the agent can invoke to perform actions or computations. They're the most used and most familiar primitive — they're the "tools" the agent has at its disposal.
Characteristics:
- Discoverable: The client asks the server "what tools do you have?" and receives the list with names, descriptions and JSON schemas for the parameters
- Invocable: The agent (via the LLM) decides when and with what arguments to call each tool
- Typed: Each tool defines its input schema (JSON Schema), which enables automatic validation
- Model-controlled: The LLM decides when to use a tool based on the description and the context
Tool: "web_search"
├── Description: "Search for current information on the internet"
├── Input Schema:
│ ├── query (string, required): "Search term"
│ └── max_results (integer, optional): "Maximum number of results"
└── Output: Text with the search results
MCP tools are conceptually identical to OpenAI function calls (M2-M3). The difference is that in function calling, you define the tools in the agent's code. In MCP, the tools live on an external server and the agent discovers them dynamically.
Resources: Data the Agent Reads
Resources are data sources the agent can read. They're like read endpoints — the agent doesn't execute an action, it accesses information.
Characteristics:
- Identified by URI: Each resource has a unique URI (e.g.
file:///home/user/doc.txt,db://users/schema) - Reading without side effects: Accessing a resource doesn't modify anything — it just returns data
- Application-controlled: The host application decides when to read a resource, not the LLM directly
- Typed by MIME: Each resource declares its MIME type (
text/plain,application/json, etc.)
Resource: "file:///home/user/notes.txt"
├── Name: "notes.txt"
├── MIME: "text/plain"
└── Content: (the file's text)
Resource: "db://products/schema"
├── Name: "Products Table Schema"
├── MIME: "application/json"
└── Content: {"columns": ["id", "name", "price"], ...}
The distinction between tools and resources matters: a tool modifies something or performs a computation (it has side effects), a resource only provides data (no side effects). web_search is a tool (it runs a search). file:///config.json is a resource (it just reads a file).
Prompts: Reusable Templates
Prompts are predefined templates for common interactions. They're the least used primitive but useful for standardizing how an agent interacts with a server.
Characteristics:
- Templates with arguments: A prompt can have parameters that get filled in when it's used
- User-controlled: The user or the application selects which prompt to use, not the LLM
- Reusable: A prompt is defined once on the server and any client can use it
Prompt: "summarize_document"
├── Description: "Summarize a document into N key points"
├── Arguments:
│ ├── document_uri (string): "URI of the document to summarize"
│ └── num_points (integer): "Number of key points"
└── Generates: Messages with summarization instructions + the document's content
In practice, most MCP servers mainly expose tools and some resources. Prompts are more common in UX-oriented servers (like the ones that integrate with Claude Desktop) where the user picks a predefined action.
Summary of the Three Primitives
| Primitive | Purpose | Who controls it | Analogy |
|---|---|---|---|
| Tools | Execute actions | The LLM decides when to use them | Functions the agent can call |
| Resources | Read data | The application decides when to read them | GET endpoints of a REST API |
| Prompts | Interaction templates | The user picks which one to use | Predefined macros or shortcuts |
Transports: stdio vs HTTP
The transport defines how client and server communicate. MCP supports two standard transports, each optimized for a different scenario.
stdio: Local Processes
In the stdio transport, the client launches the server as a subprocess and they communicate through stdin/stdout. The client writes JSON-RPC messages to the server's stdin and reads responses from stdout.
┌──────────┐ stdin (JSON-RPC) ┌──────────────┐
│ Client │─────────────────────►│ MCP Server │
│ │◄─────────────────────│ (subprocess) │
└──────────┘ stdout (JSON-RPC) └──────────────┘
Characteristics:
- The server runs as a child process of the client
- Ultrafast communication (no network in between)
- No need for authentication (same user, same machine)
- The client manages the server's lifecycle (launches it and shuts it down)
When to use it:
- Local development: your agent and the server run on your machine
- Tools that access local resources (filesystem, local databases)
- Integration with Claude Desktop, Cursor, or other IDEs
- When minimal latency is a priority
Typical configuration (in Claude Desktop):
{
"mcpServers": {
"filesystem": {
"command": "python",
"args": ["mcp_filesystem_server.py"],
"env": {
"ALLOWED_DIRS": "/home/user/projects"
}
}
}
}
The host reads this configuration, runs python mcp_filesystem_server.py as a subprocess, and the client communicates with it through stdin/stdout.
Streamable HTTP: Remote Servers
In the Streamable HTTP transport (successor to SSE), the server is an HTTP service reachable over the network. The client sends HTTP requests and receives responses, with streaming support via Server-Sent Events.
┌──────────┐ HTTP POST/GET ┌──────────────┐
│ Client │─────────────────────►│ MCP Server │
│ │◄─────────────────────│ (remote) │
└──────────┘ HTTP Response/SSE └──────────────┘
(can cross the network)
Characteristics:
- The server runs independently of the client (it can be on another continent)
- Communication over standard HTTP (traverses firewalls, load balancers, proxies)
- Requires authentication (the server is reachable over the network)
- The server has its own lifecycle (deployment, monitoring, scaling)
When to use it:
- Production: servers deployed as independent services
- Tools that access remote resources (cloud APIs, SaaS services)
- Teams where multiple agents share the same servers
- When you need authentication, rate limiting, or centralized monitoring
Transport Comparison
| Aspect | stdio | Streamable HTTP |
|---|---|---|
| Server location | Same process/machine | Anywhere with a network |
| Latency | Microseconds | Milliseconds (network) |
| Authentication | Not needed | Required (tokens, OAuth) |
| Lifecycle | Client launches/shuts down the server | The server is independent |
| Scalability | One server per client | One server, many clients |
| Deployment | python server.py | Docker, Kubernetes, cloud |
| Debugging | Simple (local logs) | Requires observability |
| Ideal for | Development, local tools | Production, shared tools |
Practical rule: Start with stdio for development and prototyping. Migrate to Streamable HTTP when you need to share the server across teams or deploy it to production. The server's code barely changes — only the transport does.
The End-to-End Flow
What happens between the user asking a question and the agent answering using an MCP tool? Here's the full flow step by step:
Phase 1: Initialization (happens once at startup)
1. The Host starts
└─► Reads the MCP server configuration
2. For each configured server:
└─► Creates an MCP Client
└─► Establishes a connection through the transport (stdio or HTTP)
└─► Sends: "initialize" (protocol version, capabilities)
└─► Receives: server info + supported capabilities
3. Each Client discovers the server's capabilities:
└─► Sends: "tools/list"
└─► Receives: list of tools with names, descriptions, schemas
└─► Sends: "resources/list"
└─► Receives: list of resources with URIs and types
└─► Sends: "prompts/list"
└─► Receives: list of prompts with arguments
At the end of initialization, the host knows exactly which tools are available from each server. The tool schemas turn into function definitions the LLM can understand.
Phase 2: Interaction (happens on every agent turn)
┌──────────────────────────────────────────────────────────────────┐
│ │
│ 1. USER │
│ "Find the open issues in my repo and write a summary" │
│ │ │
│ ▼ │
│ 2. HOST / AGENT │
│ Prepares messages + tool definitions (from every server) │
│ │ │
│ ▼ │
│ 3. LLM (GPT-4.1, Claude, etc.) │
│ Reasons: "I need list_issues from the GitHub server" │
│ Returns: tool_call = {name: "list_issues", args: {repo: …}} │
│ │ │
│ ▼ │
│ 4. HOST │
│ Identifies that "list_issues" belongs to the GitHub MCP Client│
│ │ │
│ ▼ │
│ 5. MCP CLIENT (GitHub) │
│ Sends to the server: "tools/call" {name: "list_issues", args: …}│
│ │ │
│ ▼ (Transport: stdio or HTTP) │
│ │
│ 6. MCP SERVER (GitHub) │
│ Runs the logic: calls the GitHub API │
│ Returns: result (list of issues in JSON) │
│ │ │
│ ▼ │
│ 7. MCP CLIENT │
│ Receives the result, converts it into a ToolMessage │
│ │ │
│ ▼ │
│ 8. HOST / AGENT │
│ Appends the ToolMessage to the history │
│ Does the LLM need more tools? → repeat from step 3 │
│ Does it have enough? → generate the final answer │
│ │ │
│ ▼ │
│ 9. ANSWER TO THE USER │
│ "You have 12 open issues. Here's the summary: ..." │
│ │
└──────────────────────────────────────────────────────────────────┘
What makes this powerful
Notice what doesn't happen in this flow:
- The agent doesn't need to know in advance which tools exist — it discovers them from the server
- The agent has no GitHub-specific code — the MCP server encapsulates all that logic
- If tomorrow you switch from GitHub to GitLab, you deploy a new MCP server with the same tools but a different implementation — the agent doesn't change
- If you need a new tool (e.g.
create_branch), you add it to the server — the agent discovers it automatically on the nexttools/list
This is dynamic tool discovery — and it's MCP's "wow" moment. Your agent didn't know create_branch existed 5 minutes ago. It discovered it from the MCP server and used it correctly, because the schema told the LLM which arguments it needs.
Why MCP Matters
1. Standardization
Without MCP, every framework invents its own way of integrating tools. LangChain has @tool, CrewAI has its own API, AutoGen has another. If you build a tool for LangChain, you have to adapt it for CrewAI. With MCP, you build a server once and any framework with an MCP client can use it.
2. Decoupling
The agent and the tools evolve independently. The infrastructure team can update the PostgreSQL MCP server without touching the agent's code. The AI team can switch from GPT-4.1 to Claude without touching the servers. Each piece has its own release cycle.
3. Ecosystem
Hundreds of community MCP servers already exist: GitHub, Slack, PostgreSQL, filesystem, Brave Search, Google Drive, Notion, and more. Instead of writing integrations from scratch, you connect your agent to existing servers. It's like npm for agent tools.
4. Independent Deployment
Each MCP server is deployed as an independent service: its own container, its own configuration, its own secrets, its own monitoring. You don't need to redeploy the agent to add a tool. You don't need to give the agent access to every service's credentials — each server handles its own.
5. Security by Design
MCP separates permissions per server. The filesystem server only has access to specific directories. The database server only has access to certain tables. The agent has no direct access to anything — it can only invoke the tools the servers expose, with the permissions the servers define.
MCP vs Alternatives
MCP isn't the only way to connect agents to tools. Understanding the alternatives helps you appreciate which problem MCP solves and when another option might be a better fit.
Detailed Comparison
| Aspect | MCP | OpenAPI / Swagger | Custom Integrations | LangChain Tools |
|---|---|---|---|---|
| What it is | Protocol for agents ↔ tools | Spec for HTTP REST APIs | Ad hoc code per integration | LangChain abstractions |
| Designed for | AI agents | Web APIs in general | A specific case | Agents in LangChain |
| Dynamic discovery | Yes (tools/list) | Partial (static spec) | No (hardcoded) | No (defined in code) |
| Primitives | Tools + Resources + Prompts | HTTP endpoints | Whatever you implement | Tool (function + schema) |
| Transport | stdio + HTTP | HTTP only | Variable | In-process (Python) |
| Ecosystem | Growing (100+ servers) | Massive (any REST API) | None | LangChain Hub |
| Vendor lock-in | No (open spec) | No (open spec) | Yes (your own code) | Yes (LangChain) |
| Streaming | Native (SSE) | Requires implementation | Variable | Via callbacks |
| Complexity | Medium (client + server) | Low (HTTP requests) | High (each integration) | Low (@tool decorator) |
MCP vs OpenAPI/Swagger
OpenAPI defines how to document and consume REST APIs. It's mature, ubiquitous, and battle-tested. But it's designed for generic web APIs, not for AI agents.
Key differences:
- OpenAPI describes HTTP endpoints. MCP describes capabilities for agents (tools with LLM-oriented descriptions, resources with semantic URIs, prompts).
- OpenAPI requires the agent to understand HTTP, status codes, per-endpoint authentication. MCP abstracts all that away — the agent only sees tools with schemas.
- OpenAPI has no dynamic runtime discovery. MCP does:
tools/listcan be called at any moment to discover new tools. - OpenAPI doesn't define a local transport (stdio). MCP does, which enables ultrafast local tools.
When should you choose OpenAPI? When the API already exists, is stable, and you don't need dynamic discovery. Many frameworks can consume OpenAPI APIs directly.
MCP vs Custom Integrations
Custom integrations are ad hoc code connecting an agent to a specific service. It's what you did in M2-M3: a @tool decorator with specific logic.
Key differences:
- Custom works with 5 tools. With 50, it's unsustainable.
- Custom couples the agent to the service. MCP decouples.
- Custom doesn't allow reuse across projects. MCP does.
- Custom is faster for a prototype. MCP is better in the long run.
When should you choose custom? For quick prototypes, tools only your agent will use, or logic so specific it makes no sense to abstract it into a server.
MCP vs LangChain Tools (@tool decorator)
LangChain tools via @tool are Python functions the agent invokes directly. They're what you've used throughout the guide so far.
Key differences:
@toolruns in-process (the same Python runtime as the agent). MCP runs out-of-process (a separate server).@toolis defined in the agent's code. MCP tools are defined on the server.@toolrequires redeploying the agent to add a tool. MCP doesn't.@toolis simpler to start with. MCP scales better.
When should you choose @tool? For simple tools that don't need independent deployment: calculations, formatting, business logic internal to the agent.
The Natural Progression
Think of this as an evolution, not as mutually exclusive options:
M2: @tool → hardcoded, 5 tools, works fine
│
▼
M3: Function calling → invocation patterns
│
▼
M7: MCP → standard protocol, dynamic tools, independent deployment
In production, a typical agent combines both: simple tools with @tool (formatting, calculations) + complex tools via MCP servers (external APIs, databases, shared services).
Connection with the Project
The Research Agent you've built in M4-M6 uses hardcoded tools: web_search, save_research, etc. In this module, those tools migrate to MCP servers:
- Filesystem MCP Server — Replaces the
save_researchtool. The agent reads and writes research files through the standard protocol. - Web Search MCP Server — Replaces the hardcoded
web_searchtool. If tomorrow you want to switch from Tavily to Brave Search, you deploy a new server without touching the agent. - Paper Database MCP Server — A custom server exposing search over an academic paper database. It shows you how to create servers for your own data sources.
The real advantage: after M7, adding a new tool to the Research Agent doesn't require modifying the agent's code. You just deploy a new MCP server and the agent discovers it automatically.
In capsule 03, you'll create the Filesystem MCP Server. In 04, you'll build the MCP client that consumes it. In 05, you'll integrate both into the Research Agent's StateGraph.
Troubleshooting
Problem 1: The server doesn't respond to initialization
Symptom: The client tries to connect to the server over stdio and gets no response. It hangs or times out.
Likely cause: The server is printing logs or other output to stdout, interfering with the JSON-RPC messages.
Solution: In stdio servers, all debugging output must go to stderr, never to stdout. stdout is exclusively for MCP messages.
import sys
# DON'T do this in a stdio MCP server:
print("Server started") # Pollutes stdout
# Do this instead:
print("Server started", file=sys.stderr) # Goes to stderr, doesn't interfere
Problem 2: The agent doesn't see the server's tools
Symptom: The agent connects to the server successfully, but discovers no tools. It answers without using any.
Likely cause: The server's tools aren't being passed to the LLM as tool definitions. The MCP connection works, but the model's bind_tools doesn't include the MCP tools.
Solution: After tools/list, the discovered tools have to be converted to LangChain format (StructuredTool or equivalent) and passed to the model with bind_tools. That conversion step is the responsibility of the client or of the integration code.
Problem 3: Timeout on slow tools
Symptom: A tool that takes more than 30 seconds (e.g. web scraping multiple pages) fails with a timeout.
Likely cause: The transport has a default timeout shorter than the tool's execution time.
Solution: Configure appropriate timeouts on the client and on the server. For inherently slow tools, consider: (1) splitting them into smaller sub-operations, (2) returning a "job_id" the agent can check later, or (3) increasing the specific transport's timeout.
Problem 4: Name collision between servers
Symptom: Two servers expose a tool with the same name (e.g. both have search). The agent uses the wrong one.
Likely cause: The host isn't namespacing tools per server. search from the GitHub server and search from the Web server collide.
Solution: Use namespacing when registering the tools with the LLM: github__search and web__search. This is the host/client's responsibility, not the server's. Several MCP clients do this automatically.
Problem 5: The server fails silently
Symptom: The tool call returns an empty result or a generic error. There's no information about what failed.
Likely cause: The server catches exceptions internally and returns a generic message instead of propagating the error with detail.
Solution: MCP servers should return descriptive errors. In production, add structured logging to the server (stderr in stdio, normal logs in HTTP) to diagnose failures without depending on the protocol's error message.
Exercises
Exercise 1: Map the MCP components
Given this scenario: "A developer uses Cursor IDE to ask the AI assistant to find bugs in their GitHub repo and create an issue for each one," identify which MCP component each part is:
- Cursor IDE = ?
- The module inside Cursor that connects to the GitHub server = ?
- The process that exposes the
list_issues,create_issuetools = ? - The communication between Cursor and that process = ?
View solution
- Cursor IDE = Host. It's the main application that hosts the agent (Cursor's AI assistant).
- The module that connects to the GitHub server = Client. It maintains the 1:1 connection with the GitHub MCP server, sends requests and receives responses.
- The process that exposes
list_issues,create_issue= Server. It implements the GitHub logic and exposes it as MCP tools. - The communication between the two = Transport. If the server runs locally as a subprocess, it's stdio. If it runs on a remote server, it's Streamable HTTP.
The full chain: Host (Cursor) → Client (MCP module) → Transport (stdio/HTTP) → Server (GitHub tools).
Exercise 2: Classify the primitives
Classify each item as a Tool, Resource or Prompt:
web_search(query)— Searches for information on the internetfile:///home/user/config.json— Contents of the configuration filesummarize_code— Template that takes a file and generates a summarycreate_pull_request(title, body, branch)— Creates a PR on GitHubdb://users/schema— Schema of the users tabledebug_error— Template that takes a stack trace and suggests fixes
View solution
- Tool —
web_search(query): Executes an action (searching the internet), has side effects (makes network requests), the LLM decides when to use it. - Resource —
file:///config.json: It's a URI that provides data (the file's contents), no side effects, read only. - Prompt —
summarize_code: It's an interaction template that generates predefined messages for a common task. - Tool —
create_pull_request(...): Executes an action (creating a PR on GitHub), has side effects (modifies the repo). - Resource —
db://users/schema: It's a URI that provides data (a table schema), no side effects. - Prompt —
debug_error: It's a template that takes input (a stack trace) and generates predefined messages to guide the debugging.
Quick rule: If it executes an action → Tool. If it provides data through a URI → Resource. If it's a message template → Prompt.
Exercise 3: Choose the transport
For each scenario, say whether you'd use stdio or Streamable HTTP and justify it:
- A local agent that reads/writes files on your laptop
- A web search service shared across 20 agents in a company
- A PostgreSQL database MCP server running in the same Kubernetes pod as the agent
- Development and debugging of a new MCP server
- An MCP server that needs OAuth authentication and rate limiting
View solution
-
stdio — The filesystem is local, you don't need a network. stdio is faster and requires no authentication. The server runs as a subprocess of the agent.
-
Streamable HTTP — Multiple agents need to reach the same server. HTTP allows a centralized server with authentication, rate limiting, and monitoring. Deploy it as a service.
-
stdio or HTTP, it depends. If they're in the same pod and communicate over localhost, HTTP has minimal latency. If you can launch the server as a sidecar container and communicate through stdin/stdout, stdio works. In Kubernetes, HTTP is more idiomatic.
-
stdio — For development, stdio is simpler: you run the server locally, see logs on stderr, iterate fast. You don't need to configure HTTP, ports, or authentication.
-
Streamable HTTP — OAuth and rate limiting are HTTP features. stdio has no concept of authentication because the server runs as a subprocess of the user (it's already implicitly authenticated).
Exercise 4: Design an MCP server
Design (without code) an MCP server for a corporate email service. Define:
- The server's name
- 3 tools with their parameters
- 2 resources with their URIs
- 1 prompt with its arguments
View solution
Server: corporate-email
Tools:
send_email(to: string, subject: string, body: string, cc?: string[])— Sends an email from the user's corporate account. Side effect: it sends the email.search_emails(query: string, folder?: string, max_results?: int)— Searches emails by content, subject, or sender. Returns a list of emails with metadata.move_to_folder(email_id: string, target_folder: string)— Moves an email to a specific folder. Side effect: it modifies the mailbox.
Resources:
email://inbox/unread— List of unread emails in the inbox. MIME:application/json. Read only.email://folders— List of all available folders with email counts. MIME:application/json. Read only.
Prompt:
compose_reply(email_id: string, tone: string)— Template that loads the original email and the conversation thread, and generates instructions for the LLM to draft a reply with the given tone (formal, casual, concise). Arguments:email_id(the email to reply to),tone(the reply's style).
Reasoning: The tools have side effects (sending, moving). The resources are pure reads (inbox, folders). The prompt is a reusable template for a common task (replying to emails).
Exercise 5: Trace the end-to-end flow
A user tells an agent: "Read the README.md of my project and create a GitHub issue with the missing sections." The agent has access to two MCP servers: Filesystem and GitHub. Trace the full flow: which tool calls the agent makes, in what order, which server each one goes to, and what decisions the LLM makes.
View solution
Phase 0 — Initialization (already happened):
- Client 1 connected to the Filesystem MCP Server → discovered tools:
read_file,write_file,list_directory - Client 2 connected to the GitHub MCP Server → discovered tools:
list_issues,create_issue,get_repo_info
Phase 1 — First iteration of the loop:
- The user sends: "Read README.md and create an issue with the missing sections"
- The LLM receives the message + the list of 6 available tools (3 from each server)
- The LLM reasons: "First I need to read the file" → decides to call
read_file - Tool call:
read_file(path="README.md")→ goes to Client 1 → Filesystem Server - The server reads the file, returns the content
- A ToolMessage with the README.md content is appended to the history
Phase 2 — Second iteration of the loop:
7. The LLM receives the README's content, reasons: "The README has no Installation, Contributing or License section"
8. The LLM decides: create an issue with this information → calls create_issue
9. Tool call: create_issue(title="Add missing sections to the README", body="The current README doesn't include:\n- Installation\n- Contributing\n- License") → goes to Client 2 → GitHub Server
10. The server creates the issue through the GitHub API, returns the issue's URL
11. A ToolMessage with the issue's URL is appended to the history
Phase 3 — Third iteration (final answer): 12. The LLM sees it has all the information it needs 13. It makes no more tool calls → generates the answer for the user 14. "I read your README.md. It's missing the Installation, Contributing and License sections. I created issue #42 in your repo: https://github.com/..."
The LLM's key decisions:
- It decided to read the file before creating the issue (it needs the content to know what's missing)
- It chose
read_filefrom the Filesystem server, notget_repo_infofrom the GitHub server - It wrote the issue's body based on its analysis of the README
- It decided not to make more tool calls after creating the issue (task complete)
Summary
- MCP (Model Context Protocol) is an open standard for connecting AI agents to external tools. Created by Anthropic, backed by Microsoft and OpenAI, MIT licensed. It isn't a product — it's a protocol any framework can implement.
- The USB analogy is key: before USB, every peripheral needed its own port. Before MCP, every tool needed its own integration. MCP standardizes the agent-tool connection the way USB standardized the computer-peripheral connection.
- The architecture has four components: Host (your app), Client (a 1:1 connection with a server), Server (exposes tools), Transport (stdio for local, HTTP for remote).
- Three primitives define the capabilities: Tools (actions the LLM invokes), Resources (read-only data), Prompts (reusable templates). In practice, tools are the most used.
- Two standard transports: stdio for local development (subprocesses, ultrafast, no auth), Streamable HTTP for remote production (network, authentication, scalable).
- Dynamic tool discovery is the superpower: the agent doesn't need to know in advance which tools exist. It discovers them from the server at runtime and uses them based on their schemas. Adding a tool = updating the server, without touching the agent.
- MCP complements, it doesn't replace other approaches. Simple tools with
@toolfor internal logic, MCP servers for external integrations that need independent deployment. - The evolution from M2 to M7: hardcoded tools (M2) → function calling patterns (M3) → a standard protocol with dynamic discovery (M7). Each step solves the previous one's pain point.
Next capsule: MCP Servers: Exposing Tools — you'll create your first MCP server with the official SDK, define tools with schemas, and run it locally over stdio.
Additional Resources
- MCP Specification — The complete formal specification of the protocol
- MCP Introduction — Official introduction with architecture and concepts
- Anthropic MCP Announcement — The original blog post announcing MCP
- MCP Python SDK — Official SDK for creating servers and clients in Python
- MCP Servers Registry — Official repository with community servers
- OpenAI MCP Support — OpenAI's announcement integrating MCP into their SDK