入门】用 Node.js 写一个 STDIO 版 MCP 服务器:TaoToken 配置与调试骨架 1. 为什么本地 MCP 服务器值得先跑一个 STDIO 版本MCPModel Context Protocol说白了就是给 AI 工具定的一套「点菜规则」AI 负责按标准格式说出要调什么工具、传什么参数你的服务器负责真正执行并把结果按标准格式端回去。它解决的是「AI 想用你的能力但每家接法都不一样」的问题。而 STDIO 方式是所有传输方式里门槛最低的一种——不用开端口、不用配网络、不用管鉴权网关进程之间用标准输入输出对话就行特别适合本地工具链、个人脚本、编辑器插件这类场景。这篇面向的是想从零跑通本地 MCP 工具链的 Node.js 开发者。我会带你写一个最小可用的 STDIO 版 MCP 服务器给出可直接复制的package.json、server启动骨架再补上 TaoToken 统一 Key/API 通道的settings.json配置片段最后用一次真实的 STDIO 握手和工具调用把整条链路验证一遍。全程不需要你懂协议细节照着敲就能跑起来。适合谁写过一点 Node.js、想让自己的脚本被 AI 工具调用的人或者已经在用支持 MCP 的编辑器、想搞清楚「服务器那头到底发生了什么」的人。跑完这一遍你对 MCP 的握手、工具注册、参数校验、返回结构会有一个能上手改的实体认知而不是停留在概念层。2. TaoToken 前置把 Key 和 API 通道先备好在写代码之前先把「AI 侧怎么连上模型」这件事解决掉。MCP 服务器本身只负责执行工具真正发起对话、决定调用哪个工具的是模型客户端。如果你用的是支持自定义 API 通道的客户端可以把它统一指向 TaoToken这样 Key 管理、模型切换、用量查看都在一个地方不用每个工具各配一套。TaoToken 在这里扮演的是统一入口你拿到一个 Key客户端通过https://taotoken.net/api这个 API 地址访问模型模型对话、编码计划、控制台、Key 管理都有对应页面。对本地 MCP 调试来说好处是你不用在多个客户端之间来回换 Key调试时切换模型也方便。具体操作路径先去控制台创建 API Key地址是https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewrite在 Key 管理页生成一个复制保存好后面配置里要用。如果你只是想先验证模型能不能通可以直接用模型对话页试一句https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_contentmodel_chatutm_campaignrewrite能正常返回就说明 Key 和通道没问题。如果你打算长期做编码类、Agent 类的接入建议看一下 Coding Planhttps://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_contentcoding_planutm_campaignrewrite它更适合高频调用场景。Key 的详细管理入口在https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi_keysutm_campaignrewrite接入文档在https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite遇到字段含义不清楚的时候翻文档最快。注意Key 属于敏感凭据别写进会提交到仓库的文件里。本地调试建议用环境变量或单独的本地配置文件并在.gitignore里排除掉。3. 可复制配置package.json 与服务器启动骨架先建目录、初始化项目。Node.js 建议用 18 以上版本SDK 对 ESM 支持更稳。mkdir mcp-stdio-demo cd mcp-stdio-demo npm init -y然后把package.json改成下面这样。关键是type: module因为 SDK 用的是 ESM 导入语法依赖只装两个MCP 官方 SDK 和 zod用来描述工具参数的类型和校验规则。{ name: mcp-stdio-demo, version: 1.0.0, description: A minimal STDIO MCP server demo, main: server.js, type: module, scripts: { start: node server.js, inspect: npx modelcontextprotocol/inspector node server.js }, dependencies: { modelcontextprotocol/sdk: ^1.20.2, zod: ^3.23.8 } }装依赖npm install接着写server.js。这个骨架做了三件事创建一个 MCP 服务器实例、注册一个带参数校验的工具、用 STDIO 传输层把服务器挂起来。注意日志一律走console.error因为console.log在 STDIO 模式下会污染协议通道这是新手最容易踩的坑。#!/usr/bin/env node import { McpServer } from modelcontextprotocol/sdk/server/mcp.js; import { StdioServerTransport } from modelcontextprotocol/sdk/server/stdio.js; import { z } from zod; const server new McpServer({ name: demo_service, version: 1.0.0 }); // 注册一个工具say_hello server.tool( say_hello, { needShowMeText: z.string().describe(想要展示的话) }, async ({ needShowMeText }) { try { return { content: [{ type: text, text: Hello needShowMeText }] }; } catch (error) { return { content: [{ type: text, text: 失败: ${error.message} }], isError: true }; } } ); async function main() { try { const transport new StdioServerTransport(); await server.connect(transport); console.error(MCP 服务器已启动等待 STDIO 连接); } catch (error) { console.error(启动服务器时出错:, error); process.exit(1); } } main();这里server.tool的三个参数分别是工具名、参数 schemazod 对象describe里的文字会作为参数说明暴露给模型、执行函数。执行函数返回的content数组是 MCP 规定的标准返回结构type: text表示文本结果出错时把isError设为true客户端就能识别为失败。如果你想让这个服务器被编辑器里的 AI 工具接入还需要在客户端的 MCP 配置里登记它。以常见的settings.json风格配置为例片段长这样{ mcpServers: { mcp-stdio-demo: { command: node, args: [/absolute/path/to/mcp-stdio-demo/server.js], env: { TAOTOKEN_API_KEY: 你的Key, TAOTOKEN_BASE_URL: https://taotoken.net/api } } } }command是启动命令args是脚本路径注意用绝对路径相对路径在不同客户端的工作目录下容易找不到文件。env里放的是给服务器进程用的环境变量如果你的工具需要调用模型就可以在这里注入 TaoToken 的 Key 和 API 地址服务器里用process.env.TAOTOKEN_API_KEY读取即可。4. 验证请求一次 STDIO 握手与工具调用代码写完先确认进程能起来node server.js正常的话终端不会有标准输出只会在 stderr 打印「MCP 服务器已启动等待 STDIO 连接」然后进程挂起等待输入——这就是 STDIO 模式的正常状态它在等客户端通过标准输入发消息。最省事的验证方式是用官方调试工具 Inspectornpx modelcontextprotocol/inspector node server.js它会起一个本地网页界面自动连上你的服务器。在界面里你能看到服务器信息、已注册的工具列表点进say_hello在参数框里填一段文字比如world点执行。如果返回里出现Tool Result: Success并且内容区显示Hello world说明握手、工具发现、参数传递、结果返回整条链路都通了。想更硬核一点也可以手动发一条 JSON-RPC 消息验证握手。MCP 基于 JSON-RPC 2.0初始化请求大概长这样{jsonrpc:2.0,id:1,method:initialize,params:{protocolVersion:2024-11-05,capabilities:{},clientInfo:{name:manual-test,version:1.0.0}}}把它通过标准输入喂给进程你会收到一条包含serverInfo和capabilities的响应这就代表握手成功。日常调试用 Inspector 就够了手动发消息主要是帮你理解协议层发生了什么。5. 本篇常见错排查启动就报Cannot use import statement outside a modulepackage.json里漏了type: module或者你用了.cjs后缀。补上这一行即可。客户端连不上、提示找不到命令args里的脚本路径写成了相对路径。改成绝对路径Windows 下注意反斜杠转义或直接用正斜杠。工具调用返回乱码或客户端直接断开检查代码里有没有用console.log输出调试信息。STDIO 模式下标准输出是协议通道任何非协议内容都会破坏消息解析调试信息一律用console.error。Inspector 里看不到工具确认server.tool(...)的注册代码在server.connect(transport)之前执行。如果注册写在main之后或者异步没等待工具列表会是空的。参数校验一直失败zod schema 的字段名要和执行函数解构出来的名字完全一致needShowMeText大小写错一个字母就会报参数缺失。改了代码但行为没变客户端可能缓存了旧的服务器进程。重启客户端或者确认你改的是客户端实际加载的那个文件路径。6. 把这条链路接到你的真实工具上跑通这个骨架之后真正有价值的是往里塞你自己的逻辑。say_hello换成查数据库、读本地文件、调内部接口都行返回结构保持content数组的格式就不会出问题。参数 schema 用 zod 描述得越清楚模型越知道该怎么传参describe里的说明文字别偷懒。如果你后面要接模型能力Key 和通道统一走 TaoToken 会省很多事模型对话验证在https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_contentmodel_chatutm_campaignrewriteKey 管理在https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi_keysutm_campaignrewrite接入字段不清楚就查https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite。长期做编码和 Agent 接入的话Coding Plan 那条线更合适https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_contentcoding_planutm_campaignrewrite。一个实用建议把每个工具的执行函数都包一层 try/catch出错时返回isError: true而不是让进程崩掉。STDIO 服务器一旦退出客户端那边就是「连接断开」排查起来比看一条错误返回麻烦得多。