学习目标:掌握 MCP(Model Context Protocol)TypeScript SDK 的完整开发流程,从理解 Tools / Resources / Prompts 三大原语开始,逐步构建一个具备真实功能的 MCP Server,最终配置到 Claude Code 并发布到 npm。
适用人群:有 TypeScript/Node.js 基础,希望为 Claude Code 创建自定义工具扩展的开发者。
预计时长:4-6 小时(含实战项目)
MCP(Model Context Protocol)是 Anthropic 推出的开放标准协议,定义了 AI 模型与外部系统之间的通信规范。截至 2026 年 5 月,MCP SDK 月下载量已超过 9700 万,公开注册的 MCP Server 超过 10,000 个。
理解 MCP 架构的关键是区分三个角色和两层协议:
┌──────────────┐ JSON-RPC ┌──────────────┐
│ MCP Client │◄──────────────►│ MCP Server │
│ (Claude Code│ stdio/HTTP │ (你开发的 │
│ / Desktop) │ │ 工具服务) │
└──────────────┘ └──────────────┘
│ │
│ LLM 调用工具 │ 访问外部系统
▼ ▼
┌──────────────┐ ┌──────────────┐
│ AI Model │ │ 数据库/API/ │
│ (Claude等) │ │ 文件系统等 │
└──────────────┘ └──────────────┘
MCP Server 通过三个核心原语(Primitives)向 Client 暴露能力:
| 原语 | 控制方 | 用途 | 典型场景 |
|---|---|---|---|
| Tools | 模型控制 | 执行操作、调用 API | 查询数据库、发送请求、计算 |
| Resources | 应用控制 | 提供数据上下文 | 读取文件、获取配置、加载数据 |
| Prompts | 用户控制 | 提供模板化交互 | 预设提示词、工作流模板 |
控制方的区别至关重要: - Tools 由 LLM 决定何时调用(模型自主) - Resources 由用户或应用显式请求(人为驱动) - Prompts 由用户选择并填充参数后提交(模板化交互)
MCP 支持两种传输方式:
| 传输方式 | 适用场景 | Claude Code 支持 |
|---|---|---|
| stdio | 本地进程通信 | 原生支持 |
| Streamable HTTP | 远程/网络通信 | 支持 |
注意:SSE 传输已在 MCP 规范最新版本中被标记为废弃(deprecated),推荐使用 Streamable HTTP 替代。
flowchart TB
subgraph Local["本地模式 (stdio)"]
CC1["Claude Code"] -->|stdin/stdout| MS1["MCP Server 进程"]
end
subgraph Remote["远程模式 (Streamable HTTP)"]
CC2["Claude Code"] -->|HTTP POST| PROXY["HTTP Server"]
PROXY -->|JSON-RPC| MS2["MCP Server"]
end
subgraph Dev["开发调试"]
MI["MCP Inspector"] -->|stdio/HTTP| MS3["MCP Server"]
end
# 确认 Node.js >= 18
node --version # v18+ 或 v20+ 推荐
# 确认 Claude Code 已安装
claude --version
# 创建项目目录
mkdir mcp-weather-server && cd mcp-weather-server
# 初始化 TypeScript 项目
npm init -y
npm install @modelcontextprotocol/sdk zod
npm install -D typescript @types/node
# 创建 TypeScript 配置
cat > tsconfig.json << 'EOF'
{
"compilerOptions": {
"target": "ES2022",
"module": "Node16",
"moduleResolution": "Node16",
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"declaration": true
},
"include": ["src/**/*"]
}
EOF
# 创建源码目录
mkdir src
{
"name": "mcp-weather-server",
"version": "1.0.0",
"description": "MCP Server for weather data queries",
"type": "module",
"main": "dist/index.js",
"bin": {
"mcp-weather-server": "dist/index.js"
},
"scripts": {
"build": "tsc",
"dev": "tsc --watch",
"start": "node dist/index.js",
"inspect": "npx @modelcontextprotocol/inspector node dist/index.js"
},
"files": ["dist"]
}
关键点说明:
- "type": "module" — 使用 ES Module,MCP SDK 要求 ESM
- "bin" 字段 — 使包可通过 npx 执行,也用于 npm 发布
- "inspect" 脚本 — 使用 MCP Inspector 进行可视化调试
Tool 是 MCP Server 最核心的原语,LLM 会根据用户意图自主决定调用哪个 Tool。每个 Tool 需要声明名称、描述和参数 Schema。
创建 src/index.ts:
#!/usr/bin/env node
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
// 创建 MCP Server 实例
const server = new McpServer({
name: "weather-server",
version: "1.0.0",
});
// ========== Tool 1: 获取当前天气 ==========
server.tool(
"get_current_weather", // 工具名称
"获取指定城市的当前天气信息", // 工具描述(LLM 靠这个决定是否调用)
{
city: z.string().describe("城市名称,如'北京'、'Shanghai'"),
unit: z.enum(["celsius", "fahrenheit"]).optional()
.describe("温度单位,默认摄氏度"),
},
async ({ city, unit }) => {
// 调用天气 API(这里用模拟数据演示)
const weather = await fetchWeather(city, unit ?? "celsius");
return {
content: [
{
type: "text",
text: JSON.stringify(weather, null, 2),
},
],
};
}
);
// ========== Tool 2: 天气预报 ==========
server.tool(
"get_weather_forecast",
"获取指定城市未来几天的天气预报",
{
city: z.string().describe("城市名称"),
days: z.number().min(1).max(7).optional()
.describe("预报天数,1-7天,默认3天"),
},
async ({ city, days }) => {
const forecast = await fetchForecast(city, days ?? 3);
return {
content: [
{
type: "text",
text: formatForecast(forecast),
},
],
};
}
);
// ========== 启动 Server ==========
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
// Server 启动后通过 stdin/stdout 与 Client 通信
// 不需要 console.log — stdout 被用于协议通信
}
main().catch((error) => {
// 错误输出到 stderr,避免干扰 MCP 协议
process.stderr.write(`Fatal error: ${error}\n`);
process.exit(1);
});
// ========== 模拟数据层(实际项目替换为真实 API)==========
async function fetchWeather(city: string, unit: string) {
// 实际项目中调用 OpenWeatherMap / 和风天气 等 API
return {
city,
temperature: unit === "fahrenheit" ? 82 : 28,
unit: unit === "fahrenheit" ? "°F" : "°C",
condition: "晴朗",
humidity: 45,
wind_speed: "12 km/h",
updated_at: new Date().toISOString(),
};
}
async function fetchForecast(city: string, days: number) {
const conditions = ["晴", "多云", "小雨", "阴", "雷阵雨"];
return Array.from({ length: days }, (_, i) => ({
date: new Date(Date.now() + i * 86400000)
.toISOString().split("T")[0],
high: Math.round(25 + Math.random() * 10),
low: Math.round(15 + Math.random() * 8),
condition: conditions[Math.floor(Math.random() * conditions.length)],
}));
}
function formatForecast(forecast: any[]): string {
return forecast
.map((d) => `${d.date}: ${d.condition}, ${d.low}°C ~ ${d.high}°C`)
.join("\n");
}
server.tool() 四参数模式:
server.tool(
string, // 工具名称 — 必须唯一,推荐 snake_case
string, // 描述 — LLM 根据描述决定是否调用,务必清晰准确
ZodSchema, // 参数 Schema — 用 Zod 定义,自动生成 JSON Schema
Handler, // 异步处理函数 — 接收解析后的参数,返回 content 数组
);
Zod Schema 到 JSON Schema 的自动映射:
// Zod 定义 → JSON Schema(自动生成)
z.string() → { type: "string" }
z.string().describe("城市") → { type: "string", description: "城市" }
z.number().min(1).max(7) → { type: "number", minimum: 1, maximum: 7 }
z.enum(["a", "b"]) → { enum: ["a", "b"] }
z.string().optional() → 属性不加入 required 数组
z.array(z.string()) → { type: "array", items: { type: "string" } }
Handler 返回值结构:
// 文本内容
return {
content: [{ type: "text", text: "结果文本" }]
};
// 图片内容(Tool 特有)
return {
content: [{
type: "image",
data: base64EncodedImage,
mimeType: "image/png"
}]
};
// 错误(不抛异常,而是返回 isError 标记)
return {
content: [{ type: "text", text: "城市不存在: xxx" }],
isError: true,
};
# 编译 TypeScript
npm run build
# 使用 MCP Inspector 可视化测试
npm run inspect
Inspector 启动后会在浏览器中打开,你可以: 1. 查看注册的 Tool 列表 2. 手动填入参数并调用 Tool 3. 查看请求/响应的完整 JSON-RPC 消息
# 方式一:使用 claude mcp add 命令(推荐)
claude mcp add weather-server -- node /path/to/mcp-weather-server/dist/index.js
# 方式二:使用 JSON 配置
claude mcp add-json weather-server '{
"type": "stdio",
"command": "node",
"args": ["/path/to/mcp-weather-server/dist/index.js"]
}'
# 验证配置
claude mcp list
# 在 Claude Code 中测试
claude
> 北京今天天气怎么样?
# Claude 会自动识别并调用 get_current_weather 工具
常见问题:
| 问题 | 原因 | 解决方案 |
|---|---|---|
spawn ENOENT |
命令路径错误 | 使用绝对路径 |
| Server 启动后无响应 | console.log 干扰协议 |
改用 process.stderr.write |
| 工具未显示 | 配置文件格式错误 | 检查 JSON 语法 |
| Tool 调用超时 | Handler 卡住 | 确保异步操作有超时控制 |
Resources 向 Client 暴露可读取的数据资源。与 Tool 不同,Resource 由应用/用户主动请求,LLM 不会自主调用。适合提供配置文件、数据字典、模板等上下文信息。
// ========== Resource: 支持的城市列表 ==========
server.resource(
"supported-cities", // 资源名称
"weather://cities/supported", // URI(MCP 使用自定义 URI Scheme)
async (uri) => {
const cities = [
{ name: "北京", code: "BJ", lat: 39.9, lon: 116.4 },
{ name: "上海", code: "SH", lat: 31.2, lon: 121.5 },
{ name: "广州", code: "GZ", lat: 23.1, lon: 113.3 },
{ name: "Shenzhen", code: "SZ", lat: 22.5, lon: 114.1 },
];
return {
contents: [
{
uri: uri.href,
mimeType: "application/json",
text: JSON.stringify(cities, null, 2),
},
],
};
}
);
Resource Template 使用 URI 模板支持参数化访问:
// ========== Resource Template: 城市天气历史 ==========
server.resource(
"city-weather-history",
new ResourceTemplate("weather://cities/{city}/history", { list: undefined }),
async (uri, { city }) => {
// {city} 从 URI 中自动提取
const history = await getCityHistory(city);
return {
contents: [
{
uri: uri.href,
mimeType: "application/json",
text: JSON.stringify(history, null, 2),
},
],
};
}
);
// 配合 list 回调,让 Client 能发现所有可用资源
server.resource(
"city-detail",
new ResourceTemplate("weather://cities/{city}/detail", {
list: async () => {
const cities = ["beijing", "shanghai", "guangzhou", "shenzhen"];
return {
resources: cities.map((city) => ({
name: `${city} 详情`,
uri: `weather://cities/${city}/detail`,
mimeType: "application/json",
})),
};
},
}),
async (uri, { city }) => {
const detail = await getCityDetail(city);
return {
contents: [
{
uri: uri.href,
mimeType: "application/json",
text: JSON.stringify(detail, null, 2),
},
],
};
}
);
当资源数据变化时,Server 可以主动通知 Client:
// 声明支持订阅
const server = new McpServer({
name: "weather-server",
version: "1.0.0",
}, {
capabilities: {
resources: {
subscribe: true, // 启用订阅能力
listChanged: true, // 启用资源列表变更通知
},
},
});
// 在数据变更时发送通知
async function onWeatherUpdate(city: string) {
// 通知所有订阅了该资源的 Client
await server.notification({
method: "notifications/resources/updated",
params: {
uri: `weather://cities/${city}/current`,
},
});
}
// 资源列表变更时通知
async function onNewCityAdded() {
await server.notification({
method: "notifications/resources/list_changed",
});
}
注意:Claude Code 目前对 Resource 订阅的支持有限,建议优先使用 Tool 来提供动态数据。
Prompts 是用户控制的模板化交互方式。用户从可用 Prompt 列表中选择一个,填写参数后提交给 LLM。
// ========== Prompt: 天气分析报告 ==========
server.prompt(
"weather-analysis", // Prompt 名称
"生成城市天气分析报告,包含趋势预测和建议", // 描述
{
city: z.string().describe("目标城市"),
period: z.enum(["daily", "weekly", "monthly"])
.describe("分析周期"),
focus: z.string().optional()
.describe("关注重点,如'出行'、'农业'、'能源'"),
},
async ({ city, period, focus }) => {
const weatherData = await fetchWeather(city, "celsius");
const forecast = await fetchForecast(city, 7);
return {
messages: [
{
role: "user",
content: {
type: "text",
text: [
`请为 ${city} 生成一份${period === "daily" ? "每日" : period === "weekly" ? "每周" : "每月"}天气分析报告。`,
``,
`## 当前天气`,
`- 温度: ${weatherData.temperature}°C`,
`- 天气状况: ${weatherData.condition}`,
`- 湿度: ${weatherData.humidity}%`,
`- 风速: ${weatherData.wind_speed}`,
``,
`## 未来预报`,
forecast.map((f: any) => `- ${f.date}: ${f.condition}, ${f.low}°C ~ ${f.high}°C`).join("\n"),
focus ? `\n## 特别关注\n请重点从"${focus}"角度分析影响。` : "",
``,
`请包含以下内容:`,
`1. 天气趋势总结`,
`2. 对日常生活的影响和建议`,
`3. ${focus ? `${focus}相关的具体建议` : "出行建议"}`,
].join("\n"),
},
},
],
};
}
);
// ========== Prompt: 对比分析 ==========
server.prompt(
"city-comparison",
"对比两个城市的天气状况",
{
city1: z.string().describe("第一个城市"),
city2: z.string().describe("第二个城市"),
aspect: z.enum(["temperature", "comfort", "travel"])
.describe("对比维度"),
},
async ({ city1, city2, aspect }) => {
const [w1, w2] = await Promise.all([
fetchWeather(city1, "celsius"),
fetchWeather(city2, "celsius"),
]);
const aspectLabel = {
temperature: "温度",
comfort: "舒适度",
travel: "出行适宜度",
}[aspect];
return {
messages: [
{
role: "user",
content: {
type: "text",
text: `对比 ${city1} 和 ${city2} 的${aspectLabel}:\n\n`
+ `${city1}: ${w1.temperature}°C, ${w1.condition}\n`
+ `${city2}: ${w2.temperature}°C, ${w2.condition}\n\n`
+ `请从${aspectLabel}角度进行详细对比分析。`,
},
},
],
};
}
);
Prompt vs Tool 的选择策略:
由谁决定调用?
│
┌────────────┼────────────┐
▼ ▼ ▼
LLM自主 用户选择 应用请求
Tool Prompt Resource
│ │ │
▼ ▼ ▼
执行操作/计算 模板化交互 读取数据
查询API 生成报告 获取配置
修改状态 预设工作流 加载文件
Tool 可以直接返回图片内容:
server.tool(
"weather_chart",
"生成城市天气图表",
{
city: z.string().describe("城市名称"),
chart_type: z.enum(["temperature", "humidity", "wind"])
.describe("图表类型"),
},
async ({ city, chart_type }) => {
// 生成图表(使用 chart.js + canvas 等库)
const imageBuffer = await generateChart(city, chart_type);
return {
content: [
{
type: "image",
data: imageBuffer.toString("base64"),
mimeType: "image/png",
},
],
};
}
);
长时间操作可以通过进度通知让 Client 感知进度:
server.tool(
"batch_weather_report",
"批量生成多个城市的天气报告",
{
cities: z.array(z.string()).describe("城市列表"),
},
async ({ cities }, { signal, meta }) => {
const results: string[] = [];
for (let i = 0; i < cities.length; i++) {
// 检查是否被取消
signal?.throwIfAborted();
const report = await generateReport(cities[i]);
results.push(report);
// 发送进度通知
await server.notification({
method: "notifications/progress",
params: {
progress: i + 1,
total: cities.length,
progressToken: meta?.progressToken,
},
});
}
return {
content: [{ type: "text", text: results.join("\n---\n") }],
};
}
);
将以上所有原语组合成一个完整项目。创建 src/index.ts:
#!/usr/bin/env node
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";
const server = new McpServer(
{ name: "weather-server", version: "1.0.0" },
{
capabilities: {
resources: { subscribe: true, listChanged: true },
},
}
);
// ━━━━━━━ Tools ━━━━━━━
server.tool("get_current_weather", "获取指定城市的当前天气信息", {
city: z.string().describe("城市名称"),
unit: z.enum(["celsius", "fahrenheit"]).optional().describe("温度单位"),
}, async ({ city, unit }) => {
const u = unit ?? "celsius";
const data = await fetchWeather(city, u);
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
});
server.tool("get_weather_forecast", "获取未来天气预报", {
city: z.string().describe("城市名称"),
days: z.number().min(1).max(7).optional().describe("预报天数"),
}, async ({ city, days }) => {
const data = await fetchForecast(city, days ?? 3);
const text = data
.map((d: any) => `${d.date}: ${d.condition}, ${d.low}°C ~ ${d.high}°C`)
.join("\n");
return { content: [{ type: "text", text }] };
});
server.tool("compare_weather", "对比两个城市的天气", {
city1: z.string().describe("城市1"),
city2: z.string().describe("城市2"),
}, async ({ city1, city2 }) => {
const [w1, w2] = await Promise.all([
fetchWeather(city1, "celsius"),
fetchWeather(city2, "celsius"),
]);
const comparison = [
`| 指标 | ${city1} | ${city2} |`,
`|------|---------|---------|`,
`| 温度 | ${w1.temperature}°C | ${w2.temperature}°C |`,
`| 天气 | ${w1.condition} | ${w2.condition} |`,
`| 湿度 | ${w1.humidity}% | ${w2.humidity}% |`,
`| 风速 | ${w1.wind_speed} | ${w2.wind_speed} |`,
].join("\n");
return { content: [{ type: "text", text: comparison }] };
});
// ━━━━━━━ Resources ━━━━━━━
server.resource("supported-cities", "weather://config/cities",
async (uri) => ({
contents: [{
uri: uri.href,
mimeType: "application/json",
text: JSON.stringify([
{ name: "北京", code: "BJ" },
{ name: "上海", code: "SH" },
{ name: "广州", code: "GZ" },
], null, 2),
}],
})
);
server.resource("city-weather",
new ResourceTemplate("weather://cities/{city}/current", {
list: async () => ({
resources: ["beijing", "shanghai", "guangzhou"].map((c) => ({
name: `${c} 实时天气`,
uri: `weather://cities/${c}/current`,
})),
}),
}),
async (uri, { city }) => {
const data = await fetchWeather(city, "celsius");
return {
contents: [{
uri: uri.href,
mimeType: "application/json",
text: JSON.stringify(data, null, 2),
}],
};
}
);
// ━━━━━━━ Prompts ━━━━━━━
server.prompt("weather-analysis", "生成城市天气分析报告", {
city: z.string().describe("目标城市"),
period: z.enum(["daily", "weekly", "monthly"]).describe("分析周期"),
}, async ({ city, period }) => {
const weather = await fetchWeather(city, "celsius");
return {
messages: [{
role: "user",
content: {
type: "text",
text: `请为${city}生成${period === "daily" ? "每日" : period === "weekly" ? "每周" : "每月"}天气分析。\n`
+ `当前: ${weather.temperature}°C, ${weather.condition}, 湿度${weather.humidity}%`,
},
}],
};
});
// ━━━━━━━ 启动 ━━━━━━━
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
}
main().catch((err) => {
process.stderr.write(`Fatal: ${err}\n`);
process.exit(1);
});
// ━━━━━━━ 数据层 ━━━━━━━
async function fetchWeather(city: string, unit: string) {
return {
city,
temperature: unit === "fahrenheit" ? 82 : 28,
unit: unit === "fahrenheit" ? "°F" : "°C",
condition: "晴朗",
humidity: 45,
wind_speed: "12 km/h",
updated_at: new Date().toISOString(),
};
}
async function fetchForecast(city: string, days: number) {
const conditions = ["晴", "多云", "小雨", "阴"];
return Array.from({ length: days }, (_, i) => ({
date: new Date(Date.now() + i * 86400000).toISOString().split("T")[0],
high: Math.round(25 + Math.random() * 10),
low: Math.round(15 + Math.random() * 8),
condition: conditions[Math.floor(Math.random() * conditions.length)],
}));
}
flowchart LR
subgraph 开发阶段
A["npm init + 安装依赖"] --> B["编写 MCP Server 代码"]
B --> C["tsc 编译"]
C --> D["MCP Inspector 调试"]
end
subgraph 集成阶段
D --> E["claude mcp add"]
E --> F["Claude Code 中测试"]
F --> G{"功能正确?"}
G -->|否| B
G -->|是| H["项目级 .mcp.json"]
end
subgraph 发布阶段
H --> I["npm publish"]
I --> J["MCP Registry 注册"]
J --> K["社区使用"]
end
.mcp.json在项目根目录创建 .mcp.json,团队成员自动共享 MCP 配置:
{
"mcpServers": {
"weather-server": {
"type": "stdio",
"command": "node",
"args": ["./mcp-servers/weather/dist/index.js"]
}
}
}
~/.claude.json适用于个人全局使用的 MCP Server:
# 全局配置(所有项目可用)
claude mcp add --scope user weather-server -- node ~/mcp-servers/weather/dist/index.js
项目级 .mcp.json > 用户级 ~/.claude.json > 系统级配置
(高优先) (低优先)
MCP Inspector 是官方可视化调试工具,支持测试所有三种原语。
# 方式一:通过 npm scripts(已在 package.json 中配置)
npm run inspect
# 方式二:直接使用 npx
npx @modelcontextprotocol/inspector node dist/index.js
# 方式三:带环境变量
npx @modelcontextprotocol/inspector \
-e API_KEY=your_key \
node dist/index.js
Inspector 提供一键导出功能,生成 Claude Code / Claude Desktop 兼容的配置:
{
"mcpServers": {
"weather-server": {
"command": "node",
"args": ["/absolute/path/to/dist/index.js"]
}
}
}
# 确保入口文件有 shebang
head -1 dist/index.js
# 应输出: #!/usr/bin/env node
# 确保构建产物是最新的
npm run build
# 检查 package.json 字段
npm pack --dry-run
# 登录 npm(首次)
npm login
# 发布
npm publish --access public
# 发布后,用户可以这样使用:
# claude mcp add weather-server -- npx mcp-weather-server
MCP Registry 是官方的 Server 发现平台,只存储元数据(不存代码):
# 安装 MCP CLI
npm install -g @modelcontextprotocol/cli
# 发布到 Registry
mcp publish
在项目根目录创建 mcp-metadata.json:
{
"name": "weather-server",
"description": "天气查询 MCP Server,支持全球城市天气、预报和对比",
"repository": "https://github.com/yourname/mcp-weather-server",
"npmPackage": "mcp-weather-server",
"categories": ["weather", "utilities"],
"tools": [
{
"name": "get_current_weather",
"description": "获取当前天气"
},
{
"name": "get_weather_forecast",
"description": "获取天气预报"
},
{
"name": "compare_weather",
"description": "对比城市天气"
}
]
}
// 使用 Zod 进行严格验证
server.tool("search_weather", "搜索天气记录", {
query: z.string().min(1).max(200)
.regex(/^[\w\s\u4e00-\u9fff,.-]+$/) // 只允许安全字符
.describe("搜索关键词"),
}, async ({ query }) => {
// ...
});
server.tool("risky_operation", "需要安全处理的操作", {
param: z.string(),
}, async ({ param }) => {
try {
const result = await callExternalAPI(param);
return { content: [{ type: "text", text: JSON.stringify(result) }] };
} catch (error) {
// 返回错误信息,而非抛出异常
return {
content: [{
type: "text",
text: `操作失败: ${error instanceof Error ? error.message : "未知错误"}`,
}],
isError: true,
};
}
});
// 差:暴露了 API 端点
server.tool("get_user", "调用 /api/v2/users/{id} 获取用户", ...);
// 好:描述功能而非实现
server.tool("get_user", "获取指定用户的基本信息", ...);
| 症状 | 检查项 | 解决方法 |
|---|---|---|
| Server 启动失败 | Node.js 版本 >= 18 | nvm use 20 |
Cannot find module |
ESM 配置 | 确认 "type": "module" 和 .js 后缀 |
| Tool 参数解析失败 | Zod Schema | 在 Inspector 中验证 Schema |
| stdio 被污染 | console.log | 替换为 process.stderr.write |
| Claude Code 看不到工具 | 配置路径 | 使用绝对路径,检查 claude mcp list |
| 工具调用返回空 | Handler 逻辑 | 在 Inspector 中单独测试 |
| 类型错误 | TypeScript 配置 | 确认 moduleResolution: "Node16" |
flowchart TD
START["Server 无法工作"] --> Q1{"能启动吗?"}
Q1 -->|否| Q2{"Node版本?"}
Q2 -->|< 18| A1["升级 Node.js"]
Q2 -->|>= 18| Q3{"模块错误?"}
Q3 -->|是| A2["检查 type:module 和 .js 后缀"]
Q3 -->|否| A3["检查 import 路径"]
Q1 -->|能启动| Q4{"Inspector 能调通?"}
Q4 -->|否| Q5{"Schema 正确?"}
Q5 -->|否| A4["用 Zod 验证参数"]
Q5 -->|是| A5["检查 Handler 逻辑"]
Q4 -->|能| Q6{"Claude Code 可见?"}
Q6 -->|否| A6["检查 claude mcp list"]
Q6 -->|是| A7["✅ 正常工作"]
| 维度 | 评估 |
|---|---|
| 适合训练营 | AI 工具开发营、MCP 生态开发营、Claude Code 进阶营 |
| 教学时长 | 4-6 小时(含动手实验) |
| 难度等级 | 中级(需要 TypeScript 和 Node.js 基础) |
| 实验环境 | Node.js 20+、Claude Code CLI、MCP Inspector |
| 前置课程 | Claude Code 入门、TypeScript 基础 |
| 后续课程 | MCP 质量工程、MCP Gateway 网关、多 Server 编排 |
[!tip] 51学通信知识星球 星主:爱卫生(微信:gprshome201101)——公众号「51学通信」创办者,19 年通信行业运维与讲师经验,著有《GPRS网络信令实例详解》《EPC原理与实践》《5G核心网原理与实践》三本纸质书(各大电商平台有售)。 星球定位:深耕 5G 核心网、IMS、NFV/AI、云原生等 ICT 领域,主打运维岗位与信令分析。 当前规模:5200 篇主题 / 990 个回答,198 元/年。 加入福利:加入星球后请加爱卫生微信,拉您进知识星球年度陪伴与答疑群——不限量回复您的各种问题,另有群内分享。 AI 加持:51学通信知识星球已作为 skill 接入 WorkBuddy,使用 deepseek-4-flash 在海量星球帖子中精准找贴、找视频、找 FAQ 和案例,做总结、找文档和 PPT 等场景全覆盖。
