The Model Context Protocol (MCP) is an open standard that lets AI models like Claude use external tools, read live data, and follow reusable prompt templates — all through a standard interface. An MCP server exposes tools (functions Claude can call), resources (data Claude can read), and prompts (templated instructions). Claude Code writes MCP server implementations: tool handlers, resource readers, schema definitions, and the auth patterns that connect Claude to your internal APIs securely.
CLAUDE.md for MCP Server Projects
## MCP Stack
- SDK: @modelcontextprotocol/sdk (TypeScript) or mcp (Python)
- Transport: stdio for local tools (desktop), HTTP/SSE for hosted servers
- Tools: well-typed Zod/JSON Schema inputs; always return structured content
- Resources: URI-based data access (myapp://orders/123, db://tables/orders)
- Prompts: reusable templates with arguments for common agent workflows
- Auth: OAuth 2.1 for hosted servers; no auth needed for local stdio
- Error handling: return isError: true in tool result — never throw unhandled errors
TypeScript MCP Server
// src/server.ts — MCP server with tools and resources
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { CallToolRequestSchema, ListToolsRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema } from '@modelcontextprotocol/sdk/types.js';
import { z } from 'zod';
const server = new Server(
{
name: 'orders-mcp-server',
version: '1.0.0',
},
{
capabilities: {
tools: {},
resources: {},
prompts: {},
},
}
);
// Tool definitions
const TOOLS = [
{
name: 'search_orders',
description: 'Search orders by customer ID, status, or date range. Returns a list of matching orders.',
inputSchema: {
type: 'object',
properties: {
customerId: { type: 'string', description: 'Filter by customer ID (optional)' },
status: {
type: 'string',
enum: ['pending', 'processing', 'shipped', 'delivered', 'cancelled'],
description: 'Filter by status (optional)',
},
limit: { type: 'number', minimum: 1, maximum: 50, default: 10 },
},
},
},
{
name: 'get_order_details',
description: 'Get full details for a specific order including items, shipping, and timeline.',
inputSchema: {
type: 'object',
properties: {
orderId: { type: 'string', description: 'Order ID to look up' },
},
required: ['orderId'],
},
},
{
name: 'create_order_note',
description: 'Add an internal note to an order. Use for documenting customer interactions.',
inputSchema: {
type: 'object',
properties: {
orderId: { type: 'string' },
note: { type: 'string', maxLength: 1000 },
author: { type: 'string' },
},
required: ['orderId', 'note'],
},
},
];
// Handle tool listing
server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: TOOLS,
}));
// Handle tool execution
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
try {
switch (name) {
case 'search_orders': {
const { customerId, status, limit = 10 } = args as any;
const orders = await db.searchOrders({ customerId, status, limit });
return {
content: [
{
type: 'text',
text: JSON.stringify(orders, null, 2),
},
],
};
}
case 'get_order_details': {
const { orderId } = args as { orderId: string };
const order = await db.getOrder(orderId);
if (!order) {
return {
isError: true,
content: [{ type: 'text', text: `Order ${orderId} not found` }],
};
}
return {
content: [
{
type: 'text',
text: JSON.stringify(order, null, 2),
},
],
};
}
case 'create_order_note': {
const { orderId, note, author = 'AI Assistant' } = args as any;
await db.addOrderNote(orderId, { note, author, createdAt: new Date() });
return {
content: [{ type: 'text', text: `Note added to order ${orderId}` }],
};
}
default:
return {
isError: true,
content: [{ type: 'text', text: `Unknown tool: ${name}` }],
};
}
} catch (err) {
return {
isError: true,
content: [{ type: 'text', text: `Error: ${err instanceof Error ? err.message : String(err)}` }],
};
}
});
Resources
// Resources: URI-addressable data Claude can read as context
server.setRequestHandler(ListResourcesRequestSchema, async () => ({
resources: [
{
uri: 'orders://recent',
name: 'Recent Orders',
description: 'The 20 most recent orders across all customers',
mimeType: 'application/json',
},
{
uri: 'orders://stats/today',
name: 'Today\'s Order Stats',
description: 'Order counts, revenue, and status breakdown for today',
mimeType: 'application/json',
},
],
}));
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
const { uri } = request.params;
if (uri === 'orders://recent') {
const orders = await db.getRecentOrders(20);
return {
contents: [
{
uri,
mimeType: 'application/json',
text: JSON.stringify(orders, null, 2),
},
],
};
}
if (uri === 'orders://stats/today') {
const stats = await db.getTodayStats();
return {
contents: [
{
uri,
mimeType: 'application/json',
text: JSON.stringify(stats, null, 2),
},
],
};
}
throw new Error(`Unknown resource: ${uri}`);
});
Python MCP Server
# server.py — MCP server in Python
from mcp.server import Server
from mcp.server.stdio import stdio_server
from mcp.types import Tool, TextContent, CallToolResult
app = Server("orders-mcp-server")
@app.list_tools()
async def list_tools():
return [
Tool(
name="search_orders",
description="Search orders by customer ID or status",
inputSchema={
"type": "object",
"properties": {
"customerId": {"type": "string"},
"status": {"type": "string"},
},
},
),
]
@app.call_tool()
async def call_tool(name: str, arguments: dict) -> list[TextContent]:
if name == "search_orders":
orders = await db.search_orders(**arguments)
return [TextContent(type="text", text=json.dumps(orders, indent=2, default=str))]
raise ValueError(f"Unknown tool: {name}")
async def main():
async with stdio_server() as (read_stream, write_stream):
await app.run(read_stream, write_stream, app.create_initialization_options())
if __name__ == "__main__":
import asyncio
asyncio.run(main())
Claude Desktop Configuration
// ~/.config/claude-desktop/claude_desktop_config.json (macOS: ~/Library/Application Support/)
{
"mcpServers": {
"orders": {
"command": "node",
"args": ["/path/to/orders-mcp-server/dist/server.js"],
"env": {
"DATABASE_URL": "postgresql://...",
"API_KEY": "..."
}
},
"orders-python": {
"command": "python",
"args": ["/path/to/orders-mcp-server/server.py"]
}
}
}
For the Anthropic SDK tool use patterns that underpin how Claude calls MCP tools, the Anthropic SDK guide covers the raw tool use API and agentic loops. For the LangGraph multi-agent patterns that can orchestrate multiple MCP-connected tools, the LangChain/LangGraph guide covers stateful agent graph design. The Claude Skills 360 bundle includes MCP skill sets covering tool definitions, resource readers, prompt templates, and transport configuration. Start with the free tier to try MCP server generation.