What Is MCP?
MCP (Model Context Protocol) is an open-source standard protocol announced by Anthropic in November 2024. It defines a specification for connecting AI applications to external systems (databases, APIs, file systems, etc.).
Think of it as a USB-C port for AI. Just as USB-C unified charging, data, and video output into a single standard, MCP standardizes how AI calls external tools. It was donated to the Linux Foundation in December 2025, and both OpenAI and Google DeepMind have adopted it.
Architecture
MCP consists of 3 roles.
| Component | Role | Example |
|---|---|---|
| Host | AI application (manages multiple Clients) | Claude Desktop, VS Code, Claude Code |
| Client | Maintains a 1:1 connection with each Server | Auto-created within the Host |
| Server | Provides Tools/Resources/Prompts | The part developers implement |
The 3 primitives a Server provides:
| Primitive | Description | Analogy |
|---|---|---|
| Tool | Executable function that AI can call | REST POST endpoint |
| Resource | Provides context data to AI | REST GET endpoint |
| Prompt | Template that structures LLM interactions | Pre-written task instructions |
Installation
# Using uv (recommended)
uv add "mcp[cli]"
# Using pip
pip install "mcp[cli]"
# Verify installation
python -c "import mcp; print(mcp.__version__)"
# 1.27.0
Implementing Tools
A Tool is a function that AI calls directly. Define it with a single decorator.
from mcp.server.fastmcp import FastMCP
# Create MCP server instance
mcp = FastMCP("MyTools")
@mcp.tool()
def add(a: int, b: int) -> int:
"""Adds two numbers"""
return a + b
@mcp.tool()
def search_files(directory: str, extension: str = ".py") -> list[str]:
"""Searches for files with a specific extension in a directory
Args:
directory: Directory path to search
extension: File extension (default: .py)
"""
from pathlib import Path
return [str(f) for f in Path(directory).rglob(f"*{extension}")]
@mcp.tool()
async def fetch_url(url: str) -> str:
"""Fetches the content of a URL"""
import httpx
async with httpx.AsyncClient() as client:
resp = await client.get(url, timeout=30.0)
return resp.text[:5000] # Max 5000 characters
if __name__ == "__main__":
mcp.run(transport="stdio")
MCP schemas are automatically generated from Python type hints and docstrings. Write detailed parameter descriptions in the Args: section, as they are passed to the AI.
Implementing Resources
Resources provide context data to AI. Unlike Tools, they are read-only with no side effects.
from mcp.server.fastmcp import FastMCP
import json
mcp = FastMCP("MyResources")
# Static resource -- fixed URI
@mcp.resource("config://app-version")
def get_version() -> str:
"""Returns app version information"""
return "v2.1.0"
# Dynamic resource -- uses URI parameters
@mcp.resource("db://users/{user_id}")
def get_user(user_id: str) -> str:
"""Returns user information as JSON"""
users = {
"1": {"name": "Alice", "email": "alice@example.com"},
"2": {"name": "Bob", "email": "bob@example.com"},
}
user = users.get(user_id, {"error": "User not found"})
return json.dumps(user, ensure_ascii=False)
# File-based resource
@mcp.resource("file://logs/latest")
def get_latest_log() -> str:
"""Returns the latest log file contents"""
from pathlib import Path
log_file = Path("logs/app.log")
if not log_file.exists():
return "Log file not found"
return log_file.read_text(encoding="utf-8")[-2000:] # Last 2000 characters
if __name__ == "__main__":
mcp.run(transport="stdio")
Implementing Prompts
Prompts are pre-defined templates for AI interactions. They enable reuse of recurring task instructions.
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("MyPrompts")
@mcp.prompt()
def code_review(language: str, code: str) -> str:
"""Generates a code review prompt"""
return f"""Please review the following {language} code.
## Review Criteria
1. Potential bugs
2. Performance improvement opportunities
3. Readability and naming
## Code
```{language}
{code}
```"""
@mcp.prompt()
def sql_query(table_name: str, columns: str = "*") -> str:
"""SQL query generation prompt"""
return f"""Write an optimized SQL query to retrieve the {columns} columns
from the '{table_name}' table. Consider index usage and performance."""
if __name__ == "__main__":
mcp.run(transport="stdio")
Practical Example: Note Management Server
A hands-on example combining Tool + Resource.
from mcp.server.fastmcp import FastMCP
import json
from datetime import datetime
mcp = FastMCP("NoteServer")
# In-memory storage
notes: dict[str, dict] = {}
@mcp.tool()
def create_note(title: str, content: str) -> str:
"""Creates a new note
Args:
title: Note title
content: Note content
"""
note_id = str(len(notes) + 1)
notes[note_id] = {
"title": title,
"content": content,
"created_at": datetime.now().isoformat(),
}
return f"Note #{note_id} '{title}' created"
@mcp.tool()
def delete_note(note_id: str) -> str:
"""Deletes a note"""
if note_id not in notes:
return f"Note #{note_id} not found"
title = notes.pop(note_id)["title"]
return f"Note #{note_id} '{title}' deleted"
@mcp.resource("notes://list")
def list_notes() -> str:
"""Returns all notes"""
if not notes:
return "No notes found"
return json.dumps(notes, ensure_ascii=False, indent=2)
@mcp.resource("notes://{note_id}")
def get_note(note_id: str) -> str:
"""Retrieves a specific note"""
note = notes.get(note_id)
if not note:
return f"Note #{note_id} not found"
return json.dumps(note, ensure_ascii=False, indent=2)
if __name__ == "__main__":
mcp.run(transport="stdio")
Connecting to Claude Desktop
Configuration for registering an MCP server with Claude Desktop.
Config file location:
- macOS:
~/Library/Application Support/Claude/claude_desktop_config.json - Windows:
%APPDATA%\Claude\claude_desktop_config.json
{
"mcpServers": {
"note-server": {
"command": "uv",
"args": [
"--directory", "/path/to/note-server",
"run", "server.py"
]
}
}
}
Connecting from Claude Code:
# Register STDIO server
claude mcp add note-server -- uv --directory /path/to/note-server run server.py
# Verify registration
claude mcp list
# Check status in session
/mcp
Important Notes
| Item | Description |
|---|---|
| No print() | In STDIO transport, print() breaks JSON-RPC messages. Use the logging module |
| Error handling | When a Tool throws an exception, the error message is sent to AI. Write clear error messages |
| Type hints required | MCP schema generation fails without type hints |
| Write docstrings | Function descriptions are passed to AI. More detail helps AI choose the right Tool |
| Async support | Define with async def for async execution. Recommended for I/O operations |
Summary
| Primitive | Decorator | Use Case |
|---|---|---|
| Tool | @mcp.tool() | Functions AI calls (create, update, delete, API calls) |
| Resource | @mcp.resource("uri://pattern") | Providing data to AI (queries, reads) |
| Prompt | @mcp.prompt() | Templatizing recurring tasks |
The advantage of Python FastMCP is that a single decorator does it all. As long as you write proper type hints and docstrings, MCP schema generation, parameter validation, and error handling are all handled automatically.