本教程面向想亲手构建 MCP(Model Context Protocol)工具并接入 Claude Code 的开发者。我们将使用官方 TypeScript SDK(@modelcontextprotocol/sdk)从零搭建一个"开发者瑞士军刀"MCP 服务器,借助 MCP Inspector 进行可视化调试,最后通过 claude mcp add 与项目级 .mcp.json 把它接入 Claude Code,跑通"开发→调试→接入"的完整闭环。全文配套一个离线可运行的多工具项目,无需任何 API Key。
MCP 生态里已有 Go、Python、Rust、Java 等多套官方/社区 SDK,但 TypeScript SDK 始终是受众最广、迭代最快、示例最全的一套,原因有三:
crypto、fs、child_process)开箱即用。McpServer API 极简:注册一个工具只需 server.tool(name, desc, schema, handler) 四个参数,配合 Zod 声明入参,自动生成 JSON Schema 给客户端。@modelcontextprotocol/inspector 是 Web UI,能实时查看工具列表、JSON-RPC 报文与调用结果,把"黑盒子进程"变成可视化面板。与已有的 Go SDK、FastMCP(Python)教程不同,本篇聚焦 TS SDK 的开发循环 + Inspector 调试 + Claude Code 接入,这条链路是前序教程尚未覆盖的。
| 项目 | 要求 | 说明 |
|---|---|---|
| Node.js | ≥ 20 LTS | TS SDK 与 Inspector 都依赖较新 Node;建议 20.x 或 22.x |
| 包管理器 | npm / pnpm / bun 任一 | 示例统一用 npm |
| TypeScript | 5.4+ | 通过 typescript devDependency 安装 |
| Claude Code | 已安装并登录 | 用于最后的接入环节 |
| 操作系统 | macOS / Linux / Windows | 跨平台;示例命令以 Linux/macOS 为准 |
环境自检命令:
node -v # 应输出 v20.x 或更高
npm -v
claude --version
如尚未安装 Claude Code,可执行 npm install -g @anthropic-ai/claude-code(或按官方推荐的原生安装方式)。
新建项目目录并初始化:
mkdir mcp-devtools && cd mcp-devtools
npm init -y
安装核心依赖:
npm install @modelcontextprotocol/sdk zod
npm install -D typescript @types/node tsx
tsx让我们跳过编译直接运行.ts,开发期迭代极快;正式接入时再用tsc编译成 JS。
初始化 TS 配置:
npx tsc --init
编辑 tsconfig.json,关键字段如下(ESM 模块,NodeNext 解析):
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"outDir": "./build",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true
},
"include": ["src/**/*"]
}
在 package.json 中补上 "type": "module" 与脚本:
{
"type": "module",
"scripts": {
"dev": "tsx src/index.ts",
"build": "tsc",
"start": "node build/index.js",
"inspect": "npx @modelcontextprotocol/inspector node build/index.js"
}
}
预期结果:目录中出现 tsconfig.json,node_modules 内可见 @modelcontextprotocol/sdk。
常见问题:
ERR_UNKNOWN_FILE_EXTENSION .ts:忘了在 package.json 写 "type": "module",或 moduleResolution 没设成 NodeNext。.js 后缀导出,例如 @modelcontextprotocol/sdk/server/mcp.js——这是 ESM 规范要求的,不要改成 .ts。新建 src/index.ts。先写一个最小的 stdio 服务器,注册一个 uuid-generate 工具:
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
import { randomUUID } from "node:crypto";
// 1. 创建服务器实例,声明名称与版本(客户端会在握手时读到)
const server = new McpServer({
name: "mcp-devtools",
version: "0.1.0",
});
// 2. 注册工具:名称 / 描述 / Zod 入参 schema / 处理函数
server.tool(
"uuid-generate",
"生成一个或多个 UUID v4,默认返回 1 个",
{
count: z.number().int().min(1).max(100).default(1).describe("要生成的数量"),
},
async ({ count }) => {
const uuids = Array.from({ length: count }, () => randomUUID());
return {
content: [
{ type: "text", text: uuids.join("\n") },
],
};
}
);
// 3. 绑定 stdio 传输层并启动
const transport = new StdioServerTransport();
await server.connect(transport);
启动开发版:
npm run dev
预期结果:进程不会退出,静默等待 stdio 上的 JSON-RPC 报文。这是正常的——stdio MCP 服务器就是一个"哑终端",等待客户端发指令。
关键点解析:
server.tool(...) 的第三个参数是 Zod schema 对象,SDK 会自动把它转成 JSON Schema 暴露给客户端(Claude Code 由此知道该工具接受哪些参数)。content 数组,每个元素带 type。2025-06-18 规范下文本用 "text",图片用 "image",并支持 "resource"(按 URI 暴露只读数据,本篇略)。console.log 打印调试信息!stdio 通道上的任何非 JSON-RPC 输出都会破坏协议。调试日志请走 console.error(stderr 不污染通道)。常见问题:
console.log 污染了 stdout。把日志全部改到 console.error。.default() 让参数变成可选:客户端可不传,SDK 会填默认值。把"瑞士军刀"补成三件套:uuid-generate、hash-text、encode-decode。在 server.tool(...) 注册块之后继续追加(保持同一个 server 实例):
import { createHash } from "node:crypto";
// 工具 2:哈希
server.tool(
"hash-text",
"计算文本的哈希摘要,支持 sha256 / sha512 / md5",
{
text: z.string().min(1).describe("待哈希的文本"),
algorithm: z
.enum(["sha256", "sha512", "md5"])
.default("sha256")
.describe("哈希算法"),
},
async ({ text, algorithm }) => {
const digest = createHash(algorithm).update(text).digest("hex");
return {
content: [{ type: "text", text: `${algorithm}: ${digest}` }],
};
}
);
// 工具 3:编解码
server.tool(
"encode-decode",
"Base64 / URL 的编码与解码",
{
mode: z
.enum(["base64-encode", "base64-decode", "url-encode", "url-decode"])
.describe("操作模式"),
input: z.string().describe("输入字符串"),
},
async ({ mode, input }) => {
let output: string;
try {
switch (mode) {
case "base64-encode":
output = Buffer.from(input, "utf8").toString("base64");
break;
case "base64-decode":
output = Buffer.from(input, "base64").toString("utf8");
break;
case "url-encode":
output = encodeURIComponent(input);
break;
case "url-decode":
output = decodeURIComponent(input);
break;
}
return { content: [{ type: "text", text: output }] };
} catch (err) {
// 出错时返回 isError,客户端会把它呈现给模型而非崩溃
return {
isError: true,
content: [
{ type: "text", text: `解码失败:输入不是合法的 ${mode} 内容` },
],
};
}
}
);
预期结果:服务器现在暴露 3 个工具。注意 hash-text 用了 z.enum(...)——SDK 会把它编译成 JSON Schema 的 enum,模型只能从限定值里选,避免幻觉出 sha1024 这种不存在的东西。
设计要点:
isError: true + 文本说明。这样模型能看到错误原因并自行纠正(例如换个模式重试)。这是 TS SDK 开发循环里最省时的一环。先编译一次:
npm run build # 产出 build/index.js
启动 Inspector(它会在本地起一个 Web 面板,并自动拉起你的服务器作为子进程):
npm run inspect
# 等价于:npx @modelcontextprotocol/inspector node build/index.js
终端会打印一行类似 MCP Inspector is up and running at http://localhost:6274/ 的地址,浏览器打开它。
在面板里完成以下验证:
Connected。若失败,检查 Server Command 是否是 node、Args 是否是 build/index.js。encode-decode、hash-text、uuid-generate 三个工具,每个工具下方有 SDK 根据 Zod 自动生成的参数表单。hash-text,填 text = hello、algorithm = sha256,点 Run Tool,右侧应返回 sha256: 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824。tools/list 与 tools/call 请求/响应,是排查 schema 问题的金标准。为什么这一步值钱:没有 Inspector 时,你只能靠 Claude Code 反馈"工具没出现",但分不清是 schema 错、stdout 污染、还是握手失败。Inspector 把整条链路摊开在眼前。
常见问题:
tools/list 返回了空——多半是 server.tool() 的 Zod 对象写得不对,或返回值缺 content。Disconnected 后立刻退出:服务器启动即崩溃。直接 node build/index.js 单独跑一次,看 stderr 报错。调试通过后,把服务器正式挂进 Claude Code。Claude Code 支持 三种作用域:
| 作用域 | 标志 | 配置位置 | 适用场景 |
|---|---|---|---|
local(默认) |
-s local |
~/.claude.json(项目私有) |
只在当前项目、当前机器用 |
project |
-s project |
项目根 .mcp.json(可提交 Git) |
团队共享,新人 clone 即用 |
user |
-s user |
~/.claude.json(全局) |
所有项目都可用 |
# 先 build,保证 node 能跑的是编译产物
npm run build
# 注册(注意 -- 之后才是真正的启动命令)
claude mcp add devtools -s local -- node /绝对路径/mcp-devtools/build/index.js
验证:
claude mcp list # 应列出 devtools
claude mcp get devtools # 查看详细配置与连接状态
进入任意项目目录,启动 claude,输入:
帮我生成 5 个 UUID,再把 "hello world" 做 base64 编码
模型会自主调用 uuid-generate(count:5) 与 encode-decode(mode:base64-encode,...),并在回复里贴出结果。
.mcp.json(团队共享,推荐)在项目根放一份 .mcp.json,团队成员 clone 后 Claude Code 会自动提示是否信任并加载:
{
"mcpServers": {
"devtools": {
"command": "node",
"args": ["/绝对路径/mcp-devtools/build/index.js"]
}
}
}
也可写成更可移植的
npx形式:"command": "npx","args": ["tsx", "/path/src/index.ts"],免去编译步骤,但每次启动稍慢。
预期结果:claude 启动时顶部状态栏出现 devtools 标记;/mcp 命令可查看连接详情与工具数。
常见问题:
claude mcp list 显示 failed to connect:90% 是路径写成了相对路径,或 node build/index.js 的工作目录不对。改用绝对路径最稳。.mcp.json 没被加载:Claude Code 出于安全要求手动确认信任。首次启动会弹窗,选 Trust;之后可在 /mcp 里管理信任状态。src/*.ts,但 Claude Code 跑的是 build/*.js——记得重新 npm run build。把上述步骤串成一条可复现的流水线。假设你刚加了一个 hash-text 的 blake2b 选项想验证:
# 1) 改代码(src/index.ts 里给 enum 加 "blake2b",并用 createHash 支持)
# 2) 编译
npm run build
# 3) Inspector 里连上,调一次 hash-text(algorithm=blake2b) 看输出
npm run inspect
# 4) 满意后退出 Inspector,Claude Code 已经指向 build/index.js,无需重注册
claude
> 用 blake2b 算一下 "claude code" 的哈希
注意第 4 步:MCP 服务器进程在每次会话启动时由 Claude Code 拉起,因此只要 build/index.js 更新了,新会话自然用上新代码,无需重启 Claude Code 本身、也无需重新 claude mcp add。
flowchart LR
U([用户提问]) --> CC[Claude Code 宿主]
CC -->|tools/call JSON-RPC| SP[stdio 子进程<br/>build/index.js]
SP --> T1[uuid-generate]
SP --> T2[hash-text]
SP --> T3[encode-decode]
T2 -->|crypto.createHash| N((Node 内置模块))
T1 --> N
T3 -->|Buffer/encodeURIComponent| N
SP -->|tools/result| CC
CC --> U
subgraph 开发调试
IN[MCP Inspector Web UI] -.拉起并观察.-> SP
IN -.JSON-RPC 报文.-> SP
end
理解握手有助于排查"工具不出现"类问题:
sequenceDiagram
participant CC as Claude Code(客户端)
participant S as 你的 MCP 服务器(stdio)
CC->>S: initialize {name, version, capabilities}
S-->>CC: InitializeResult {serverInfo, capabilities}
CC->>S: initialized(通知)
CC->>S: tools/list
S-->>CC: [uuid-generate, hash-text, encode-decode]
CC->>S: tools/call {name: "hash-text", arguments:{text,algorithm}}
S-->>CC: content:[{type:text, text:"sha256:..."}]
握手失败、或 tools/list 返回异常,都会导致工具在 Claude Code 中"隐身"——对照时序图,定位是哪一跳出问题。
stdio 适合本地工具;若要把服务器部署到远端(云函数、容器),需用 Streamable HTTP 传输(2025-06-18 规范中取代了旧的纯 SSE 传输)。最小改法:
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
import { createServer } from "node:http";
const httpTransport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined });
// 复用同一个 server 实例
await server.connect(httpTransport);
const httpServer = createServer(async (req, res) => {
// 单一端点 /mcp 同时承载 POST(请求)与 GET(可选的服务器推送)
if (req.url === "/mcp") {
await httpTransport.handleRequest(req, res);
} else {
res.writeHead(404).end();
}
});
httpServer.listen(3000, () => console.error("MCP HTTP on :3000/mcp"));
接入 Claude Code 时用 --transport http:
claude mcp add --transport http devtools http://localhost:3000/mcp
远程 HTTP 服务器若涉及公网暴露,应叠加 OAuth 2.1 鉴权(属另一专题,本篇不展开)。
完成本教程后,对照下表自检:
node -v ≥ 20,npm run build 无报错src/index.ts 中所有日志走 console.error,stdout 干净claude mcp list 显示 devtools ✔ connectedclaude 会话里能成功触发多工具组合调用.mcp.json 已提交,团队成员可一键加载| 症状 | 可能原因 | 处置 |
|---|---|---|
| 工具不出现 | stdout 被污染 / schema 非法 | 改 console.error;查 Inspector 报文 |
failed to connect |
路径相对 / 工作目录错 | 用绝对路径;单独 node build/index.js 试跑 |
| 模型乱传参数 | enum 写成了自由 string | 用 z.enum 收窄取值 |
| 改代码无变化 | 没重新 build / 跑的是旧产物 | npm run build 后开新会话 |
| 解码类工具崩 | 抛了未捕获异常 | 改用 isError: true 返回结构化错误 |
examples/ 目录:包含 streamableHttp、stdio、scheduling 等多套可直接运行的示例,进阶实现的首选参考。z.object、z.union、z.discriminatedUnion),让复杂工具入参既灵活又安全。.mcp.json 与 README,完成从"会用"到"会造"的闭环。McpServer 高层 API 与各传输层实现。[!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 等场景全覆盖。
