Agent skill

model-context-protocol

Model Context Protocol (MCP) - Open standard for connecting AI applications to external data sources, tools, and systems. Use for building MCP servers (tools, resources, prompts), clients, understanding protocol architecture, and implementing AI integrations.

Stars 163
Forks 31

Install this agent skill to your Project

npx add-skill https://github.com/majiayu000/claude-skill-registry/tree/main/skills/data/model-context-protocol

SKILL.md

Model Context Protocol Skill

The Model Context Protocol (MCP) is an open-source standard that provides a universal way to connect AI-powered applications to external data sources, tools, and systems. Think of MCP as a USB-C port for AI applications - a standardized interface that enables seamless integration regardless of the underlying implementation.

Core Value Proposition: Build once, connect anywhere. MCP servers work with any MCP-compatible AI application, eliminating the need for custom integrations per application.

When to Use This Skill

This skill should be triggered when:

  • Building MCP servers to expose tools, resources, or prompts
  • Implementing MCP clients in AI applications
  • Understanding MCP protocol architecture and message flow
  • Creating tool handlers for AI agent operations
  • Implementing resource providers for data access
  • Building prompt templates for AI interactions
  • Integrating external services with AI applications
  • Debugging MCP server/client communication

Protocol Overview

The AI Integration Paradox

MCP addresses a fundamental challenge: AI systems need dynamic, context-aware access to resources, but traditional APIs were built for predictable workflows.

Traditional APIs assume:

  • Known endpoints at design time
  • Predictable request patterns
  • Static data schemas

AI systems require:

  • Dynamic Discovery - Identify needed resources at runtime
  • Rich Context Exchange - Metadata and relationships flow with data
  • Secure Sandboxing - Controlled access without direct AI permissions
  • Bidirectional Communication - Systems ask questions, not just consume

The Problem MCP Solves

Before MCP:

  • Each AI application builds custom integrations for each data source
  • Every data source implements provider-specific APIs
  • N applications × M data sources = N×M integrations

After MCP:

  • One protocol specification
  • N + M implementations needed
  • Any server works with any client

The USB-C Analogy

Just as USB-C provides a universal connector for devices:

  • MCP Host = Device (AI application like Claude Desktop)
  • MCP Client = Port (connection manager within the host)
  • MCP Server = Peripheral (service providing context)

Mediated Access Pattern (Security Broker Model)

"The Host mediates ALL AI-resource interactions"

┌────────────────────────────────────────────────────────┐
│                     HOST APPLICATION                    │
│            (Claude Desktop, IDE, Custom App)            │
│                                                        │
│   ┌──────────┐   ┌──────────┐   ┌──────────┐         │
│   │ Client 1 │   │ Client 2 │   │ Client 3 │         │
│   │   ↕      │   │   ↕      │   │   ↕      │         │
│   │Server A  │   │Server B  │   │Server C  │   1:1   │
│   └──────────┘   └──────────┘   └──────────┘  mapping │
└────────────────────────────────────────────────────────┘

Key security principles:

  • Each Client-Server pair is isolated
  • Host acts as central authority
  • No direct AI-to-resource access
  • Prevents resource interference

Architecture

Core Components

┌─────────────────────────────────────────────────────────────┐
│                    MCP ARCHITECTURE                          │
└─────────────────────────────────────────────────────────────┘

┌──────────────────────────────────────────────────────────────┐
│                        HOST (AI App)                          │
│  ┌────────────────────────────────────────────────────────┐  │
│  │                    MCP CLIENT                           │  │
│  │  • Maintains 1:1 connections with servers              │  │
│  │  • Handles protocol negotiation                         │  │
│  │  • Routes messages to/from servers                      │  │
│  └───────────┬────────────────────────┬───────────────────┘  │
│              │                        │                       │
└──────────────┼────────────────────────┼───────────────────────┘
               │ stdio                  │ HTTP
               ▼                        ▼
┌──────────────────────┐    ┌──────────────────────┐
│    LOCAL SERVER      │    │   REMOTE SERVER      │
│  (subprocess)        │    │   (network)          │
│                      │    │                      │
│  • Tools             │    │  • Tools             │
│  • Resources         │    │  • Resources         │
│  • Prompts           │    │  • Prompts           │
└──────────────────────┘    └──────────────────────┘

Communication Protocol

MCP uses JSON-RPC 2.0 over various transports (Specification: 2024-11-05):

JSON-RPC Requirements:

  • id MUST NOT be null for requests (use string or integer)
  • id MUST be unique within a session
  • Requests expect responses; notifications do not
  • Unknown methods return -32601 (Method not found)
