首页 › tutorials › 用TS SDK造MCP:调试到接入闭环

概述

本教程面向想亲手构建 MCP(Model Context Protocol)工具并接入 Claude Code 的开发者。我们将使用官方 TypeScript SDK(@modelcontextprotocol/sdk)从零搭建一个"开发者瑞士军刀"MCP 服务器,借助 MCP Inspector 进行可视化调试,最后通过 claude mcp add 与项目级 .mcp.json 把它接入 Claude Code,跑通"开发→调试→接入"的完整闭环。全文配套一个离线可运行的多工具项目,无需任何 API Key。


一、为什么用 TypeScript SDK

MCP 生态里已有 Go、Python、Rust、Java 等多套官方/社区 SDK,但 TypeScript SDK 始终是受众最广、迭代最快、示例最全的一套,原因有三:

  1. 与 Claude Code 同源:Claude Code 本身是 Node/TS 生态,stdio 子进程通信最顺畅,跨平台路径处理与 Node 内置模块(crypto、fs、child_process)开箱即用。
  2. 高层 McpServer API 极简:注册一个工具只需 server.tool(name, desc, schema, handler) 四个参数,配合 Zod 声明入参,自动生成 JSON Schema 给客户端。
  3. Inspector 一键调试:官方提供的 @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(或按官方推荐的原生安装方式)。


三、第 1 步:脚手架与依赖

新建项目目录并初始化:

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。

常见问题:


四、第 2 步:用高层 API 注册第一个工具

新建 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 服务器就是一个"哑终端",等待客户端发指令。

关键点解析:

常见问题:


五、第 3 步:补全多工具(哈希与编解码)

把"瑞士军刀"补成三件套: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 这种不存在的东西。

设计要点:


六、第 4 步:用 MCP Inspector 可视化调试

这是 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/ 的地址,浏览器打开它。

在面板里完成以下验证:

  1. 连接:左侧 Server Status 应显示 Connected。若失败,检查 Server Command 是否是 node、Args 是否是 build/index.js。
  2. Tools 标签页:应看到 encode-decode、hash-text、uuid-generate 三个工具,每个工具下方有 SDK 根据 Zod 自动生成的参数表单。
  3. 单工具调用:点开 hash-text,填 text = hello、algorithm = sha256,点 Run Tool,右侧应返回 sha256: 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824。
  4. Notifications / 原始报文:切换到 Notifications 或 Protocol Log 标签,能看到完整的 JSON-RPC tools/list 与 tools/call 请求/响应,是排查 schema 问题的金标准。

为什么这一步值钱:没有 Inspector 时,你只能靠 Claude Code 反馈"工具没出现",但分不清是 schema 错、stdout 污染、还是握手失败。Inspector 把整条链路摊开在眼前。

常见问题:


七、第 5 步:接入 Claude Code

调试通过后,把服务器正式挂进 Claude Code。Claude Code 支持 三种作用域:

作用域 标志 配置位置 适用场景
local(默认) -s local ~/.claude.json(项目私有) 只在当前项目、当前机器用
project -s project 项目根 .mcp.json(可提交 Git) 团队共享,新人 clone 即用
user -s user ~/.claude.json(全局) 所有项目都可用

方式 A:命令行添加(local 作用域)

# 先 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,...),并在回复里贴出结果。

方式 B:项目级 .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 命令可查看连接详情与工具数。

常见问题:


八、实战案例:完整工作流串讲

把上述步骤串成一条可复现的流水线。假设你刚加了一个 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

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 鉴权(属另一专题,本篇不展开)。


十、检查清单与排错速查

完成本教程后,对照下表自检:

症状 可能原因 处置
工具不出现 stdout 被污染 / schema 非法 改 console.error;查 Inspector 报文
failed to connect 路径相对 / 工作目录错 用绝对路径;单独 node build/index.js 试跑
模型乱传参数 enum 写成了自由 string 用 z.enum 收窄取值
改代码无变化 没重新 build / 跑的是旧产物 npm run build 后开新会话
解码类工具崩 抛了未捕获异常 改用 isError: true 返回结构化错误

延伸阅读


训练营适用性评估


参考来源

  1. modelcontextprotocol/typescript-sdk(官方 TypeScript SDK) — 官方 SDK 源码、McpServer 高层 API 与各传输层实现。
  2. MCP Specification 2025-06-18 — Transports — stdio 与 Streamable HTTP 两种标准传输的协议定义。
  3. How to build MCP servers with TypeScript SDK — DEV Community — 从零搭建 TS MCP 服务器的实战教程,含工具注册示例。
  4. Tool Registration and Execution — DeepWiki — 对 SDK 工具注册机制、Zod→JSON Schema 编译与执行流程的深入解析。
  5. @modelcontextprotocol/sdk — npm — 包安装、版本与发布说明。

[!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 等场景全覆盖。

51学通信知识星球海报-扫码加入