Tutorial | 构建 MCP Server¶
难度:⭐⭐⭐⭐ 时间:~2h 前置:deep-dive-mcp-client.md 产物:Claude Code 可用的 MCP server
1. MCP 是什么¶
MCP (Model Context Protocol) = 给 LLM 暴露工具的标准协议 - 类似 USB —— 一次写,到处用 - Claude Code / Cursor / 其他 IDE 都支持 - 4 种 transport:stdio / SSE / Streamable HTTP / WebSocket
2. 3 种实现方式¶
2.1 官方 SDK¶
SDK —— 最简单。
2.2 自己实现¶
自己实现 —— 完全控制。
2.3 现成 server¶
用现成的 —— 1 行命令。
3. TypeScript SDK 实战¶
3.1 项目初始化¶
mkdir my-mcp-server
cd my-mcp-server
npm init -y
npm install @modelcontextprotocol/sdk zod
npm install -D typescript @types/node
3.2 tsconfig.json¶
{
"compilerOptions": {
"target": "ES2022",
"module": "Node16",
"moduleResolution": "Node16",
"strict": true,
"esModuleInterop": true,
"outDir": "dist",
"skipLibCheck": true
}
}
3.3 完整 server (src/index.ts)¶
#!/usr/bin/env node
import { Server } from '@modelcontextprotocol/sdk/server/index.js'
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js'
import { z } from 'zod'
const server = new Server(
{ name: 'my-mcp', version: '1.0.0' },
{ capabilities: { tools: {} } }
)
// 列出工具
server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [
{
name: 'add',
description: 'Add two numbers',
inputSchema: {
type: 'object',
properties: {
a: { type: 'number' },
b: { type: 'number' },
},
required: ['a', 'b'],
},
},
{
name: 'get_weather',
description: 'Get weather for a city',
inputSchema: {
type: 'object',
properties: {
city: { type: 'string' },
},
required: ['city'],
},
},
],
}))
// 处理工具调用
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params
if (name === 'add') {
const { a, b } = z.object({
a: z.number(),
b: z.number(),
}).parse(args)
return {
content: [{ type: 'text', text: String(a + b) }],
}
}
if (name === 'get_weather') {
const { city } = z.object({ city: z.string() }).parse(args)
const weather = await fetchWeather(city)
return {
content: [{ type: 'text', text: JSON.stringify(weather) }],
}
}
throw new Error(`Unknown tool: ${name}`)
})
async function fetchWeather(city: string) {
// 实际 API 调用
return { city, temp: 20, condition: 'sunny' }
}
const transport = new StdioServerTransport()
await server.connect(transport)
3.4 package.json¶
3.5 build¶
3.6 在 Claude Code 用¶
claude mcp add --transport stdio -- node /path/to/dist/index.js
# 或 npx
claude mcp add --transport stdio -- npx my-mcp
4. Python SDK 实战¶
4.1 安装¶
4.2 server.py¶
import asyncio
from mcp.server import Server
from mcp.server.stdio import stdio_server
from mcp.types import Tool, TextContent
app = Server("my-mcp")
@app.list_tools()
async def list_tools() -> list[Tool]:
return [
Tool(
name="add",
description="Add two numbers",
inputSchema={
"type": "object",
"properties": {
"a": {"type": "number"},
"b": {"type": "number"},
},
"required": ["a", "b"],
},
)
]
@app.call_tool()
async def call_tool(name: str, arguments: dict) -> list[TextContent]:
if name == "add":
return [TextContent(type="text", text=str(arguments["a"] + arguments["b"]))]
raise ValueError(f"Unknown tool: {name}")
async def main():
async with stdio_server() as (read, write):
await app.run(read, write, app.create_initialization_options())
asyncio.run(main())
4.3 跑¶
5. 5 个核心概念¶
5.1 Tool¶
Tool —— 给 Claude 用的。
5.2 Resource¶
Resource —— 给 Claude 读的。
5.3 Prompt¶
Prompt —— 预定义模板。
5.4 Capabilities¶
3 种能力。
5.5 Transport¶
- stdio(subprocess)
- SSE(HTTP)
- Streamable HTTP(HTTP)
- WebSocket
4 种。
6. 高级特性¶
6.1 Elicitation(请求用户信息)¶
// MCP 1.0 新增
if (error.code === -32042) {
const params = parseElicitParams(error)
const result = await handleElicitation(params)
return retryToolCall(result)
}
-32042 触发 elicitation。
6.2 OAuth¶
OAuth —— SDK 帮大半。
6.3 Sampling¶
// MCP server 反过来调 LLM
const response = await server.createMessage({
messages: [...],
maxTokens: 100
})
Sampling —— server 可调 LLM。
6.4 Progress 通知¶
await server.notification({
method: 'notifications/progress',
params: { progressToken, progress: 50, total: 100 }
})
进度通知。
6.5 Logging¶
await server.notification({
method: 'notifications/message',
params: { level: 'info', data: '...' }
})
Logging。
7. 实战:GitHub MCP Server¶
import { Octokit } from '@octokit/rest'
const octokit = new Octokit({ auth: process.env.GITHUB_TOKEN })
// 工具
{
name: 'create_issue',
description: 'Create a GitHub issue',
inputSchema: {
type: 'object',
properties: {
repo: { type: 'string' },
title: { type: 'string' },
body: { type: 'string' },
},
required: ['repo', 'title'],
},
handler: async ({ repo, title, body }) => {
const [owner, name] = repo.split('/')
const issue = await octokit.issues.create({
owner, repo: name, title, body
})
return { content: [{ type: 'text', text: issue.data.html_url }] }
}
}
完整 GitHub 集成。
8. 调试¶
8.1 MCP Debug¶
debug mode。
8.2 看 log¶
log 文件(推测)。
8.3 测试工具¶
inspector —— GUI 测试。
9. 发布¶
9.1 npm¶
npm 注册。
9.2 私有¶
私有。
9.3 MCP Marketplace¶
官方 —— 提交 PR。
10. 6 个最佳实践¶
- 描述清晰 —— Claude 靠 description 决定何时调
- inputSchema 严格 —— 用 zod 验证
- 错误友好 —— throw 明确错误
- 超时控制 —— 30s 默认
- 资源用尽释放 —— 文件句柄、连接
- 日志 —— 便于调试
6 条。
11. 完整可工作示例¶
见 GitHub: modelcontextprotocol/servers 100+ server。
12. 下一步¶
- 写第一个 MCP server
- 用 inspector 调试
- 接到 Claude Code
- 发布 npm