Claude Code · Model Context Protocol

Model Context Protocol (MCP)

MCP is the open standard that makes Claude infinitely extensible. Instead of being limited to what's in your messages, Claude can connect to any database, API, file system, or service — all through a clean, universal interface.

🔌 MCP Architecture 🛠️ Building Servers 🗃️ DB Connectors 🌐 API Bridges ⚠️ Error Handling

What is MCP and Why It Matters

Before MCP, connecting Claude to your company's data required custom integrations for every use case. A Slack integration was completely different from a PostgreSQL integration, a Salesforce connector, or a GitHub integration. Each one needed custom code, different authentication patterns, and bespoke error handling.

MCP defines a universal interface. Any data source or tool that implements the MCP protocol can connect to Claude (and any other MCP-compatible AI) using the same standard. Build it once, connect it everywhere.

🔑 The MCP Mental Model

Think of MCP like USB-C for AI. Before USB-C, every device had a different connector. USB-C created one standard — any USB-C device connects to any USB-C port. MCP does the same for AI-to-tool connectivity. Write one MCP server for your database, and Claude (or any MCP client) can query it.

MCP Architecture Explained

MCP uses a client-server architecture with three core components:

  ┌──────────────────────────────────────────────────────┐
  │                    YOUR TASK                          │
  │   "Find all customers who bought > $1000 this month" │
  └───────────────────┬──────────────────────────────────┘
                      │
            ┌─────────▼─────────┐
            │   Claude (LLM)    │  ← MCP HOST (Claude Code / API)
            │  Understands task │
            │  Selects tools    │
            └─────────┬─────────┘
                      │  MCP Protocol (JSON-RPC over stdio/HTTP)
         ┌────────────▼────────────┐
         │    MCP CLIENT           │
         │  (Built into Claude     │
         │   Code & API)           │
         └──┬──────────┬────────┬──┘
            │          │        │
   ┌────────▼──┐  ┌────▼───┐  ┌─▼──────────┐
   │ MCP Server│  │MCP Svr │  │ MCP Server │
   │ PostgreSQL│  │ GitHub │  │ Salesforce │
   └────────┬──┘  └────┬───┘  └─────┬──────┘
            │          │             │
         Your DB    Your Repo    Your CRM

Claude (the host) calls the MCP Client, which speaks to each MCP Server using a standardized JSON-RPC protocol. Each server exposes tools, resources, and optionally prompts that Claude can discover and use automatically.

Core MCP Concepts

🔧

Tools

Functions Claude can call. Each tool has a name, description, and JSON Schema defining its inputs. Claude reads the description to decide when to call the tool and what parameters to pass.

📚

Resources

Data sources Claude can read — files, database records, API responses. Resources are identified by URIs and returned as text or binary content for Claude to process.

💬

Prompts

Reusable prompt templates stored in the MCP server. The server can offer pre-built workflows like "summarize-document" or "analyze-customer-data" that Claude can invoke.

🔄

Transport

How Claude communicates with your server: stdio (local process, most common), HTTP/SSE (network, for remote servers), or WebSocket. Choose based on where your server runs.

Building Your First MCP Server

Here's a minimal but complete MCP server in Python that exposes a database query tool:

python — mcp_server.py (minimal example)
import asyncio
import json
from mcp.server import Server
from mcp.server.stdio import stdio_server
from mcp import types
import psycopg2

# Initialize the MCP server
server = Server("customer-db")

# Register a tool Claude can call
@server.list_tools()
async def list_tools():
    return [
        types.Tool(
            name="query_customers",
            description="""Search customer records by various criteria.
            Use this when you need to find customers, check order history,
            or analyze purchase patterns. Returns structured customer data.""",
            inputSchema={
                "type": "object",
                "properties": {
                    "query": {
                        "type": "string",
                        "description": "SQL SELECT query (read-only)"
                    },
                    "limit": {
                        "type": "integer",
                        "description": "Max rows to return (default: 100, max: 1000)",
                        "default": 100
                    }
                },
                "required": ["query"]
            }
        )
    ]

# Handle tool calls from Claude
@server.call_tool()
async def call_tool(name: str, arguments: dict):
    if name == "query_customers":
        try:
            # Safety: only allow SELECT queries
            query = arguments["query"].strip()
            if not query.upper().startswith("SELECT"):
                return [types.TextContent(
                    type="text",
                    text='{"error": "Only SELECT queries are permitted"}'
                )]
            
            conn = psycopg2.connect("postgresql://localhost/customers")
            cursor = conn.cursor()
            limit = min(arguments.get("limit", 100), 1000)
            cursor.execute(query + f" LIMIT {limit}")
            
            columns = [desc[0] for desc in cursor.description]
            rows = [dict(zip(columns, row)) for row in cursor.fetchall()]
            
            return [types.TextContent(
                type="text",
                text=json.dumps({"rows": rows, "count": len(rows)})
            )]
        except Exception as e:
            # Structured error — Claude can understand and retry
            return [types.TextContent(
                type="text",
                text=json.dumps({
                    "error": str(e),
                    "error_type": type(e).__name__,
                    "suggestion": "Check SQL syntax or table names"
                })
            )]

