Module 8: MCP Servers
|
Part 1: How It Works
Every module so far has extended Claude Code from the inside: rules in CLAUDE.md, permission gates, lifecycle hooks, invocable skills.
MCP flips the direction.
Instead of configuring Claude Code’s internal behavior, you connect it to external services that expose new capabilities.
What MCP Is
MCP — the Model Context Protocol — is an open standard for connecting AI assistants to external services. The protocol defines how a client (Claude Code) communicates with a server (your service) to discover and invoke tools.
The relationship is simple:
-
Claude Code is an MCP client. It discovers available tools, decides when to call them, and processes the results.
-
Your service is an MCP server. It registers tools with names, descriptions, and input schemas. When Claude calls a tool, the server executes it and returns a result.
(MCP Client)"] <-->|"MCP Protocol"| MS["MCP Server"] MS <--> ES["External Service
(DB, API, filesystem, etc.)"] style CC fill:#e8f4f8,stroke:#2c3e50 style MS fill:#fef9e7,stroke:#2c3e50 style ES fill:#d5f5e3,stroke:#2c3e50
From Claude Code’s perspective, MCP tools are just tools.
They appear in the same tool dispatch system you studied in Module 3.
The model reasons about them the same way it reasons about built-in tools like Bash, Read, or Edit.
The difference is where the execution happens: built-in tools run inside Claude Code, MCP tools run in an external process.
Three Transport Types
MCP supports three transport mechanisms. The transport determines how the client and server communicate.
| Transport | Mechanism | When to Use |
|---|---|---|
|
Server runs as a local subprocess. Client communicates via standard input/output. |
Most common. Local tools, file access, scripts. No network required. The server starts when Claude Code starts and dies when Claude Code exits. |
|
Server runs as a remote HTTP service. Client sends requests over the network. |
Shared services, cloud APIs, team-wide servers. The server runs independently of Claude Code. |
|
Server-Sent Events over HTTP. Client receives streaming updates. |
Streaming data, real-time connections, long-running operations where incremental results matter. |
For local development, stdio is almost always what you want.
The server is a subprocess that Claude Code manages directly — no ports to configure, no network to debug.
Configuration Scopes
MCP servers are registered in configuration files. Three scopes control where a server is visible:
Project scope: .mcp.json
Committed to git. Shared with the team. Every developer who clones the repository gets the same MCP servers.
{
"mcpServers": {
"fetch": {
"command": "npx",
"args": ["@anthropic-ai/mcp-fetch"]
}
}
}
User scope: ~/.claude.json
Personal, global. Available in every project. Not committed to any repository.
{
"mcpServers": {
"my-private-server": {
"command": "python",
"args": ["-m", "my_server"]
}
}
}
Local scope: .claude/settings.local.json
Per-project personal overrides. Not committed to git (the .claude/ directory is typically gitignored). Use this when you need a server for a specific project but do not want to impose it on the team.
{
"mcpServers": {
"local-db": {
"command": "npx",
"args": ["@anthropic-ai/mcp-postgres", "postgresql://localhost/dev"]
}
}
}
The scoping model mirrors what you learned about CLAUDE.md in Module 2 and settings in Module 4: project-level for the team, user-level for personal preferences, local-level for per-project personal overrides.
Adding Servers
Three methods to register an MCP server:
CLI command
The most common way. Claude Code writes the configuration for you.
claude mcp add <name> -- <command> [args...]
Example:
claude mcp add fetch -- npx @anthropic-ai/mcp-fetch
This writes the entry into .mcp.json in the current project.
Tool Naming Convention
MCP tools follow a strict naming pattern:
mcp__<server-name>__<tool-name>
The double-underscore separators make MCP tools visually distinct from built-in tools.
Example: if you register a server named weather that exposes a tool called forecast, Claude sees it as:
mcp__weather__forecast
This naming convention serves three purposes:
-
Disambiguation. Multiple servers can expose tools with the same name. The server prefix prevents collisions.
-
Discovery. Claude sees all available MCP tools in its tool list alongside built-in tools. The
mcp__prefix tells Claude (and you) that a tool call will go to an external server. -
Permission targeting. You can write permission rules that match specific MCP tools using glob patterns:
mcpweather*allows all tools from the weather server.
Safety Model
MCP tools are not automatically trusted. Even if a server is registered and running, Claude Code applies the same permission model you learned in Module 4:
-
Claude prompts before calling any MCP tool. Every MCP tool call triggers a permission check. The user sees what tool is being called, with what arguments, and must approve.
-
Permission rules can allow or deny specific MCP tools. You can add
mcpfetchfetchto yourallowlistin.claude/settings.jsonto auto-approve it. Or denymcpdangerous*to block all tools from a specific server. -
MCP tools are not auto-approved by default. Even if you have broad allowlist rules for built-in tools, MCP tools require explicit approval unless you add them to your permission rules.
This is deliberately conservative. An MCP server is external code running on your system. The permission model ensures you see what it does before it does it.
Server Lifecycle
MCP servers have a lifecycle managed by Claude Code:
-
Startup: Servers registered in configuration files start when Claude Code starts a session.
stdioservers are spawned as child processes.httpandsseservers connect to existing endpoints. -
Health checking: Claude Code monitors server health. If a server crashes or disconnects, it appears as unhealthy in the
/mcppanel. -
Reconnection: You can reconnect a failed server from the
/mcppanel without restarting your Claude Code session. -
Shutdown: When the Claude Code session ends,
stdioservers are terminated. Remote servers (http,sse) remain running independently.
The /mcp command is your control panel for server lifecycle:
-
View status of all registered servers (running, stopped, error)
-
Enable or disable individual servers
-
Reconnect a server that has disconnected
-
Add or remove servers
Part 2: See It In Action
Exercise 1: Add a Public MCP Server
Add the Anthropic fetch server, which provides a tool for fetching web content:
claude mcp add fetch -- npx @anthropic-ai/mcp-fetch
Start a Claude Code session and use the new tool:
cd ~/learnpath
claude
Inside the session, type:
Fetch the contents of https://httpbin.org/get
Observe what happens:
-
Claude decides to use the
mcpfetchfetchtool (not the built-inBashtool withcurl). -
A permission prompt appears asking you to approve the MCP tool call. The prompt shows the tool name (
mcpfetchfetch), the server (fetch), and the arguments (the URL). -
After you approve, the fetch server retrieves the URL and returns the content.
-
Claude presents the result.
This is the MCP flow in action: Claude reasons about available tools, selects the MCP tool, asks for permission, delegates to the server, and processes the result.
Exercise 2: Examine Configuration
After adding the fetch server, look at the configuration file it created:
cat ~/learnpath/.mcp.json
You should see:
{
"mcpServers": {
"fetch": {
"command": "npx",
"args": ["@anthropic-ai/mcp-fetch"]
}
}
}
This is a project-scoped configuration. It will be visible to anyone who clones the repository (if committed to git).
Compare this to a user-scoped configuration. Check your global config:
cat ~/.claude.json
If you had added the server with claude mcp add --scope user fetch — npx @anthropic-ai/mcp-fetch, the entry would appear in ~/.claude.json instead of .mcp.json.
The JSON structure is identical. Only the file location — and therefore the visibility scope — differs.
Exercise 3: Server Management
Inside a Claude Code session, use the management panel:
/mcp
You should see the fetch server listed with its status (running, stopped, or error).
Try these operations:
-
Disable the server: Select the fetch server and disable it. Now ask Claude to fetch a URL — it will not have the MCP tool available and will fall back to using
Bashwithcurl. -
Re-enable the server: Enable it again and verify the
mcpfetchfetchtool reappears. -
Remove the server: Exit the session and remove it from the CLI:
claude mcp remove fetch
Verify the .mcp.json file no longer contains the fetch entry.
Exercise 4: MCP in Transcripts
Run a task that uses the MCP tool and review the transcript:
# Re-add the fetch server
claude mcp add fetch -- npx @anthropic-ai/mcp-fetch
# Run a task in print mode to generate a transcript
claude "Fetch https://httpbin.org/headers and tell me what user-agent was sent"
Review the session transcript (using claude-code-transcripts if installed, or checking ~/.claude/projects/ for raw session data).
In the transcript, look for:
-
Tool name:
mcpfetchfetch— the double-underscore convention marking this as an MCP tool -
Arguments: The URL and any options passed to the fetch tool
-
Result: The response body returned by the fetch server
-
Permission prompt: Where the user was asked to approve the tool call
Compare this to a built-in tool call in the same transcript (e.g., a Read or Bash call).
The structure is identical — tool name, arguments, result — but the naming convention and permission behavior differ.
Part 3: Build With It
You will build a custom MCP server that connects Claude Code to RSS feeds and your learnpath API.
When complete, Claude will be able to search RSS feeds for learning resources and ingest them directly into your learnpath application — all through MCP tool calls.
Step 1: Create the MCP Server Project
Set up a new Python project for the MCP server:
mkdir -p ~/learnpath-mcp/src/learnpath_mcp
Create the project configuration:
[project]
name = "learnpath-mcp"
version = "0.1.0"
description = "MCP server for learnpath feed ingestion"
requires-python = ">={python-version}"
dependencies = [
"fastmcp>=2.0.0",
"httpx>=0.27.0",
"feedparser>=6.0.0",
]
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
Create the package init file:
"""Learnpath MCP server for feed ingestion."""
Step 2: Implement the Server
Build the MCP server using FastMCP. FastMCP is a Python framework for building MCP servers with minimal boilerplate — you define tools as decorated functions.
"""MCP server that searches RSS feeds and ingests assets into the learnpath API."""
from __future__ import annotations
import feedparser
import httpx
from fastmcp import FastMCP
LEARNPATH_API = "http://localhost:8000"
mcp = FastMCP(
"learnpath-feeds",
instructions=(
"Search RSS/Atom feeds for learning resources and ingest them "
"into the learnpath API. Use search_feed to find articles matching "
"a query, list_feed to browse recent items, and ingest_asset to "
"save a resource to the learnpath application."
),
)
@mcp.tool()
def list_feed(url: str, limit: int = 10) -> list[dict]:
"""List recent items from an RSS or Atom feed.
Args:
url: The URL of the RSS or Atom feed.
limit: Maximum number of items to return (default 10).
Returns:
A list of feed items with title, link, and published date.
"""
feed = feedparser.parse(url)
items = []
for entry in feed.entries[:limit]:
items.append(
{
"title": entry.get("title", ""),
"link": entry.get("link", ""),
"published": entry.get("published", ""),
"summary": entry.get("summary", "")[:200],
}
)
return items
@mcp.tool()
def search_feed(url: str, query: str) -> list[dict]:
"""Search an RSS or Atom feed for items matching a query.
Performs case-insensitive substring matching against the title
and summary of each feed item.
Args:
url: The URL of the RSS or Atom feed.
query: The search term to match against titles and summaries.
Returns:
A list of matching feed items with title, link, and published date.
"""
feed = feedparser.parse(url)
query_lower = query.lower()
matches = []
for entry in feed.entries:
title = entry.get("title", "")
summary = entry.get("summary", "")
if query_lower in title.lower() or query_lower in summary.lower():
matches.append(
{
"title": title,
"link": entry.get("link", ""),
"published": entry.get("published", ""),
"summary": summary[:200],
}
)
return matches
@mcp.tool()
def ingest_asset(
title: str,
url: str,
asset_type: str,
topic: str,
notes: str = "",
) -> dict:
"""Create a new asset in the learnpath API.
Posts to the learnpath application's asset endpoint to save
a learning resource.
Args:
title: The title of the learning resource.
url: The URL of the resource.
asset_type: The type of asset (e.g., "article", "video", "tutorial").
topic: The topic or category for the asset.
notes: Optional notes about why this resource is useful.
Returns:
The created asset record from the API, or an error message.
"""
payload = {
"title": title,
"url": url,
"asset_type": asset_type,
"topic": topic,
"notes": notes,
}
with httpx.Client() as client:
response = client.post(f"{LEARNPATH_API}/assets/", json=payload)
response.raise_for_status()
return response.json()
if __name__ == "__main__":
mcp.run()
The server exposes three tools:
-
list_feed— browse recent items from any RSS/Atom feed -
search_feed— filter feed items by a search term -
ingest_asset— POST a new asset to your learnpath API
Each tool is a plain Python function with type hints and a docstring. FastMCP uses the type hints to generate the MCP input schema and the docstring to generate the tool description. Claude reads these descriptions to decide when and how to call each tool.
Step 3: Install Dependencies
cd ~/learnpath-mcp
uv sync
Verify the server starts without errors:
uv run python -m learnpath_mcp.server
The server should start and wait for MCP client connections on stdin.
Press Ctrl+C to stop it.
Step 4: Register with the Learnpath Project
Add the MCP server to your learnpath project configuration.
Create or update ~/learnpath/.mcp.json:
{
"mcpServers": {
"learnpath-feeds": {
"command": "uv",
"args": [
"run",
"--directory", "/absolute/path/to/learnpath-mcp",
"python", "-m", "learnpath_mcp.server"
]
}
}
}
|
Replace |
The configuration tells Claude Code:
-
Server name:
learnpath-feeds— this becomes the middle segment in tool names (mcplearnpath-feeds*) -
Command:
uv— use the uv package manager to run the server -
Args: run the
learnpath_mcp.servermodule from the specified directory
Because this is in .mcp.json at the project root, the server is available to anyone working on the learnpath project.
Step 5: Use It from Claude Code
Make sure your learnpath API is running in another terminal:
cd ~/learnpath
uv run uvicorn main:app --reload
Start a Claude Code session in the learnpath project:
cd ~/learnpath
claude
Verify the MCP server is connected:
/mcp
You should see learnpath-feeds listed with status "running."
Now use the tools through natural language:
Use the learnpath-feeds server to list the 5 most recent items from the Python blog RSS feed at https://blog.python.org/feeds/posts/default?alt=rss
Claude will call mcplearnpath-feedslist_feed with the URL and limit.
Approve the permission prompt to see the results.
Next, search for specific content:
Search the Python blog feed for articles about releases
Claude will call mcplearnpath-feedssearch_feed with the URL and query "releases."
Finally, ingest a result into your API:
Ingest the most interesting result as an asset with topic "python" and type "article"
Claude will call mcplearnpath-feedsingest_asset, posting to your learnpath API.
Step 6: Transcript Review
After the session, review the transcript to see the full MCP flow.
Look for these elements in the session data:
-
Tool selection: Claude chose
mcplearnpath-feedssearch_feedover alternatives like usingBashwithcurlto fetch the feed directly. The MCP tool was selected because it provides structured, filtered results. -
Permission prompts: Each MCP tool call triggered a separate permission prompt. Compare this to built-in tools — if you have
Bashin your allowlist, shell commands run without prompting. MCP tools do not inherit that allowlist unless you explicitly add them. -
Tool arguments: The MCP tool received typed arguments (URL as string, limit as integer) that match the function signatures in your server code.
-
Tool results: The server returned structured data (list of dicts) that Claude could reason about for the next step.
The key observation: from Claude’s perspective, MCP tools are just tools. The agentic loop, context assembly, and tool dispatch work exactly the same way. MCP extends what tools are available, not how tool dispatch works.
Compare the permission model to direct API calls:
| Aspect | Direct Bash + curl |
MCP Tool |
|---|---|---|
Permission |
Covered by |
Each MCP tool requires its own permission approval or allowlist entry |
Visibility |
The full |
The tool name and typed arguments are structured and self-documenting |
Error handling |
Raw HTTP response — Claude must parse status codes and error bodies |
Server handles errors and returns structured results |
Reusability |
One-off commands — each |
Server maintains state and can be reused across sessions and projects |
|
What you should have:
Understanding check: You should be able to:
|
References
-
FastMCP — Python framework for building MCP servers
-
Community MCP Servers — reference implementations and integrations