typescript
// Request (id required, must be unique per session)
{
  "jsonrpc": "2.0",
  "id": "req-1",  // String or integer, never null
  "method": "tools/call",
  "params": {
    "name": "get_weather",
    "arguments": { "city": "San Francisco" }
  }
}

// Response (id matches request)
{
  "jsonrpc": "2.0",
  "id": "req-1",
  "result": {
    "content": [{
      "type": "text",
      "text": "Weather in San Francisco: 65°F, partly cloudy"
    }]
  }
}

// Notification (no id, no response expected)
{
  "jsonrpc": "2.0",
  "method": "notifications/resources/updated",
  "params": { "uri": "file:///data/config.json" }
}

Connection Lifecycle

┌─────────────────────────────────────────────────────────────┐
│                  CONNECTION LIFECYCLE                        │
└─────────────────────────────────────────────────────────────┘

1. INITIALIZATION (Version Negotiation)
   Client ──initialize──────► Server
     └─ protocolVersion: "2024-11-05"
     └─ capabilities: { sampling: {}, roots: {} }
     └─ clientInfo: { name, version }

   Client ◄──result────────── Server
     └─ protocolVersion: "2024-11-05"  (server's supported version)
     └─ capabilities: { tools: {}, resources: {} }
     └─ serverInfo: { name, version }

   Client ──initialized──────► Server (notification, no response)

2. OPERATION PHASE
   Client ◄──► Server (bidirectional messages)
   • Client calls server methods (tools/call, resources/read)
   • Server sends notifications (resource updates, progress)
   • Server may call client methods (sampling/createMessage)

3. TERMINATION
   For stdio:  Close input stream, wait for server exit, terminate
   For HTTP:   Send HTTP DELETE with Mcp-Session-Id header

Version Negotiation:

  • Client sends supported protocolVersion in initialize
  • Server responds with its version (SHOULD match or be compatible)
  • If incompatible, client MAY disconnect or proceed with limitations

Server Primitives

MCP servers expose three primary primitives:

1. Tools (Model-Controlled)

Tools are executable functions that AI models can invoke to perform actions:

json
{
  "name": "send_email",
  "description": "Send an email to a recipient",
  "inputSchema": {
    "type": "object",
    "properties": {
      "to": {
        "type": "string",
        "description": "Recipient email address"
      },
      "subject": {
        "type": "string",
        "description": "Email subject line"
      },
      "body": {
        "type": "string",
        "description": "Email body content"
      }
    },
    "required": ["to", "subject", "body"]
  }
}

Tool Call Flow:

1. Client requests: tools/list
2. Server returns: Available tools with schemas
3. Model decides to call tool
4. Client sends: tools/call with arguments
5. Server executes and returns: result content

Tool Result Content Types:

  • text - Plain text response
  • image - Base64-encoded image data
  • audio - Base64-encoded audio data
  • resource - Embedded resource content

Error Handling with isError:

json
// Normal result
{
  "content": [{ "type": "text", "text": "Success!" }],
  "isError": false  // Optional, defaults to false
}

// Execution error (not a JSON-RPC error)
{
  "content": [{ "type": "text", "text": "File not found: /data/missing.txt" }],
  "isError": true  // Tool ran but encountered an error
}

Use isError: true when the tool executed but encountered an expected error (file not found, validation failed, etc.). Use JSON-RPC errors for protocol-level failures.

2. Resources (Application-Controlled)

Resources are data sources that provide context to AI applications:

json
{
  "uri": "file:///projects/myapp/README.md",
  "name": "README.md",
  "description": "Project readme file",
  "mimeType": "text/markdown"
}

Resource URIs:

  • Standard schemes: file://, https://
  • Custom schemes: postgres://, git://
  • Resource templates: file:///{path} (parameterized)

Resource Templates:

json
{
  "uriTemplate": "file:///{path}",
  "name": "Project Files",
  "description": "Access files in the project directory",
  "mimeType": "text/plain"
}

Templates use URI Template syntax (RFC 6570) for parameterized resource access. Servers that support templates should also expose completion/complete for auto-completion.

Content Types:

json
// Text content
{
  "uri": "file:///README.md",
  "mimeType": "text/markdown",
  "text": "# Project Title\n..."
}

// Binary content (blob)
{
  "uri": "file:///image.png",
  "mimeType": "image/png",
  "blob": "iVBORw0KGgoAAAANSUhEUgAA..."  // base64-encoded
}

Resource Operations:

json
// List resources
{ "method": "resources/list" }

// Read resource
{
  "method": "resources/read",
  "params": { "uri": "file:///data/config.json" }
}

// Subscribe to changes
{
  "method": "resources/subscribe",
  "params": { "uri": "file:///data/config.json" }
}

3. Prompts (User-Controlled)

Prompts are reusable templates for AI interactions:

json
{
  "name": "code_review",
  "title": "Request Code Review",
  "description": "Analyze code quality and suggest improvements",
  "arguments": [
    {
      "name": "code",
      "description": "The code to review",
      "required": true
    },
    {
      "name": "language",
      "description": "Programming language",
      "required": false
    }
  ]
}

Prompt Messages:

json
{
  "method": "prompts/get",
  "params": {
    "name": "code_review",
    "arguments": {
      "code": "def hello(): print('world')",
      "language": "python"
    }
  }
}

// Response
{
  "result": {
    "messages": [
      {
        "role": "user",
        "content": {
          "type": "text",
          "text": "Please review this Python code:\ndef hello(): print('world')"
        }
      }
    ]
  }
}

Client Features

Sampling (Server → LLM)

Servers can request LLM completions through the client:

json
{
  "method": "sampling/createMessage",
  "params": {
    "messages": [
      {
        "role": "user",
        "content": {
          "type": "text",
          "text": "Summarize this document..."
        }
      }
    ],
    "modelPreferences": {
      "hints": [{ "name": "claude-3-sonnet" }],
      "intelligencePriority": 0.8,
      "speedPriority": 0.5
    },
    "systemPrompt": "You are a helpful assistant.",
    "maxTokens": 500
  }
}

Model Preferences (0-1 scale):

  • costPriority - Prefer cheaper models
  • speedPriority - Prefer faster models
  • intelligencePriority - Prefer more capable models

Human-in-the-Loop: Sampling requests SHOULD be reviewed by users before execution.

Roots (Context Boundaries)

Clients can expose filesystem roots to servers:

json
{
  "capabilities": {
    "roots": {
      "listChanged": true
    }
  }
}

Roots define boundaries for server access, allowing servers to understand which directories or resources they can interact with.


Utilities

MCP includes base utilities and server utilities for protocol-level operations.

Base Utilities

Ping (Connection Health):

json
// Request
{ "jsonrpc": "2.0", "id": 1, "method": "ping" }

// Response
{ "jsonrpc": "2.0", "id": 1, "result": {} }

Used to check connection health. Either party can send ping; receiver MUST respond promptly.

Cancellation:

json
{
  "jsonrpc": "2.0",
  "method": "notifications/cancelled",
  "params": {
    "requestId": "req-123",
    "reason": "User cancelled operation"
  }
}

Notification to cancel a pending request. The receiver SHOULD stop processing and MAY return a partial result or error.

Progress Notifications:

json
{
  "jsonrpc": "2.0",
  "method": "notifications/progress",
  "params": {
    "progressToken": "token-456",
    "progress": 50,
    "total": 100,
    "message": "Processing files..."
  }
}

For long-running operations. The progressToken is provided in the original request's _meta.progressToken.

Server Utilities

Completion (Auto-complete):

json
// Request
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "completion/complete",
  "params": {
    "ref": {
      "type": "ref/resource",
      "uri": "file:///{path}"
    },
    "argument": {
      "name": "path",
      "value": "src/"
    }
  }
}