# Start the server
if __name__ == "__main__":
    asyncio.run(stdio_server(server))

Registering the Server with Claude Code

json — ~/.claude/config.json
{
  "mcpServers": {
    "customer-db": {
      "command": "python",
      "args": ["/path/to/mcp_server.py"],
      "env": {
        "DATABASE_URL": "postgresql://localhost/customers"
      }
    },
    "github": {
      "command": "npx",
      "args": ["@anthropic/mcp-server-github"],
      "env": {
        "GITHUB_TOKEN": "ghp_..."
      }
    }
  }
}

Tool Design Best Practices

The quality of your tool descriptions is the biggest factor determining how well Claude uses your MCP server. Claude decides which tool to call — and with what parameters — entirely based on reading your descriptions.

💡 The Golden Rule of Tool Design

Write tool descriptions as if Claude has no idea what your system does. Include: what the tool does, when Claude SHOULD use it, when it should NOT use it, and what format the output is in. A 3-line description produces 3× better tool selection accuracy than a 1-line description.

Principle❌ Bad Example✅ Good Example
Clear purpose "Queries the database" "Retrieves customer purchase history for a given customer ID. Use this when asked about a specific customer's orders or spending patterns."
When to use / not use Not specified "Use for single customer lookups. For queries across many customers, use get_customer_analytics instead."
Output format Not specified "Returns JSON with keys: customer_id, name, email, orders[] array, total_spent number."
Parameter clarity "id: string — the id" "customer_id: string — The UUID from the customers table (format: uuid-v4). Found in customer email confirmation links."

Structured Error Handling

MCP errors fall into two categories: errors Claude can recover from autonomously, and errors that need human intervention. Design your error responses to signal which type they are:

python — structured error responses
def make_error(error_type: str, message: str, retryable: bool, suggestion: str = ""):
    """
    Create a structured error response that Claude can act on intelligently.
    retryable=True: Claude can try a different approach automatically
    retryable=False: Human intervention needed
    """
    return json.dumps({
        "error": True,
        "error_type": error_type,
        "message": message,
        "retryable": retryable,
        "suggestion": suggestion
    })

# Usage examples:

# Claude can fix this by correcting the query
make_error("INVALID_SQL", "Column 'user_id' doesn't exist", 
           retryable=True,
           suggestion="Available columns: id, email, name, created_at")

# Claude cannot fix this — needs human
make_error("AUTH_FAILED", "Database credentials invalid",
           retryable=False,
           suggestion="Admin must update DATABASE_URL in server config")

# Claude can retry with smaller scope
make_error("TIMEOUT", "Query exceeded 30s limit",
           retryable=True,
           suggestion="Add a date filter to reduce result set size")

Real-World MCP Patterns

🗃️

Database Connector

Read-only SQL access. Expose tools for common queries (get_customer, search_orders) rather than raw SQL input. Safer and produces better descriptions for Claude to work with.

📁

Internal File System

Access company documents, knowledge bases, or regulated data that can't leave your network. Claude reads your internal wiki without any data leaving your infrastructure.

🌐

API Bridge

Wrap your internal REST/GraphQL APIs as MCP tools. Claude can query Salesforce, Jira, Zendesk, or any custom internal API through your MCP server — one integration, all AI tools.

📊

Data Pipeline

Trigger and monitor ETL jobs, query analytics platforms, or access real-time metrics. Claude becomes your data operations co-pilot, writing queries and interpreting dashboards.

🔐

Secrets Vault

MCP server as a secrets intermediary — Claude requests credentials through the server, which enforces access controls and audit logging. Claude never sees raw secrets.

📧

Communication Hub

Send Slack messages, create Jira tickets, email customers — Claude can take action across your business toolchain. Combine with careful permission scoping for safety.

Hands-on Exercises

🧪 Exercise 1: Your First MCP Server

1
Install the MCP SDK: pip install mcp
2
Create a simple MCP server with one tool: get_weather(city: str) that calls a free weather API (wttr.in is free, no key needed: https://wttr.in/{city}?format=j1)
3
Write the tool description as if Claude has never seen a weather API. Include what it returns and when to use it.
4
Register the server in your Claude Code config and run: claude "What's the weather in Tokyo, London, and New York right now? Which is warmest?"
5
Observe: does Claude call the tool 3 times or once? How does it decide when it has enough data?

🧪 Exercise 2: Error Handling Lab

1
Modify your MCP server to intentionally fail 50% of the time (random.random() > 0.5 → return error)
2
Use retryable=True with suggestion "Try again" in the error response
3
Run a Claude task that requires the tool. Observe: does Claude retry? How many times?
4
Switch to retryable=False and see how Claude's behavior changes

Corporate MCP Deployments

At enterprise scale, MCP is not just a developer convenience — it becomes critical infrastructure. Large organizations are building MCP servers that give Claude access to their entire data and tool ecosystem:

📊 Enterprise MCP Impact

"We built 12 MCP servers in 3 months — one for each major internal system. Now our entire engineering team can ask Claude questions about our data without knowing SQL. The time savings alone paid for the build in 6 weeks."

— Head of Data Engineering, Series B SaaS company