跳转至

Mini MCP Server Demo

从 3348 行的 src/services/mcp/client.ts 提炼到 ~300 行的可运行 MCP server。 展示 MCP 协议 + stdio transport + 工具系统 的最小实现。

这是什么?

Claude Code 用 MCP (Model Context Protocol) 连接外部工具/资源。真实代码 3348 行,本 demo 用 ~300 行 实现一个能跑的 MCP server + client,让你能:

  • 手写一个工具,跑通 MCP 协议
  • 理解 JSON-RPC 2.0 在 MCP 里怎么用
  • 看 stdio transport 的"newline-delimited JSON"实际是什么
  • 给 CC 真接一个自己写的 MCP server

文件结构

mini-mcp-server/
├── src/
│   ├── server.ts            ← MCP server 核心(~180 行)
│   ├── client.ts            ← 测 server 用的 client(~120 行)
│   ├── transport/
│   │   └── stdio.ts         ← NDJSON transport(~50 行)
│   └── tools/
│       ├── types.ts         ← 工具类型定义
│       └── calculator.ts    ← 示例工具(+ - × ÷)
├── package.json
├── tsconfig.json
└── README.md

怎么跑?

# 1. 装依赖
npm install

# 2. 编译
npm run build

# 3. 启动 client(自动 spawn server,跑 6 个测试)
npm run client

# 或单独跑 server(从 stdin 读 JSON-RPC)
npm start
# 然后手动输入:
# {"jsonrpc":"2.0","id":1,"method":"tools/list"}
# {"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"calculator","arguments":{"operation":"add","a":1,"b":2}}}

真实代码对照表

Demo 文件 真实文件 行数对比 简化了啥
src/server.ts src/services/mcp/client.ts 180 vs 3348 去掉 3 种 transport(SSE/HTTP/WS)、auth、batching、cache、telemetry
src/transport/stdio.ts src/services/mcp/StdioServerTransport.ts 50 vs ~300 用 NDJSON 代替 Content-Length header
src/client.ts 真实 client 代码(被 CC 内部使用) 120 vs ~1000 不做 reconnect、auth、capability negotiation 完整流程
src/tools/calculator.ts 任何 MCP server 工具 50 vs ~200 简化 input validation(用 if 代替 zod/ajv)

协议流程图

Client                              Server
  │                                    │
  │── initialize ────────────────────►│
  │◄── {serverInfo, capabilities} ────│
  │                                    │
  │── notifications/initialized ─────►│
  │                                    │
  │── tools/list ────────────────────►│
  │◄── {tools: [calculator, ...]} ────│
  │                                    │
  │── tools/call(name=calculator, ───►│
  │   args={operation:"add",a:1,b:2}) │
  │◄── {content:[{text:"1 + 2 = 3"}]} │
  │                                    │
  │── shutdown (or stdin close) ─────►│
  │                                    │

核心设计 4 件套

1️⃣ JSON-RPC 2.0 消息结构

// Request(有 id,期望响应)
{ "jsonrpc": "2.0", "id": 1, "method": "tools/list" }

// Notification(无 id,不期望响应)
{ "jsonrpc": "2.0", "method": "notifications/initialized" }

// Response
{ "jsonrpc": "2.0", "id": 1, "result": {...} }
// 或
{ "jsonrpc": "2.0", "id": 1, "error": { "code": -32601, "message": "Method not found" } }

真实 MCP 用 Content-Length header(LSP 风格):

Content-Length: 87\r\n
\r\n
{"jsonrpc":"2.0","id":1,"method":"tools/list"}

本 demo 简化为 NDJSON(每行一个 JSON),更易调试。

2️⃣ stdio transport = readline + stdout.write

// 读:process.stdin 按行解析 JSON
const rl = readline.createInterface({ input: process.stdin });
rl.on("line", (line) => {
  const msg = JSON.parse(line);
  handle(msg);
});

// 写:每条消息一个 JSON + \n
process.stdout.write(JSON.stringify(msg) + "\n");

关键约束: - server 写日志必须用 stderr(CC 也这么做),否则会污染 stdout 协议 - 不要 print debug,否则 client 解析失败

3️⃣ 工具系统 = Map + 验证

private tools = new Map<string, Tool>();

registerTool(tool: Tool) {
  this.tools.set(tool.definition.name, tool);
}

async handleToolsCall(params) {
  const tool = this.tools.get(params.name);
  if (!tool) throw new ToolNotFoundError();
  return await tool.execute(params.arguments);
}

真实代码:CC 用 zod/ajv 做严格 schema 验证,本 demo 用 TypeScript 类型 + if 简化。

4️⃣ 错误处理 = 标准 JSON-RPC 错误码

Code 含义 何时用
-32700 Parse error JSON 解析失败
-32600 Invalid request 消息格式不对
-32601 Method not found 未实现的 method
-32602 Invalid params 参数缺失或类型错
-32603 Internal error server 端异常
-32000 Tool not found MCP 自定义
-32001 Tool exec failed MCP 自定义

测试场景(npm run client 输出)

✅ initialize response: {...serverInfo, capabilities...}
✅ sent notifications/initialized
📋 tools/list: [calculator]
🔧 tools/call tests:
  - add(1, 2) → 1 + 2 = 3
  - subtract(10, 3) → 10 - 3 = 7
  - multiply(7, 6) → 7 × 6 = 42
  - divide(100, 4) → 100 ÷ 4 = 25
⚠️  error handling:
  - divide by zero → isError=true, "Division by zero"
  - nonexistent tool → code=-32000, "Tool not found"

进阶练习

  1. 加 SSE transport:除 stdio 外加一个 SSE transport(见 spec §3)
  2. 加 zod 验证:用 zod 验证 inputSchema,严格报错
  3. 加 resources:实现 resources/list + resources/read,返回文件内容
  4. 加 prompts:实现 prompts/list + prompts/get
  5. 加 cancellation:实现 $/cancelRequest(JSON-RPC 取消机制)
  6. 接 Claude Code:在 ~/.claude/mcp.json 加这个 server 路径,在 CC 里能调 calculator

相关阅读