// Response
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "completion": {
      "values": ["src/index.ts", "src/utils/", "src/types/"],
      "hasMore": false
    }
  }
}

Provides auto-completion suggestions for resource template arguments or prompt arguments.

Logging:

json
{
  "jsonrpc": "2.0",
  "method": "notifications/message",
  "params": {
    "level": "info",  // debug, info, notice, warning, error, critical, alert, emergency
    "logger": "database",
    "data": "Connected to PostgreSQL at localhost:5432"
  }
}

Servers can send log messages to clients. The client MAY filter based on level threshold set via logging/setLevel.

Pagination: For large result sets, use cursor-based pagination:

json
// Request with cursor
{
  "method": "tools/list",
  "params": { "cursor": "eyJvZmZzZXQiOjEwMH0=" }
}

// Response with next cursor
{
  "result": {
    "tools": [...],
    "nextCursor": "eyJvZmZzZXQiOjIwMH0="  // null if no more results
  }
}

Cursors are opaque strings. Clients SHOULD NOT assume any structure.


Transports

stdio Transport (Local)

For subprocess-based communication:

bash
# Server launched by client as subprocess
$ my-mcp-server

# Communication via stdin/stdout
Server reads: stdin (JSON-RPC messages)
Server writes: stdout (JSON-RPC responses)
Server logs: stderr (debugging only)

