系列文章目录
- 第一章 TypeScript MCP Server:从零到一(已更新)
- 第二章 TypeScript MCP Server:提取业务逻辑与建立自动化测试(已更新)
- 第三章 TypeScript MCP Server:分析 package.json 与处理文件系统边界(已更新)
- 第四章 TypeScript MCP Server:多 Tool 组织与模块复用(已更新)
- 第五章 TypeScript MCP Server:Resources、Prompts 与结构化输出(已更新)
- 第六章 TypeScript MCP Server:独立综合项目与能力验收(已更新)
提示:写完文章后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
- 系列文章目录
- 前言
- 一、认识 MCP 与运行链路
- 1.1 MCP 是什么?
- 1.2 运行链路一览
- 二、项目初始化
- 2.1 运行时依赖
- 2.2 开发依赖
- 2.3 配置 package.json
- 为什么设置 `"type": "module"`
- 每条脚本的用途
- 2.4 创建 TypeScript 配置
- 三、编写第一个 MCP Server
- 3.1 创建入口文件
- 3.2 逐段理解源码
- McpServer
- registerTool
- Zod 输入 Schema
- Tool 返回值
- stdio Transport
- 为什么日志必须使用 `console.error`
- 四、类型检查与构建
- 五、使用 MCP Inspector 调试
- 六、接入 Trae
- 七、日常开发工作流
- 八、常见问题排查
- 8.1 Trae 找不到 Tool
- 8.2 修改源码后行为没有变化
- 8.3 进程启动后一直不退出
- 8.4 JSON 配置无法解析
- 8.5 协议解析错误或 Server 意外断开
- 8.6 Node 找不到模块
- 8.7 TypeScript 编译报 ESM 相关错误
- 九、验收清单与后续学习
- 9.1 第一阶段验收清单
- 9.2 第一阶段之后学什么
- 9.3 命令汇总
- 总结
前言
提示:本文记录如何从零搭建一个基于 TypeScript、Node.js 和 stdio 的本地 MCP Server,并接入 Trae 完成第一个 Tool 的调用。
随着 AI 编程助手的普及,如何让大模型安全、可控地调用本地能力成为了一个关键问题。MCP(Model Context Protocol)正是为此而生——它定义了一套标准协议,让 AI 能够发现并调用外部工具。本文将以一个最小可运行的项目为例,带你从依赖配置、源码编写、Inspector 调试,一直走到在 Trae 中成功调用第一个calculate_sumTool。
操作原则:每完成一节,先执行该节的验证命令;验证通过后再继续。
提示:以下是本篇文章正文内容,下面案例可供参考
一、认识 MCP 与运行链路
1.1 MCP 是什么?
MCP(Model Context Protocol)是一种开放协议,用于标准化应用程序向大语言模型暴露上下文和工具的方式。你可以把它理解为"AI 的 USB-C 接口"——不管 Server 端如何实现,Client 端(如 Trae)只要遵循协议,就能统一发现和调用工具。
1.2 运行链路一览
理解整体链路比急着写代码更重要。本次要建立的链路如下:
用户 ↓ 自然语言 Trae 中的 AI(MCP Client) ↓ 判断是否调用工具 calculate_sum Tool ↓ MCP 消息(stdio) 本地 Node.js MCP Server ↓ 执行 TypeScript 中定义的函数 返回计算结果 ↓ Trae 组织最终答案各角色职责:
- Trae是 MCP Client,负责连接 Server,并把可用 Tool 提供给 AI。
- 本项目是 MCP Server,负责声明并执行 Tool。
- stdio是通信通道。Trae 会启动本项目的 Node 进程,通过标准输入和标准输出交换 MCP 协议消息。
- calculate_sum是第一个 Tool,相当于"给 AI 调用的函数"。
二、项目初始化
2.1 运行时依赖
| 依赖 | 当前版本 | 作用 |
|---|---|---|
@modelcontextprotocol/sdk | ^1.29.0 | 官方 MCP TypeScript SDK,用于创建 Server、注册 Tool 和建立 stdio 通信 |
zod | ^4.4.3 | 定义并校验 Tool 的输入参数,同时帮助 SDK 生成参数 Schema |
2.2 开发依赖
| 依赖 | 当前版本 | 作用 |
|---|---|---|
typescript | ^7.0.2 | 类型检查并将 TypeScript 编译为 JavaScript |
@types/node | ^26.1.1 | 为process、Node 文件系统等 API 提供类型 |
tsx | ^4.23.1 | 开发阶段直接执行 TypeScript |
vitest | ^4.1.10 | 单元测试框架;不是运行 MCP 的必需依赖,但后续测试会用到 |
2.3 配置 package.json
打开项目根目录的package.json,修改为下面的结构。依赖版本保留 pnpm 当前安装的实际值,不要手动降级或复制其他版本。
{"name":"my-mcp","version":"1.0.0","description":"A TypeScript MCP server for learning MCP","type":"module","main":"dist/index.js","scripts":{"dev":"tsx watch src/index.ts","build":"tsc -p tsconfig.json","start":"node dist/index.js","typecheck":"tsc -p tsconfig.json --noEmit","test":"vitest run"},"keywords":["mcp","model-context-protocol"],"author":"","license":"ISC","packageManager":"pnpm@10.28.2","dependencies":{"@modelcontextprotocol/sdk":"^1.29.0","zod":"^4.4.3"},"devDependencies":{"@types/node":"^26.1.1","tsx":"^4.23.1","typescript":"^7.0.2","vitest":"^4.1.10"}}为什么设置"type": "module"
MCP SDK 以现代 ESM 方式提供模块,源码会使用:
import{McpServer}from"@modelcontextprotocol/sdk/server/mcp.js";"type": "module"告诉 Node.js,编译后的.js文件按照 ESM 运行,而不是 CommonJS。
每条脚本的用途
pnpm dev:开发时监听源码变化并自动重启。pnpm typecheck:只检查类型,不生成文件。pnpm build:将src编译到dist。pnpm start:运行编译后的正式入口。pnpm test:后续运行 Vitest 测试。
修改后执行:
pnpm install这一步会让锁文件与package.json保持一致。
2.4 创建 TypeScript 配置
在项目根目录创建tsconfig.json:
{"compilerOptions":{"target":"ES2022","module":"NodeNext","moduleResolution":"NodeNext","rootDir":"src","outDir":"dist","strict":true,"esModuleInterop":true,"forceConsistentCasingInFileNames":true,"skipLibCheck":true,"sourceMap":true,"types":["node"]},"include":["src/**/*.ts"],"exclude":["node_modules","dist"]}关键配置说明:
target: ES2022:使用现代 Node.js 支持的 JavaScript 能力。module/moduleResolution: NodeNext:按 Node.js ESM 规则解析模块。rootDir: src:TypeScript 源码放在src。outDir: dist:编译结果放在dist。strict: true:开启严格类型检查。types: ["node"]:加载 Node.js 类型。
三、编写第一个 MCP Server
3.1 创建入口文件
创建src/index.ts:
import{McpServer}from"@modelcontextprotocol/sdk/server/mcp.js";import{StdioServerTransport}from"@modelcontextprotocol/sdk/server/stdio.js";import*aszfrom"zod/v4";constserver=newMcpServer({name:"my-mcp",version:"1.0.0",});server.registerTool("calculate_sum",{title:"两数求和",description:"计算两个数字的和。当用户需要对两个数字做加法时使用。",inputSchema:{a:z.number().describe("第一个数字"),b:z.number().describe("第二个数字"),},},async({a,b})=>{constresult=a+b;return{content:[{type:"text",text:`${a}+${b}=${result}`,},],};},);asyncfunctionmain():Promise<void>{consttransport=newStdioServerTransport();awaitserver.connect(transport);console.error("my-mcp server is running via stdio");}main().catch((error:unknown)=>{console.error("MCP Server 启动失败:",error);process.exit(1);});3.2 逐段理解源码
McpServer
constserver=newMcpServer({name:"my-mcp",version:"1.0.0",});它创建 MCP Server 实例。name和version是客户端连接后看到的服务身份信息,不是 Tool 名称。
registerTool
server.registerTool("calculate_sum",config,handler);它包含三部分:
calculate_sum:稳定、唯一的 Tool 标识,推荐使用英文和 snake_case。config:告诉客户端和 AI 这个 Tool 做什么、接受什么参数。handler:Tool 被调用时真正执行的业务代码。
description不只是给人看的。AI 会依靠它决定何时调用 Tool,所以应明确写出"做什么"和"什么时候使用"。
Zod 输入 Schema
inputSchema:{a:z.number().describe("第一个数字"),b:z.number().describe("第二个数字"),}它同时承担:
- 向客户端声明参数结构;
- 在运行时校验外部输入;
- 给 TypeScript 推导 handler 中
a、b的类型。
因为 AI 传来的参数属于外部输入,不能只依赖 TypeScript 的编译时类型。
Tool 返回值
return{content:[{type:"text",text:"...",},],};Tool 不直接返回普通字符串,而是返回 MCP 规定的内容数组。第一版使用最简单的text内容。
stdio Transport
consttransport=newStdioServerTransport();awaitserver.connect(transport);这会让当前 Node.js 进程通过 stdin/stdout 接收和发送 MCP 消息。
为什么日志必须使用console.error
stdio 模式下,stdout用于传输 MCP 协议消息。随意调用console.log()可能污染协议流,导致客户端解析失败。
因此服务运行期间:
console.error("调试信息");不要使用:
console.log("调试信息");四、类型检查与构建
先执行类型检查:
pnpm typecheck预期:命令正常结束,没有 TypeScript 错误。
然后执行构建:
pnpm build构建成功后应出现:
dist/ ├─ index.js └─ index.js.map最后尝试启动:
pnpmstart预期看到:
my-mcp server is running via stdio进程会继续等待 MCP Client 发送消息,这是正常现象,不是卡死。按Ctrl+C停止。
注意:仅执行
pnpm start只能证明进程能启动,不能完整验证 Tool,因为 stdio Server 正在等待符合 MCP 协议的输入。下一节使用 Inspector 验证。
五、使用 MCP Inspector 调试
Inspector 是 MCP 的交互式调试客户端,可以发现并调用 Server 暴露的 Tool。
不必把 Inspector 安装为项目依赖,直接执行:
pnpm dlx @modelcontextprotocol/inspector node dist/index.js命令会输出本地访问地址。在浏览器打开该地址,然后:
- 确认 Transport 为
STDIO。 - 确认 Command 是
node。 - 确认 Arguments 包含当前项目的
dist/index.js。 - 点击连接按钮。
- 打开
Tools。 - 点击列出工具,应看到
calculate_sum。 - 输入
a = 10、b = 20。 - 调用 Tool。
预期结果:
10 + 20 = 30如果 Inspector 命令对相对路径解析异常,使用绝对路径:
pnpm dlx @modelcontextprotocol/inspector node"d:\BFF-BackendForFrontend\myMcp\dist\index.js"Inspector 验证通过意味着:
- Node 进程可以启动;
- MCP 握手成功;
- 客户端可以发现 Tool;
- 参数 Schema 正常;
- Tool handler 可以执行并返回 MCP 内容。
六、接入 Trae
不同版本的 Trae 设置入口和配置文件位置可能不同,但核心配置始终是"命令 + 参数 + 工作目录"。在 Trae 的 MCP 设置中新增本地 stdio Server。
推荐配置概念如下:
{"mcpServers":{"my-mcp":{"command":"node","args":["d:\\BFF-BackendForFrontend\\myMcp\\dist\\index.js"],"cwd":"d:\\BFF-BackendForFrontend\\myMcp"}}}注意事项:
- JSON 中 Windows 路径的反斜杠需要写成
\\。 - 使用
dist/index.js前必须先执行pnpm build。 command使用node,不要使用会持续 watch 的pnpm dev。- 如果 Trae 的可视化配置只提供 Command 和 Args,就分别填写
node与入口文件绝对路径。
保存配置后,重新加载或连接该 MCP Server。确认 Trae 显示calculate_sumTool,然后发起测试:
请使用 calculate_sum 工具计算 135 和 246 的和,并告诉我工具返回了什么。预期过程:
- AI 识别应调用
calculate_sum; - 参数为
{ "a": 135, "b": 246 }; - MCP Server 返回
135 + 246 = 381; - AI 将结果告诉你。
提示:明确要求"使用工具"是首次联调手段。正常使用时可以直接问"135 加 246 等于多少",但模型可能认为简单算术无需调用工具,因此不适合作为首次验证。
七、日常开发工作流
修改源码时:
pnpm dev提交或接入 Trae 前:
pnpm typecheck pnpm test pnpm buildTrae 使用编译后的dist/index.js,所以每次修改src/index.ts后,都要重新执行:
pnpm build然后在 Trae 中重启或重连 MCP Server。
八、常见问题排查
8.1 Trae 找不到 Tool
按顺序检查:
- 是否执行过
pnpm build; dist/index.js是否存在;- Trae 中入口路径是否为绝对路径;
- JSON 路径中的
\\是否正确; - MCP Server 是否已经在 Trae 中启用或重连;
- Inspector 是否能发现
calculate_sum。
如果 Inspector 正常而 Trae 不正常,问题通常在 Trae 配置;如果 Inspector 也失败,优先检查项目代码和构建结果。
8.2 修改源码后行为没有变化
Trae 运行的是dist/index.js,不是src/index.ts。重新执行pnpm build,然后重连 Server。
8.3 进程启动后一直不退出
这是正常的。stdio Server 必须持续等待客户端消息。使用Ctrl+C停止手动启动的进程。
8.4 JSON 配置无法解析
Windows 路径必须转义:
"d:\\BFF-BackendForFrontend\\myMcp\\dist\\index.js"不能直接写成:
"d:\BFF-BackendForFrontend\myMcp\dist\index.js"8.5 协议解析错误或 Server 意外断开
检查业务代码是否使用了console.log()。stdio Server 的普通日志应改成console.error()。
8.6 Node 找不到模块
确认在项目根目录执行过:
pnpm install pnpm build同时确认 Node.js 满足 SDK 要求。当前 SDK 要求 Node.js>=18,推荐使用 Node.js 20 或更高的 LTS 版本。
8.7 TypeScript 编译报 ESM 相关错误
确认:
package.json包含"type": "module";tsconfig.json同时使用"module": "NodeNext"和"moduleResolution": "NodeNext";- SDK 子路径导入带
.js后缀。
九、验收清单与后续学习
9.1 第一阶段验收清单
全部满足后,第一阶段完成:
package.json已配置 ESM 和开发脚本;- 已创建
tsconfig.json; - 已创建
src/index.ts; pnpm typecheck通过;pnpm build通过;dist/index.js已生成;- Inspector 能列出
calculate_sum; - Inspector 调用后返回正确结果;
- Trae 能连接
my-mcp; - Trae 能调用
calculate_sum。
9.2 第一阶段之后学什么
不要立即添加数据库、远程 HTTP 或复杂框架。建议按以下顺序扩展:
- 将求和业务逻辑提取成独立函数,并用 Vitest 编写单元测试;
- 实现
analyze_package_json,学习文件读取与边界校验; - 实现
explain_npm_script,学习多个 Tool 的组织方式; - 学习 MCP Resources,向 AI 暴露只读项目信息;
- 接入一个外部 API,学习
.env和密钥管理; - 最后学习 Streamable HTTP、认证和远程部署。
建议第二个 Tool 选择analyze_package_json,因为它与你的前端经验直接相关,并且不依赖 API Key 或数据库。
9.3 命令汇总
完成文件修改后,依次执行:
pnpm install pnpm typecheck pnpm build pnpmstart看到启动日志后按Ctrl+C,再运行 Inspector:
pnpm dlx @modelcontextprotocol/inspector node"d:\BFF-BackendForFrontend\myMcp\dist\index.js"Inspector 验证通过后,将node + dist/index.js 绝对路径配置到 Trae,并测试calculate_sum。
总结
提示:这里对文章进行总结:
本文从认识 MCP 协议和运行链路出发,完整走通了 TypeScript MCP Server 的搭建流程:配置package.json与tsconfig.json、编写第一个calculate_sumTool、逐段理解源码核心概念、完成类型检查与构建、使用 Inspector 交互式调试,最终成功接入 Trae 并完成首次工具调用。
关键要点回顾:
- stdio 通信下
stdout是协议通道,日志必须用console.error,否则会污染协议流。 - Zod Schema 三合一:同时承担参数声明、运行时校验和类型推导。
- Trae 运行的是
dist/index.js,每次改源码都要重新pnpm build。 - description 是写给 AI 看的,要明确"做什么"和"什么时候使用"。
- 首次联调要明确要求"使用工具",避免模型跳过工具调用。
下一篇文章我们将进入进阶 Tool 开发,提取业务逻辑并编写单元测试,敬请关注。