
1. 为什么本地工具链需要一个 stdio 版 MCP Server如果你正在用 Trae、Cline、Claude Code 这类 AI 编程客户端大概率遇到过同一个尴尬模型能读代码、能改文件但一旦你想让它调用你自己写的脚本、查一下本地某个服务的状态、或者跑一段项目专属的构建逻辑它就抓瞎了。它没有手只有嘴。MCPModel Context Protocol就是给模型装手的那套标准。你可以把它理解成「AI 世界的 USB-C 接口」Server 端负责声明「我这儿有哪些工具、参数长什么样」Client 端负责把这些工具暴露给模型模型决定什么时候调用、传什么参数。协议统一之后你写一次 Server所有支持 MCP 的客户端都能用。而 stdio 传输是本地工具链集成里最省事的一种方式。它不需要开端口、不需要配证书、不需要考虑跨域Client 直接以子进程方式启动你的 Node.js 程序通过标准输入输出交换 JSON-RPC 消息。进程即服务退出即下线干净利落。这篇文章要带你从零跑通一条最小可用链路用 TypeScript Node.js 写一个基于 stdio 的 MCP Server注册一个calculate_sum工具先用 MCP Inspector 验证再接入 Trae 完成一次真实的工具调用。全程可复制每一步都有验证命令。适合有前端或 Node 基础、想给自己的 AI 工作流加「本地能力」的开发者。我试过跳过 Inspector 直接接客户端结果一个路径转义问题卡了半小时所以强烈建议按顺序来先让 Inspector 跑通再动客户端配置。2. 环境准备与 TaoToken 前置配置在写代码之前先把「模型从哪来」这件事解决掉。MCP Server 本身不产生智能它只是工具的执行端真正决定调用哪个工具的是背后的大模型。所以你需要一个能稳定访问模型的入口。TaoToken 在这里扮演的角色是模型接入层它提供统一的 API 地址和密钥让你在 Trae、Cline、Claude Code 这些客户端里填一次配置就能用上模型不用每个客户端单独折腾。官网地址是 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 端点是 https://taotoken.net/api 。具体要准备三样东西我把它叫做「三件套」后面所有客户端配置都围绕它展开第一件是 Base URL。这是客户端发送模型请求的地址填https://taotoken.net/api。注意不要带末尾斜杠也不要自己拼/v1客户端一般会自己处理路径拼接。第二件是 API Key。去控制台创建地址是 https://taotoken.net/console/api-keys 。创建后立刻复制保存页面刷新后就看不到了。Key 的格式通常是一串以特定前缀开头的长字符串别把它提交到 Git 仓库里建议放.env或者客户端的密钥管理里。第三件是 Model ID。这是你要调用的具体模型标识比如claude-sonnet-4-5之类的字符串。不同客户端对模型名的写法可能略有差异以文档为准文档地址 https://taotoken.net/doc 。如果你打算长期做编码类任务、跑 Agent 工作流可以了解一下 Coding Plan地址是 https://taotoken.net/coding-plan 它针对高频编码场景做了额度优化。只是想先验证一下模型能不能通用模型对话页面 https://taotoken.net/models 直接试一句就行。这里有个顺序问题值得强调很多人一上来就写 MCP Server写完发现客户端连不上模型于是分不清到底是 Server 的问题还是模型接入的问题。正确做法是先确保模型通道是通的再叠加 MCP 这一层。两层分开验证排障成本会低很多。配置模型通道时客户端里通常需要填的就是上面三件套。以 Trae 为例在模型设置里选择自定义 APIBase URL 填https://taotoken.net/apiAPI Key 粘贴你创建的那串Model ID 填你要用的模型名。保存后发一句「你好」测试能正常回复就说明通道没问题。这一步做完你就有了一台「会思考的机器」。接下来要做的是给它装上一只「能干活的手」——也就是我们的 MCP Server。3. 从零搭建 TypeScript MCP Server 的完整配置这一节是全文的技术核心我会把项目初始化、依赖、配置、源码逐段拆开讲每一段都给出可直接复制的片段。3.1 初始化项目与安装依赖先建目录、初始化 package.jsonmkdir my-mcp cd my-mcp pnpm init然后安装运行时依赖和开发依赖。运行时依赖只有两个官方 MCP SDK 和 Zod。pnpm add modelcontextprotocol/sdk zod pnpm add -D typescript types/node tsx vitest版本方面SDK 用当前最新的 1.x 即可Zod 建议用 v4 分支导入路径是zod/v4TypeScript 用 5.x 以上。不要手动锁死到很旧的版本SDK 迭代比较快旧版本可能缺少registerTool这类新 API。3.2 package.json 关键配置打开 package.json改成下面这样。重点是type: module和 scripts 部分{ 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 }, 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。如果不声明 module 类型Node 会把编译后的.js当成 CommonJS 解析直接报Cannot use import statement outside a module。每条脚本的用途也说清楚dev用 tsx 监听源码变化自动重启开发时用typecheck只做类型检查不产出文件提交前跑build把 src 编译到 diststart运行编译产物客户端实际调用的就是这个test留给后续写单元测试。改完执行一次pnpm install让锁文件和 package.json 对齐。3.3 tsconfig.json 配置根目录创建 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] }几个关键项module和moduleResolution都必须是NodeNext这样 TypeScript 才会按 Node 的 ESM 规则解析模块导入 SDK 子路径时也要求带.js后缀。rootDir和outDir决定了源码和产物的位置。strict: true建议开着MCP 的参数校验很依赖类型推导。3.4 编写入口文件 src/index.ts创建src/index.ts完整内容如下import { McpServer } from modelcontextprotocol/sdk/server/mcp.js; import { StdioServerTransport } from modelcontextprotocol/sdk/server/stdio.js; import * as z from zod/v4; const server new McpServer({ 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 }) { const result a b; return { content: [ { type: text, text: ${a} ${b} ${result}, }, ], }; }, ); async function main(): Promisevoid { const transport new StdioServerTransport(); await server.connect(transport); console.error(my-mcp server is running via stdio); } main().catch((error: unknown) { console.error(MCP Server 启动失败, error); process.exit(1); });逐段解释一下。new McpServer({ name, version })创建服务实例name 和 version 是客户端连接后看到的服务身份不是工具名。registerTool有三个参数工具标识calculate_sum建议英文 snake_case稳定唯一、配置对象、以及真正执行的 handler。配置对象里的description不是写给人看的是写给 AI 看的。模型靠它判断「什么时候该调用这个工具」所以要明确写出「做什么」和「什么时候用」。inputSchema用 Zod 定义它一箭三雕向客户端声明参数结构、运行时校验外部输入、给 handler 里的a、b推导出 number 类型。因为 AI 传来的参数属于外部输入光靠 TypeScript 的编译时类型是不够的必须有运行时校验。handler 返回的不是普通字符串而是 MCP 规定的内容数组第一版用最简单的 text 类型即可。最后是 stdio 传输new StdioServerTransport()让当前 Node 进程通过 stdin/stdout 收发 MCP 消息。这里有个必须记住的坑——stdio 模式下 stdout 是协议通道日志必须用console.error绝对不能用console.log。你随手打一行console.log(debug)就可能污染协议流导致客户端解析失败、连接断开。3.5 类型检查与构建pnpm typecheck pnpm buildtypecheck 应该无输出正常结束。build 成功后 dist 目录下会出现index.js和index.js.map。然后试启动pnpm start看到my-mcp server is running via stdio就对了。进程会一直挂着等待客户端消息这是正常现象不是卡死按 CtrlC 停止。注意pnpm start只能证明进程能起来不能证明工具能被调用因为 stdio Server 在等符合 MCP 协议的输入。下一步用 Inspector 做真正的验证。4. 用 MCP Inspector 验证工具调用Inspector 是 MCP 官方的交互式调试客户端能发现并调用 Server 暴露的工具。不用装成项目依赖直接 dlx 跑pnpm dlx modelcontextprotocol/inspector node dist/index.js命令会输出一个本地访问地址浏览器打开后按这几步操作确认 Transport 选 STDIOCommand 填nodeArguments 填当前项目dist/index.js的路径点连接。然后打开 Tools 面板点「列出工具」应该能看到calculate_sum。输入 a 10、b 20调用。预期返回10 20 30如果相对路径解析异常用绝对路径pnpm dlx modelcontextprotocol/inspector node d:\BFF-BackendForFrontend\myMcp\dist\index.jsInspector 验证通过意味着整条链路都通了Node 进程能启动、MCP 握手成功、客户端能发现工具、参数 Schema 正常、handler 能执行并返回 MCP 内容。这一步过了再去接客户端问题范围就缩小到「客户端配置」这一层。接下来接入 Trae。不同版本入口位置可能不同但核心永远是「命令 参数 工作目录」。在 MCP 设置里新增本地 stdio Server配置如下{ mcpServers: { my-mcp: { command: node, args: [ d:\\BFF-BackendForFrontend\\myMcp\\dist\\index.js ], cwd: d:\\BFF-BackendForFrontend\\myMcp } } }三个注意点Windows 路径的反斜杠在 JSON 里必须写成\\用dist/index.js之前必须先pnpm buildcommand 用node不要用会持续 watch 的pnpm dev否则客户端会以为进程没退出。保存后重连该 Server确认 Trae 显示calculate_sum然后发测试指令请使用 calculate_sum 工具计算 135 和 246 的和并告诉我工具返回了什么。预期 AI 识别出要调用工具参数{ a: 135, b: 246 }Server 返回135 246 381AI 把结果告诉你。首次联调一定要明确说「使用工具」因为简单算术模型可能觉得不需要调用工具直接心算就答了那样验证不到链路。5. 常见报错排查从 401 到协议断开这一节按真实报错来对照遇到问题直接查。401 Unauthorized / invalid api key。这是模型通道的问题不是 MCP Server 的问题。检查客户端里填的 API Key 是否完整、有没有多余空格、是否已经过期。Base URL 确认是https://taotoken.net/api不要自己加/v1。如果 Key 是在控制台刚创建的确认复制的是完整串。local proxy failed / connection refused。客户端连不上模型端点。先确认网络能访问https://taotoken.net/api再确认 Base URL 没写错。如果客户端有代理设置检查是否误开了本地代理导致请求被拦。Error reading choices / unexpected response format。通常是 Base URL 或 Model ID 填错客户端拿到了非预期的响应结构。核对 Model ID 是否是该端点支持的模型名参考文档 https://taotoken.net/doc 。OAuth / authentication failed。某些客户端默认走 OAuth 流程但自定义 API 应该走 Key 认证。在客户端设置里切换到「自定义 API」或「API Key」模式填三件套。Trae 找不到 Tool。按顺序查是否执行过pnpm builddist/index.js是否存在Trae 里入口路径是否为绝对路径JSON 里\\是否正确Server 是否已启用或重连Inspector 能否发现calculate_sum。如果 Inspector 正常而 Trae 不正常问题在 Trae 配置如果 Inspector 也失败优先查代码和构建产物。修改源码后行为没变化。Trae 运行的是dist/index.js不是src/index.ts。重新pnpm build然后重连 Server。进程启动后一直不退出。这是正常的stdio Server 必须持续等待客户端消息。手动启动的进程用 CtrlC 停。JSON 配置无法解析。Windows 路径必须转义成d:\\path\\to\\index.js不能写成d:\path\to\index.js。协议解析错误或 Server 意外断开。九成是业务代码里用了console.log()。stdio 模式下 stdout 是协议通道所有日志改成console.error()。Node 找不到模块。确认在项目根目录执行过pnpm install和pnpm build并确认 Node.js 版本满足 SDK 要求18推荐 20 LTS 以上。TypeScript 编译报 ESM 相关错误。检查三处package.json 有type: moduletsconfig.json 的module和moduleResolution都是NodeNext导入 SDK 子路径时带.js后缀。排障时记住一个原则分层验证。模型通道用一句「你好」验证MCP 链路用 Inspector 验证客户端配置用 Trae 验证。哪一层失败就查哪一层不要混在一起猜。6. 下一步把这条链路用起来最小链路跑通之后别急着加数据库、远程 HTTP 或复杂框架。建议按这个顺序扩展先把求和逻辑提取成独立函数用 Vitest 写单元测试再实现一个analyze_package_json工具练习文件读取和边界校验然后实现explain_npm_script学习多个 Tool 的组织方式之后学 MCP Resources向 AI 暴露只读项目信息最后再碰 Streamable HTTP、认证和远程部署。第二个工具我建议选analyze_package_json因为它和前端经验直接相关又不依赖 API Key 或数据库练手成本最低。日常开发工作流固定成这套改源码时pnpm dev提交或接入客户端前依次跑pnpm typecheck、pnpm test、pnpm build每次改完src/index.ts都要重新 build然后在客户端重连 Server。如果你还没配好模型通道先去 https://taotoken.net/api-keys 创建 Key文档在 https://taotoken.net/doc 想直接试模型效果可以打开 https://taotoken.net/models 聊两句。长期做编码和 Agent 任务的话https://taotoken.net/coding-plan 会更划算。最后留一个我踩过的坑Inspector 里工具能列出但调用报参数错误八成是 Zod Schema 写成了z.string()而模型传的是数字。Schema 要和实际业务类型严格对齐别指望模型帮你做类型转换。