Requirements:

  • Messages delimited by newlines
  • Must NOT contain embedded newlines
  • Client SHOULD support stdio whenever possible

Streamable HTTP Transport (Remote)

For network-based communication:

POST /mcp HTTP/1.1
Content-Type: application/json
Accept: application/json, text/event-stream
MCP-Protocol-Version: 2025-06-18

{"jsonrpc":"2.0","id":1,"method":"tools/list"}

Response Types:

  • application/json - Single JSON response
  • text/event-stream - SSE stream for multiple messages

Session Management:

1. Server returns: Mcp-Session-Id header
2. Client includes: Mcp-Session-Id in subsequent requests
3. Server MAY: Return 404 to terminate session
4. Client MAY: DELETE with session ID to close

Security Requirements:

  • Validate Origin header (prevent DNS rebinding)
  • Local servers bind to localhost only
  • Implement authentication for remote access

SDK Installation

TypeScript

bash
npm install @modelcontextprotocol/sdk
typescript
import { McpServer, StdioServerTransport } from "@modelcontextprotocol/sdk/server";

const server = new McpServer({
  name: "my-server",
  version: "1.0.0"
});

// Add a tool
server.tool("get_weather", {
  description: "Get weather for a city",
  inputSchema: {
    type: "object",
    properties: {
      city: { type: "string", description: "City name" }
    },
    required: ["city"]
  }
}, async (args) => {
  const weather = await fetchWeather(args.city);
  return {
    content: [{ type: "text", text: `Weather: ${weather}` }]
  };
});

// Start server
const transport = new StdioServerTransport();
await server.connect(transport);

GitHub: https://github.com/modelcontextprotocol/typescript-sdk

Python

bash
pip install mcp
# or with uv
uv add mcp
python
from mcp.server import Server
from mcp.server.stdio import stdio_server

server = Server("my-server")

@server.tool()
async def get_weather(city: str) -> str:
    """Get weather for a city."""
    weather = await fetch_weather(city)
    return f"Weather: {weather}"

@server.resource("config://app")
async def get_config() -> str:
    """Get application configuration."""
    return json.dumps(config)

async def main():
    async with stdio_server() as (read, write):
        await server.run(read, write)

if __name__ == "__main__":
    import asyncio
    asyncio.run(main())

GitHub: https://github.com/modelcontextprotocol/python-sdk

Other SDKs

Language Installation Repository
Go go get github.com/modelcontextprotocol/go-sdk go-sdk
Kotlin Maven/Gradle kotlin-sdk
Swift Swift Package Manager swift-sdk
Java Maven java-sdk
C# NuGet csharp-sdk
Ruby gem install mcp ruby-sdk
Rust cargo add mcp rust-sdk
PHP Composer php-sdk

Building an MCP Server

Minimal TypeScript Server

typescript
import { McpServer, StdioServerTransport } from "@modelcontextprotocol/sdk/server";

const server = new McpServer({
  name: "example-server",
  version: "1.0.0",
  capabilities: {
    tools: {},
    resources: {},
    prompts: {}
  }
});

// Tool: Calculate
server.tool("calculate", {
  description: "Perform basic calculations",
  inputSchema: {
    type: "object",
    properties: {
      operation: { type: "string", enum: ["add", "subtract", "multiply", "divide"] },
      a: { type: "number" },
      b: { type: "number" }
    },
    required: ["operation", "a", "b"]
  }
}, async ({ operation, a, b }) => {
  let result: number;
  switch (operation) {
    case "add": result = a + b; break;
    case "subtract": result = a - b; break;
    case "multiply": result = a * b; break;
    case "divide": result = a / b; break;
  }
  return {
    content: [{ type: "text", text: `Result: ${result}` }]
  };
});

// Resource: Static config
server.resource("config://app", {
  name: "App Configuration",
  description: "Application settings",
  mimeType: "application/json"
}, async () => {
  return {
    contents: [{
      uri: "config://app",
      mimeType: "application/json",
      text: JSON.stringify({ version: "1.0", debug: false })
    }]
  };
});

// Prompt: Greeting
server.prompt("greeting", {
  name: "greeting",
  description: "Generate a personalized greeting",
  arguments: [
    { name: "name", description: "Person's name", required: true }
  ]
}, async ({ name }) => {
  return {
    messages: [{
      role: "user",
      content: { type: "text", text: `Please greet ${name} warmly.` }
    }]
  };
});

// Connect transport
const transport = new StdioServerTransport();
await server.connect(transport);

Minimal Python Server

python
from mcp.server import Server
from mcp.server.stdio import stdio_server
from mcp.types import Tool, TextContent, Resource, Prompt, PromptMessage

