====== How to Use MCP (Model Context Protocol) ======
A practical guide to the Model Context Protocol from zero to working server. MCP is the open standard that lets AI models connect to external tools, databases, and services — a universal protocol supported by Claude, ChatGPT, Cursor, VS Code Copilot, and more.
===== What is MCP? =====
MCP (Model Context Protocol) is an open standard created by Anthropic that standardizes how AI models communicate with external tools and data sources. Think of it as **USB-C for AI** — one protocol that works across clients and servers.
An MCP server exposes three types of capabilities:
^ Capability ^ What It Does ^ Example ^
| **Tools** | Functions the AI can call | Query a database, call an API, run a calculation |
| **Resources** | Data the AI can read | Config files, logs, system info, database records |
| **Prompts** | Reusable interaction templates | Code review template, analysis workflow |
===== Architecture =====
graph LR
subgraph MCP Clients
A[Claude Desktop]
B[Claude Code CLI]
C[Cursor IDE]
D[Custom App]
end
subgraph Transport
E[stdio]
F[HTTP + SSE]
G[Streamable HTTP]
end
subgraph MCP Servers
H[Database Server]
I[File System Server]
J[API Server]
K[Your Custom Server]
end
A --> E --> H
B --> E --> I
C --> E --> J
D --> F --> K
D --> G --> K
**How it works:**
- **Client** (Claude, Cursor, etc.) discovers available servers
- **Transport** carries JSON-RPC 2.0 messages between client and server
- **Server** exposes tools, resources, and prompts that the client can use
===== Transport Mechanisms =====
^ Transport ^ How It Works ^ Best For ^
| **stdio** | Client spawns server as subprocess, communicates via stdin/stdout | Local integrations, Claude Desktop, Claude Code |
| **HTTP + SSE** | Server runs as HTTP service, uses Server-Sent Events for streaming | Remote servers, cloud deployments |
| **Streamable HTTP** | Modern HTTP-based transport with bidirectional streaming | New deployments, replaces SSE |
===== Step 1: Install the MCP Python SDK =====
# Recommended: use uv (fast Python package manager)
curl -LsSf https://astral.sh/uv/install.sh | sh
# Create a new project
mkdir my-mcp-server && cd my-mcp-server
uv init --python 3.11
uv add "mcp[cli]"
# Alternative: pip
pip install "mcp[cli]"
===== Step 2: Create a Basic MCP Server =====
The FastMCP framework handles all protocol boilerplate. You just define tools, resources, and prompts with decorators.
==== Minimal Server ====
# server.py
from mcp.server.fastmcp import FastMCP
app = FastMCP(name="my-server")
@app.tool()
def add(a: int, b: int) -> int:
"""Add two numbers together."""
return a + b
if __name__ == "__main__":
app.run()
# Run it
uv run python server.py
# Or: python3 server.py
===== Step 3: Define Tools =====
Tools are functions the AI can call. The SDK reads type hints and docstrings to generate the JSON schema that clients see.
from mcp.server.fastmcp import FastMCP
import httpx
app = FastMCP(name="utility-server")
@app.tool()
def calculate(expression: str) -> str:
"""Evaluate a mathematical expression safely.
Args:
expression: A math expression like '2 + 3 * 4'
"""
try:
# Safe eval for math only
allowed = set("0123456789+-*/.() ")
if not all(c in allowed for c in expression):
return "Error: Only math expressions allowed"
result = eval(expression)
return str(result)
except Exception as e:
return f"Error: {e}"
@app.tool()
async def fetch_url(url: str) -> str:
"""Fetch the content of a URL.
Args:
url: The URL to fetch (must start with https://)
"""
if not url.startswith("https://"):
return "Error: Only HTTPS URLs are allowed"
async with httpx.AsyncClient() as client:
response = await client.get(url, follow_redirects=True)
return response.text[:5000] # Limit response size
@app.tool()
def search_notes(query: str) -> list[str]:
"""Search through saved notes by keyword.
Args:
query: Search term to look for in notes
"""
# Example: search a local file or database
notes = [
"Meeting with team on Monday at 10am",
"Project deadline is March 30th",
"Remember to update the API docs",
]
return [n for n in notes if query.lower() in n.lower()]
===== Step 4: Define Resources =====
Resources provide data that the AI can read on demand.
import json
import platform
from datetime import datetime
@app.resource("config://app")
def get_app_config() -> str:
"""Return the application configuration."""
config = {
"version": "1.0.0",
"environment": "development",
"features": ["search", "analytics"]
}
return json.dumps(config, indent=2)
@app.resource("system://info")
def get_system_info() -> str:
"""Return current system information."""
return json.dumps({
"platform": platform.system(),
"python_version": platform.python_version(),
"timestamp": datetime.now().isoformat()
}, indent=2)
# Dynamic resources with URI templates
@app.resource("notes://{note_id}")
def get_note(note_id: str) -> str:
"""Get a specific note by ID."""
notes_db = {
"1": "Meeting notes from Monday standup",
"2": "Architecture decision: use PostgreSQL",
"3": "TODO: Implement caching layer"
}
return notes_db.get(note_id, f"Note {note_id} not found")
===== Step 5: Define Prompts =====
Prompts are reusable templates that clients can offer to users.
from mcp.server.fastmcp import FastMCP
from mcp.server.fastmcp.prompts import base
@app.prompt()
def code_review(code: str, language: str = "python") -> str:
"""Generate a thorough code review.
Args:
code: The code to review
language: Programming language
"""
return f"""Please review the following {language} code.
Check for:
1. Bugs and logic errors
2. Security vulnerabilities
3. Performance issues
4. Code style and readability
5. Missing error handling
Code:
```{language}
{code}
```
Provide specific, actionable feedback."""
@app.prompt()
def debug_error(error_message: str, stack_trace: str = "") -> str:
"""Help debug an error.
Args:
error_message: The error message
stack_trace: Optional stack trace
"""
context = f"Error: {error_message}"
if stack_trace:
context += f"\n\nStack trace:\n{stack_trace}"
return f"""Help me debug this error. Explain what likely caused it and suggest fixes.
{context}"""
===== Step 6: Complete Working Server =====
Here is a full server combining tools, resources, and prompts:
# server.py — Complete MCP Server
from mcp.server.fastmcp import FastMCP
import json
import platform
import httpx
from datetime import datetime
app = FastMCP(
name="demo-server",
version="1.0.0"
)
# ---- TOOLS ----
@app.tool()
def calculate(expression: str) -> str:
"""Evaluate a math expression. Example: '2 + 3 * 4'"""
allowed = set("0123456789+-*/.() ")
if not all(c in allowed for c in expression):
return "Error: Only math expressions allowed"
try:
return str(eval(expression))
except Exception as e:
return f"Error: {e}"
@app.tool()
async def fetch_url(url: str) -> str:
"""Fetch content from a URL. Only HTTPS allowed."""
if not url.startswith("https://"):
return "Error: HTTPS required"
async with httpx.AsyncClient() as client:
resp = await client.get(url, follow_redirects=True, timeout=10)
return resp.text[:5000]
@app.tool()
def search_notes(query: str) -> list[str]:
"""Search notes by keyword."""
notes = [
"Team standup moved to 10am",
"Deploy v2.0 by Friday",
"Review PR #42 for auth changes",
]
return [n for n in notes if query.lower() in n.lower()]
# ---- RESOURCES ----
@app.resource("config://app")
def get_config() -> str:
"""Application configuration."""
return json.dumps({"version": "1.0.0", "env": "dev"}, indent=2)
@app.resource("system://info")
def get_system_info() -> str:
"""Current system information."""
return json.dumps({
"platform": platform.system(),
"python": platform.python_version(),
"time": datetime.now().isoformat()
}, indent=2)
# ---- PROMPTS ----
@app.prompt()
def code_review(code: str, language: str = "python") -> str:
"""Review code for bugs, security, and style."""
return f"Review this {language} code for bugs, security issues, and style:\n```{language}\n{code}\n```"
# ---- RUN ----
if __name__ == "__main__":
app.run()
===== Step 7: Connect to Claude Desktop =====
Configure Claude Desktop to discover your MCP server.
==== claude_desktop_config.json ====
**Location:**
* macOS: ''~/Library/Application Support/Claude/claude_desktop_config.json''
* Windows: ''%APPDATA%\Claude\claude_desktop_config.json''
* Linux: ''~/.config/Claude/claude_desktop_config.json''
{
"mcpServers": {
"demo-server": {
"command": "uv",
"args": ["run", "--directory", "/path/to/my-mcp-server", "python", "server.py"]
}
}
}
Or with pip/python directly:
{
"mcpServers": {
"demo-server": {
"command": "python3",
"args": ["/path/to/my-mcp-server/server.py"]
}
}
}
After saving, restart Claude Desktop. Your tools will appear in the tools menu.
===== Step 8: Connect to Claude Code (CLI) =====
# Add server to Claude Code
claude mcp add demo-server -- uv run --directory /path/to/my-mcp-server python server.py
# Or add to project config (.claude/settings.json)
claude mcp add --scope project demo-server -- python3 /path/to/server.py
# List configured servers
claude mcp list
# Test the server
claude mcp serve demo-server
===== Step 9: Build an MCP Client =====
For custom applications, build your own MCP client to connect to any MCP server.
# client.py — MCP Client Example
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
import asyncio
async def main():
# Connect to an MCP server via stdio
server_params = StdioServerParameters(
command="python3",
args=["server.py"]
)
async with stdio_client(server_params) as (read, write):
async with ClientSession(read, write) as session:
# Initialize the connection
await session.initialize()
# List available tools
tools = await session.list_tools()
print("Available tools:")
for tool in tools.tools:
print(f" - {tool.name}: {tool.description}")
# Call a tool
result = await session.call_tool(
"calculate",
arguments={"expression": "42 * 17 + 3"}
)
print(f"\nCalculation result: {result.content[0].text}")
# List resources
resources = await session.list_resources()
print("\nAvailable resources:")
for resource in resources.resources:
print(f" - {resource.uri}: {resource.name}")
# Read a resource
content = await session.read_resource("config://app")
print(f"\nConfig: {content.contents[0].text}")
# List prompts
prompts = await session.list_prompts()
print("\nAvailable prompts:")
for prompt in prompts.prompts:
print(f" - {prompt.name}: {prompt.description}")
if __name__ == "__main__":
asyncio.run(main())
===== Testing with MCP Inspector =====
The MCP Inspector is a visual tool for testing servers interactively.
# Install and run
npx @modelcontextprotocol/inspector uv run python server.py
# Opens a web UI where you can:
# - See all tools, resources, and prompts
# - Call tools with custom arguments
# - Read resources
# - View raw JSON-RPC messages
===== Remote Server (HTTP + SSE) =====
For remote/cloud deployment, use the SSE transport instead of stdio.
# server_remote.py
from mcp.server.fastmcp import FastMCP
app = FastMCP(
name="remote-server",
host="0.0.0.0",
port=8080
)
@app.tool()
def hello(name: str) -> str:
"""Say hello."""
return f"Hello, {name}!"
if __name__ == "__main__":
app.run(transport="sse") # or transport="streamable-http"
Connect from Claude Desktop:
{
"mcpServers": {
"remote-server": {
"url": "http://localhost:8080/sse"
}
}
}
===== Client-Server Flow =====
sequenceDiagram
participant Client as MCP Client (Claude)
participant Server as MCP Server
Client->>Server: initialize (capabilities, protocol version)
Server-->>Client: initialize response (server capabilities)
Client->>Server: tools/list
Server-->>Client: [calculate, fetch_url, search_notes]
Client->>Server: resources/list
Server-->>Client: [config://app, system://info]
Note over Client: User asks "What is 42 * 17?"
Client->>Server: tools/call (calculate, {"expression": "42 * 17"})
Server-->>Client: "714"
Note over Client: Claude includes result in response
===== Best Practices for Tool Design =====
* **Clear docstrings** — The AI reads them to decide when and how to use your tool
* **Typed parameters** — Use Python type hints; the SDK generates JSON Schema from them
* **Return strings** — Tools should return human-readable text, not raw objects
* **Handle errors gracefully** — Return error messages instead of raising exceptions
* **Limit output size** — Cap response length to avoid overwhelming the context window
* **One tool, one job** — Keep tools focused; compose multiple tools for complex workflows
* **Validate inputs** — Check parameters before doing expensive operations
* **Async for I/O** — Use ''async def'' for tools that make network calls or read files
===== See Also =====
* [[how_to_build_a_rag_pipeline|How to Build a RAG Pipeline]]
* [[how_to_deploy_an_agent|How to Deploy an Agent]]
* [[how_to_add_memory_to_an_agent|How to Add Memory to an Agent]]
* [[how_to_evaluate_an_agent|How to Evaluate an Agent]]
{{tag>mcp model-context-protocol tools protocol claude how-to}}