跳转至

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

# TypeScript
npm install @modelcontextprotocol/sdk

# Python
pip install mcp

SDK —— 最简单。

2.2 自己实现

// JSON-RPC 2.0 over stdio
// 实现 tools/list, tools/call, resources/list, ...

自己实现 —— 完全控制。

2.3 现成 server

# 已有 100+ 公开 MCP server
claude mcp add --transport stdio -- npx @modelcontextprotocol/server-github

用现成的 —— 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

{
  "bin": { "my-mcp": "./dist/index.js" },
  "scripts": {
    "build": "tsc"
  }
}

3.5 build

npm run build
chmod +x dist/index.js

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 安装

pip install mcp

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 跑

chmod +x server.py
claude mcp add --transport stdio -- python /path/to/server.py

5. 5 个核心概念

5.1 Tool

{
  name: 'add',
  description: 'Add two numbers',  // Claude 看的
  inputSchema: { ... }              // JSON Schema
}

Tool —— 给 Claude 用的。

5.2 Resource

{
  uri: 'file://...',
  name: 'My file',
  mimeType: 'text/plain'
}

Resource —— 给 Claude 读的。

5.3 Prompt

{
  name: 'review',
  description: 'Review code',
  arguments: [{ name: 'code', description: '...' }]
}

Prompt —— 预定义模板。

5.4 Capabilities

{ capabilities: { tools: {}, resources: {}, prompts: {} } }

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

// Claude Code 自动处理 OAuth flow
// 你的 server 只需暴露 OAuth endpoint

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

claude --mcp-debug
# 或
claude --debug mcp

debug mode

8.2 看 log

# 推测
tail -f ~/.claude/logs/mcp.log

log 文件(推测)。

8.3 测试工具

# 用 mcp-cli 测
npx @modelcontextprotocol/inspector /path/to/server.js

inspector —— GUI 测试。


9. 发布

9.1 npm

npm publish

npm 注册

9.2 私有

# 私有 registry
npm publish --registry https://your-private-npm

私有

9.3 MCP Marketplace

# 注册到官方市场
# https://github.com/modelcontextprotocol/servers

官方 —— 提交 PR。


10. 6 个最佳实践

  1. 描述清晰 —— Claude 靠 description 决定何时调
  2. inputSchema 严格 —— 用 zod 验证
  3. 错误友好 —— throw 明确错误
  4. 超时控制 —— 30s 默认
  5. 资源用尽释放 —— 文件句柄、连接
  6. 日志 —— 便于调试

6 条


11. 完整可工作示例

GitHub: modelcontextprotocol/servers 100+ server。


12. 下一步

  • 写第一个 MCP server
  • 用 inspector 调试
  • 接到 Claude Code
  • 发布 npm