server = Server("example-server")

# Tool: Calculate
@server.tool()
async def calculate(operation: str, a: float, b: float) -> list[TextContent]:
    """Perform basic calculations (add, subtract, multiply, divide)."""
    ops = {
        "add": a + b,
        "subtract": a - b,
        "multiply": a * b,
        "divide": a / b if b != 0 else float('inf')
    }
    result = ops.get(operation, 0)
    return [TextContent(type="text", text=f"Result: {result}")]

# Resource: Config
@server.resource("config://app")
async def get_config() -> str:
    """Application configuration."""
    return '{"version": "1.0", "debug": false}'

# Prompt: Greeting
@server.prompt()
async def greeting(name: str) -> list[PromptMessage]:
    """Generate a personalized greeting."""
    return [
        PromptMessage(
            role="user",
            content=TextContent(type="text", text=f"Please greet {name} warmly.")
        )
    ]

async def main():
    async with stdio_server() as (read, write):
        await server.run(read, write)

if __name__ == "__main__":
    import asyncio
    asyncio.run(main())

Building an MCP Client

TypeScript Client

typescript
import { McpClient, StdioClientTransport } from "@modelcontextprotocol/sdk/client";
import { spawn } from "child_process";

// Spawn server as subprocess
const serverProcess = spawn("node", ["path/to/server.js"]);

// Create client
const client = new McpClient({
  name: "my-client",
  version: "1.0.0"
});

// Connect via stdio
const transport = new StdioClientTransport({
  reader: serverProcess.stdout,
  writer: serverProcess.stdin
});

await client.connect(transport);

// Initialize and get capabilities
const capabilities = await client.initialize();
console.log("Server capabilities:", capabilities);

// List available tools
const tools = await client.listTools();
console.log("Available tools:", tools);

// Call a tool
const result = await client.callTool("calculate", {
  operation: "add",
  a: 5,
  b: 3
});
console.log("Tool result:", result);

// List and read resources
const resources = await client.listResources();
const config = await client.readResource("config://app");
console.log("Config:", config);

// Get a prompt
const prompt = await client.getPrompt("greeting", { name: "Alice" });
console.log("Prompt messages:", prompt.messages);

// Cleanup
await client.close();
serverProcess.kill();

Python Client

python
from mcp.client import ClientSession
from mcp.client.stdio import stdio_client
import subprocess
import asyncio

async def main():
    # Spawn server subprocess
    server = subprocess.Popen(
        ["python", "path/to/server.py"],
        stdin=subprocess.PIPE,
        stdout=subprocess.PIPE
    )

    # Connect client
    async with stdio_client(server.stdin, server.stdout) as (read, write):
        async with ClientSession(read, write) as session:
            # Initialize
            await session.initialize()

            # List tools
            tools = await session.list_tools()
            print("Tools:", tools)

            # Call tool
            result = await session.call_tool("calculate", {
                "operation": "multiply",
                "a": 7,
                "b": 6
            })
            print("Result:", result)

            # Read resource
            config = await session.read_resource("config://app")
            print("Config:", config)

if __name__ == "__main__":
    asyncio.run(main())

Capabilities Negotiation

Servers and clients exchange capabilities during initialization:

Server Capabilities

json
{
  "capabilities": {
    "tools": {
      "listChanged": true
    },
    "resources": {
      "subscribe": true,
      "listChanged": true
    },
    "prompts": {
      "listChanged": true
    },
    "logging": {}
  }
}

Client Capabilities

json
{
  "capabilities": {
    "sampling": {},
    "roots": {
      "listChanged": true
    }
  }
}

Capability Flags

Capability Flag Description
tools.listChanged boolean Server sends notifications when tools change
resources.subscribe boolean Client can subscribe to resource updates
resources.listChanged boolean Server sends notifications when resources change
prompts.listChanged boolean Server sends notifications when prompts change
sampling object Client supports LLM sampling requests
roots.listChanged boolean Client sends notifications when roots change

Error Handling

JSON-RPC Error Codes

Code Name Description
-32700 Parse error Invalid JSON
-32600 Invalid Request Not a valid JSON-RPC request
-32601 Method not found Unknown method name
-32602 Invalid params Invalid method parameters
-32603 Internal error Server-side error

Error Response Format

json
{
  "jsonrpc": "2.0",
  "id": 1,
  "error": {
    "code": -32602,
    "message": "Invalid params",
    "data": {
      "details": "Missing required parameter: city"
    }
  }
}

Best Practices

  1. Validate inputs before processing
  2. Return descriptive errors with actionable messages
  3. Use appropriate error codes for different failure types
  4. Include error data for debugging when helpful
  5. Log errors for server-side troubleshooting

