7.1 KiB
7.1 KiB
MCP Servers Guide
Complete guide to creating and using Model Context Protocol (MCP) servers with Claude Agent SDK.
What Are MCP Servers?
MCP servers extend agent capabilities with custom tools. Think of them as plugins that give your agent new abilities.
Use Cases:
- Database access
- API integrations
- Custom calculations
- External service interactions
- File system operations
Creating In-Process MCP Servers
Basic Server
import { createSdkMcpServer, tool } from "@anthropic-ai/claude-agent-sdk";
import { z } from "zod";
const myServer = createSdkMcpServer({
name: "my-service",
version: "1.0.0",
tools: [
tool(
"tool_name",
"Tool description",
{ /* Zod schema */ },
async (args) => {
// Implementation
return {
content: [{ type: "text", text: "Result" }]
};
}
)
]
});
Tool Definition Pattern
tool(
name: string, // Tool identifier
description: string, // What it does
inputSchema: ZodSchema, // Input validation
handler: Handler // Implementation
)
Zod Schemas
Common Patterns
import { z } from "zod";
// String
z.string()
z.string().email()
z.string().url()
z.string().min(5).max(100)
z.string().describe("Description for AI")
// Number
z.number()
z.number().int()
z.number().positive()
z.number().min(0).max(100)
// Boolean
z.boolean()
z.boolean().default(false)
// Enum
z.enum(["option1", "option2", "option3"])
z.union([z.literal("a"), z.literal("b")])
// Optional
z.string().optional()
z.number().default(10)
// Object
z.object({
name: z.string(),
age: z.number(),
email: z.string().email().optional()
})
// Array
z.array(z.string())
z.array(z.number()).min(1).max(10)
// Complex nested
z.object({
user: z.object({
id: z.string().uuid(),
name: z.string(),
roles: z.array(z.enum(["admin", "user", "guest"]))
}),
metadata: z.record(z.any()).optional()
})
Best Practices
// ✅ Good: Clear descriptions
{
location: z.string().describe("City name or coordinates (e.g., 'San Francisco, CA')"),
radius: z.number().min(1).max(100).describe("Search radius in kilometers")
}
// ❌ Bad: No descriptions
{
location: z.string(),
radius: z.number()
}
// ✅ Good: Validation constraints
{
email: z.string().email(),
age: z.number().int().min(0).max(120),
role: z.enum(["admin", "user", "guest"])
}
// ❌ Bad: No validation
{
email: z.string(),
age: z.number(),
role: z.string()
}
Handler Implementation
Success Response
async (args) => {
const result = await performOperation(args);
return {
content: [{
type: "text",
text: JSON.stringify(result, null, 2)
}]
};
}
Error Response
async (args) => {
try {
const result = await riskyOperation(args);
return {
content: [{ type: "text", text: result }]
};
} catch (error) {
return {
content: [{
type: "text",
text: `Error: ${error.message}`
}],
isError: true // Mark as error
};
}
}
Complete Examples
Weather Service
const weatherServer = createSdkMcpServer({
name: "weather",
version: "1.0.0",
tools: [
tool(
"get_weather",
"Get current weather for a location",
{
location: z.string().describe("City name"),
units: z.enum(["celsius", "fahrenheit"]).default("celsius")
},
async (args) => {
const response = await fetch(
`https://api.weather.com/v1/current?location=${args.location}&units=${args.units}`
);
const data = await response.json();
return {
content: [{
type: "text",
text: `Temp: ${data.temp}° ${args.units}\nConditions: ${data.conditions}`
}]
};
}
),
tool(
"get_forecast",
"Get 7-day forecast",
{
location: z.string(),
days: z.number().min(1).max(7).default(7)
},
async (args) => {
const forecast = await fetchForecast(args.location, args.days);
return {
content: [{ type: "text", text: JSON.stringify(forecast, null, 2) }]
};
}
)
]
});
Database Service
const databaseServer = createSdkMcpServer({
name: "database",
version: "1.0.0",
tools: [
tool(
"query",
"Execute SQL query",
{
sql: z.string().describe("SQL query to execute"),
params: z.array(z.any()).optional().describe("Query parameters")
},
async (args) => {
try {
const results = await db.query(args.sql, args.params);
return {
content: [{ type: "text", text: JSON.stringify(results, null, 2) }]
};
} catch (error) {
return {
content: [{ type: "text", text: `SQL Error: ${error.message}` }],
isError: true
};
}
}
)
]
});
Using MCP Servers
In Query Options
const response = query({
prompt: "What's the weather in NYC?",
options: {
mcpServers: {
"weather": weatherServer,
"database": databaseServer
},
allowedTools: [
"mcp__weather__get_weather",
"mcp__database__query"
]
}
});
Tool Naming Convention
Format: mcp__<server-name>__<tool-name>
Examples:
mcp__weather__get_weathermcp__database__querymcp__filesystem__read_file
CRITICAL: Server and tool names must match exactly.
External MCP Servers
Stdio Servers
options: {
mcpServers: {
"filesystem": {
command: "npx",
args: ["@modelcontextprotocol/server-filesystem"],
env: {
ALLOWED_PATHS: "/path/to/allowed/dir"
}
}
}
}
HTTP/SSE Servers
options: {
mcpServers: {
"remote": {
url: "https://api.example.com/mcp",
headers: {
"Authorization": "Bearer token",
"Content-Type": "application/json"
}
}
}
}
Best Practices
✅ Do
- Use clear tool names and descriptions
- Add
.describe()to all Zod fields - Implement error handling in handlers
- Validate inputs with Zod constraints
- Return clear, formatted responses
- Test tools independently before integration
- Use unique tool names across servers
- Version your servers
❌ Don't
- Use generic names like "process" or "run"
- Skip input validation
- Return raw error objects
- Forget
isError: trueon errors - Use duplicate tool names
- Expose sensitive operations without checks
- Skip testing in isolation
Troubleshooting
Tool Not Found
Problem: "Tool mcp__server__tool not found"
Solution:
- Check server name matches
- Check tool name matches
- Include in
allowedToolsarray - Verify server added to
mcpServers
Tool Name Collision
Problem: Two tools with same name
Solution: Use unique names or prefix with server name
Validation Errors
Problem: Invalid input to tool
Solution: Add descriptive Zod schemas with constraints
For more details: See SKILL.md Official MCP docs: https://modelcontextprotocol.io/