====== 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}}