Security Considerations

Transport Security

  • stdio: Inherently secure (same machine)
  • HTTP: Use HTTPS in production
  • Origin validation: Prevent DNS rebinding attacks
  • Session tokens: Cryptographically secure (UUID, JWT)

Input Validation

typescript
// Always validate tool inputs
server.tool("query_database", schema, async (args) => {
  // Validate SQL to prevent injection
  if (!isValidQuery(args.query)) {
    throw new Error("Invalid query format");
  }
  // Sanitize parameters
  const sanitizedParams = sanitize(args.params);
  // Execute with prepared statements
  return await db.query(args.query, sanitizedParams);
});

Resource Access

typescript
// Validate resource URIs
server.resource("file://{path}", async (uri, params) => {
  const path = params.path;
  // Prevent directory traversal
  if (path.includes("..") || path.startsWith("/")) {
    throw new Error("Invalid path");
  }
  // Check allowed directories
  if (!isInAllowedDirectory(path)) {
    throw new Error("Access denied");
  }
  return await readFile(path);
});

Sampling Security

  • Human-in-the-loop: Always allow user review
  • Rate limiting: Prevent abuse
  • Content filtering: Validate request/response content
  • Cost controls: Set token limits

Best Practices

For Server Developers

  1. Implement clear tool descriptions - Models rely on these to decide when to use tools
  2. Use JSON Schema properly - Define required fields, types, and constraints
  3. Return structured content - Use appropriate content types (text, image, resource)
  4. Handle errors gracefully - Provide actionable error messages
  5. Support notifications - Emit listChanged when capabilities update
  6. Implement pagination - For large resource/tool lists
  7. Document your server - Describe capabilities and usage patterns

For Client Developers

  1. Handle all message types - Requests, responses, notifications
  2. Implement timeout handling - Don't block indefinitely
  3. Support reconnection - Handle transport failures gracefully
  4. Respect capabilities - Only use features the server supports
  5. Implement human-in-the-loop - For sampling requests
  6. Cache appropriately - Tools/resources/prompts lists

Security Best Practices

  1. Validate all inputs - Never trust user or model input
  2. Use least privilege - Request only necessary permissions
  3. Sanitize outputs - Prevent injection attacks
  4. Implement rate limiting - Protect against abuse
  5. Log audit trails - Track all operations
  6. Use secure transports - HTTPS for remote, validate origins

Production Operations

Performance Targets

Production MCP servers should target:

Metric Target Notes
Throughput >1,000 req/sec Per instance
Latency (P95) <100ms Simple operations
Error Rate <0.1% Under normal conditions
Availability >99.9% Uptime target

Architectural Principles

Single Responsibility: Each server should have one clear, well-defined purpose:

✅ weather-server     → Only weather data
✅ database-server    → Only database operations
❌ everything-server  → Too broad, hard to maintain

Defense in Depth: Layer security controls:

Network → Authentication → Authorization → Input Validation → Output Sanitization

Fail-Safe Design: Graceful degradation:

  • Circuit breakers for external APIs
  • Caching fallbacks when services are down
  • Safe defaults when configuration missing

Health Checks

Expose comprehensive health endpoints:

json
{
  "status": "healthy",
  "checks": {
    "database": "connected",
    "cache": "available",
    "external_api": "reachable",
    "disk_space": "sufficient",
    "memory": "normal"
  },
  "uptime": 86400,
  "version": "1.0.0"
}

Monitoring & Observability

Key Metrics to Collect:

  • Request count, latency (P50, P95, P99)
  • Active connections
  • Error count by type
  • Resource usage (CPU, memory)

Structured Logging:

json
{
  "timestamp": "2024-01-15T10:30:00Z",
  "level": "info",
  "client_id": "client-123",
  "method": "tools/call",
  "tool": "get_weather",
  "duration_ms": 45,
  "status": "success"
}

Testing and Debugging

MCP Inspector

The MCP Inspector is an interactive developer tool for testing and debugging MCP servers. It provides a web-based interface without requiring installation.

Basic Usage:

bash
# Run directly via npx (no installation needed)
npx @modelcontextprotocol/inspector <command>

# With arguments
npx @modelcontextprotocol/inspector <command> <arg1> <arg2>

Inspecting Different Server Types:

bash
# NPM packages
npx -y @modelcontextprotocol/inspector npx @modelcontextprotocol/server-filesystem /path/to/dir

# PyPI packages
npx @modelcontextprotocol/inspector uvx mcp-server-git --repository ~/code/repo.git

