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.
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:
idMUST NOT be null for requests (use string or integer)idMUST be unique within a session- Requests expect responses; notifications do not
- Unknown methods return
-32601(Method not found)
// 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
protocolVersionininitialize - 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:
{
"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 responseimage- Base64-encoded image dataaudio- Base64-encoded audio dataresource- Embedded resource content
Error Handling with isError:
// 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:
{
"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:
{
"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:
// 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:
// 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:
{
"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:
{
"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:
{
"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 modelsspeedPriority- Prefer faster modelsintelligencePriority- 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:
{
"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):
// 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:
{
"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:
{
"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):
// 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:
{
"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:
// 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:
# 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 responsetext/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
Originheader (prevent DNS rebinding) - Local servers bind to localhost only
- Implement authentication for remote access
SDK Installation
TypeScript
npm install @modelcontextprotocol/sdk
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
pip install mcp
# or with uv
uv add mcp
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
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
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
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
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
{
"capabilities": {
"tools": {
"listChanged": true
},
"resources": {
"subscribe": true,
"listChanged": true
},
"prompts": {
"listChanged": true
},
"logging": {}
}
}
Client Capabilities
{
"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
{
"jsonrpc": "2.0",
"id": 1,
"error": {
"code": -32602,
"message": "Invalid params",
"data": {
"details": "Missing required parameter: city"
}
}
}
Best Practices
- Validate inputs before processing
- Return descriptive errors with actionable messages
- Use appropriate error codes for different failure types
- Include error data for debugging when helpful
- 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
// 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
// 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
- Implement clear tool descriptions - Models rely on these to decide when to use tools
- Use JSON Schema properly - Define required fields, types, and constraints
- Return structured content - Use appropriate content types (text, image, resource)
- Handle errors gracefully - Provide actionable error messages
- Support notifications - Emit
listChangedwhen capabilities update - Implement pagination - For large resource/tool lists
- Document your server - Describe capabilities and usage patterns
For Client Developers
- Handle all message types - Requests, responses, notifications
- Implement timeout handling - Don't block indefinitely
- Support reconnection - Handle transport failures gracefully
- Respect capabilities - Only use features the server supports
- Implement human-in-the-loop - For sampling requests
- Cache appropriately - Tools/resources/prompts lists
Security Best Practices
- Validate all inputs - Never trust user or model input
- Use least privilege - Request only necessary permissions
- Sanitize outputs - Prevent injection attacks
- Implement rate limiting - Protect against abuse
- Log audit trails - Track all operations
- 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:
{
"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:
{
"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:
# Run directly via npx (no installation needed)
npx @modelcontextprotocol/inspector <command>
# With arguments
npx @modelcontextprotocol/inspector <command> <arg1> <arg2>
Inspecting Different Server Types:
# 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
- Enable verbose logging - Set
DEBUG=mcp:*environment variable - Inspect JSON-RPC messages - Log raw request/response pairs
- Test tools individually - Before integrating with clients
- Validate schemas - Ensure input/output schemas are correct
- Check capabilities - Verify both sides support required features
Common Debugging Scenarios
Server won't connect:
# 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:
// 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:
# 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:
# 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:
# 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,PATHby default) - Rate limiting from external APIs
Environment Variable Gotcha:
{
"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-Rto reload servers
Server-Side Logging
Important: Log to stderr, NOT stdout (stdout interferes with JSON-RPC protocol).
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:
// 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:
- Check server logs for errors
- Test with MCP Inspector standalone
- Review
claude_desktop_config.jsonsyntax - 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:
{
"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:
// 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,PATHonly) - 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
- Enhanced Claude Desktop Debugging:
-
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
- Comprehensive MCP Inspector documentation:
-
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
isErrorhandling 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?