# Local TypeScript server
npx @modelcontextprotocol/inspector node path/to/server/index.js args...

# Local Python server
npx @modelcontextprotocol/inspector uv --directory path/to/server run package-name args...

Inspector UI Panels:

Panel Purpose
Server Connection Configure transport, command-line args, environment variables
Resources Tab List resources, view metadata, inspect content, test subscriptions
Prompts Tab View templates, test with custom arguments, preview generated messages
Tools Tab Browse tool schemas, test with custom inputs, view execution results
Notifications Monitor server logs and notifications in real-time

Development Workflow:

┌─────────────────────────────────────────────────────────────┐
│                INSPECTOR DEVELOPMENT WORKFLOW                │
└─────────────────────────────────────────────────────────────┘

1. VERIFICATION
   • Launch Inspector with your server
   • Verify basic connectivity
   • Check capability negotiation

2. ITERATION
   • Make server code changes
   • Rebuild server
   • Reconnect Inspector
   • Test affected features
   • Monitor JSON-RPC messages

3. EDGE CASES
   • Test invalid inputs
   • Missing prompt arguments
   • Concurrent operations
   • Verify error handling

Repository: https://github.com/modelcontextprotocol/inspector

Debugging Tips

  1. Enable verbose logging - Set DEBUG=mcp:* environment variable
  2. Inspect JSON-RPC messages - Log raw request/response pairs
  3. Test tools individually - Before integrating with clients
  4. Validate schemas - Ensure input/output schemas are correct
  5. Check capabilities - Verify both sides support required features

Common Debugging Scenarios

Server won't connect:

bash
# Check if server starts independently
node path/to/server.js

# Check for port conflicts
lsof -i :3000

# Verify environment variables are set
env | grep API_KEY

Tool calls failing:

typescript
// Add debug logging to tool handler
server.tool("my_tool", schema, async (args) => {
  console.error("Tool called with:", JSON.stringify(args));
  try {
    const result = await processArgs(args);
    console.error("Tool result:", JSON.stringify(result));
    return result;
  } catch (error) {
    console.error("Tool error:", error);
    throw error;
  }
});

Capability mismatch:

bash
# In Inspector, check the initialization exchange:
# 1. Client capabilities sent
# 2. Server capabilities returned
# 3. Verify both sides support required features

Claude Desktop Debugging

Log locations:

bash
# macOS
~/Library/Logs/Claude/mcp*.log

# Windows
%APPDATA%\Claude\logs\mcp*.log

# Linux
~/.config/Claude/logs/mcp*.log

# Real-time log monitoring (macOS)
tail -n 20 -F ~/Library/Logs/Claude/mcp*.log

Enable Chrome DevTools:

bash
# Create developer settings file
echo '{"allowDevTools": true}' > ~/Library/Application\ Support/Claude/developer_settings.json

# Access DevTools: Command-Option-Shift-i (macOS)
# Use Console panel for client-side errors
# Use Network panel for message payloads and timing

Checking Server Status in Claude Desktop:

  • Click the plug icon to view connected servers, available prompts, and resources
  • Click the "Search and tools" slider icon to view tools available to the model

Common issues:

  • Malformed JSON in config file
  • Relative paths instead of absolute (servers often start with / as working directory)
  • Missing environment variables (servers only inherit USER, HOME, PATH by default)
  • Rate limiting from external APIs

Environment Variable Gotcha:

json
{
  "myserver": {
    "command": "mcp-server-myapp",
    "env": {
      "MYAPP_API_KEY": "your-key-here",
      "DATABASE_URL": "postgres://localhost/mydb"
    }
  }
}

Reloading Changes:

  • Configuration changes: Restart Claude Desktop completely
  • Server code changes: Use Command-R to reload servers

Server-Side Logging

Important: Log to stderr, NOT stdout (stdout interferes with JSON-RPC protocol).

Python:

python
import sys

# Direct stderr logging
print("Debug: Processing request", file=sys.stderr)

# Using MCP logging API
server.request_context.session.send_log_message(
    level="info",
    data="Server started successfully"
)

TypeScript:

typescript
// Direct stderr logging
console.error("Debug: Processing request");

// Using MCP logging API
server.sendLoggingMessage({
  level: "info",
  data: "Server started successfully"
});

What to Log:

  • Initialization steps and configuration loaded
  • Resource access (URI, user, timestamp)
  • Tool execution (name, arguments, duration, result type)
  • Error conditions with stack traces
  • Performance metrics (operation timing, message sizes)

Debugging Workflow

┌─────────────────────────────────────────────────────────────┐
│                    DEBUGGING WORKFLOW                        │
└─────────────────────────────────────────────────────────────┘

1. INITIAL DEVELOPMENT
   └─ Use MCP Inspector for basic testing
   └─ Implement core functionality
   └─ Add logging at key points

2. INTEGRATION TESTING
   └─ Test in Claude Desktop
   └─ Monitor logs in real-time
   └─ Check error handling paths

3. TROUBLESHOOTING
   └─ Check server logs first
   └─ Verify configuration syntax
   └─ Test standalone with Inspector
   └─ Review environment variables

Getting Help

First steps:

  1. Check server logs for errors
  2. Test with MCP Inspector standalone
  3. Review claude_desktop_config.json syntax
  4. Verify environment variables are set

When reporting issues, provide:

  • Relevant log excerpts
  • Configuration files (sanitized)
  • Steps to reproduce
  • Environment details (OS, Node/Python version)

Integration Patterns

Claude Desktop Configuration

Add MCP servers to Claude Desktop's configuration:

json
{
  "mcpServers": {
    "my-server": {
      "command": "node",
      "args": ["/path/to/server.js"],
      "env": {
        "API_KEY": "your-key"
      }
    },
    "remote-server": {
      "url": "https://mcp.example.com/",
      "headers": {
        "Authorization": "Bearer token"
      }
    }
  }
}

AI SDK Integration

MCP works with various AI frameworks:

typescript
// With Vercel AI SDK
import { experimental_createMCPClient } from "ai";

const mcpClient = await experimental_createMCPClient({
  transport: { type: "stdio", command: "node", args: ["server.js"] }
});

const tools = await mcpClient.tools();
// Use tools with AI model...

Resources

Official Documentation

SDKs

Tools

Community


Version History

  • 1.4.0 (2026-01-10): Enhanced with comprehensive debugging documentation

    • Enhanced Claude Desktop Debugging:
      • Real-time log monitoring commands
      • Chrome DevTools integration (enable, access, panels)
      • Server status checking via UI icons
      • Environment variable inheritance gotcha (USER, HOME, PATH only)
      • Reloading changes (config vs code)
    • Added Server-Side Logging section:
      • stderr vs stdout importance
      • Python and TypeScript logging examples
      • MCP logging API usage
      • What to log checklist
    • Added Debugging Workflow diagram
    • Added Getting Help section with issue reporting guidance
  • 1.3.0 (2026-01-10): Enhanced Testing and Debugging with MCP Inspector

    • Comprehensive MCP Inspector documentation:
      • Installation-free usage via npx
      • Commands for NPM, PyPI, and local servers
      • UI panels table (Server Connection, Resources, Prompts, Tools, Notifications)
      • Development workflow diagram (Verification → Iteration → Edge Cases)
    • Added Common Debugging Scenarios section:
      • Server connection troubleshooting
      • Tool call debugging with logging examples
      • Capability mismatch diagnosis
    • Added Claude Desktop Debugging:
      • Log file locations (macOS, Windows, Linux)
      • Common configuration issues
  • 1.2.0 (2026-01-10): Enhanced with official documentation content

    • Added AI Integration Paradox design philosophy
    • Added Mediated Access Pattern (security broker model)
    • Added Production Operations section:
      • Performance targets (throughput, latency, error rate)
      • Architectural principles (single responsibility, defense in depth)
      • Health checks and monitoring patterns
      • Structured logging examples
    • Enhanced protocol overview with dynamic discovery concepts
  • 1.1.0 (2026-01-10): Enhanced with official specification details

    • Added specification version reference (2024-11-05)
    • Enhanced JSON-RPC requirements (id uniqueness, null prohibition)
    • Added detailed lifecycle with version negotiation
    • Added shutdown procedures (stdio vs HTTP)
    • Added tool isError handling for execution errors
    • Added resource templates with URI Template syntax (RFC 6570)
    • Added blob content type for binary resources
    • Added Utilities section:
      • Ping (connection health)
      • Cancellation (notifications/cancelled)
      • Progress notifications
      • Completion (auto-complete)
      • Logging (notifications/message)
      • Pagination (cursor-based)
  • 1.0.0 (2026-01-10): Initial skill release

    • Complete protocol overview (architecture, primitives, transports)
    • Server development guide (tools, resources, prompts)
    • Client development guide (connecting, calling, sampling)
    • 10 SDKs documented (TypeScript, Python, Go, Kotlin, Swift, Java, C#, Ruby, Rust, PHP)
    • Security best practices
    • Testing and debugging guidance
    • Integration patterns (Claude Desktop, AI SDKs)

Didn't find tool you were looking for?

Be as detailed as possible for better results