
1. 从零搭一个 MCP 服务为什么绕不开统一 Key 这件事MCPModel Context Protocol是 Anthropic 开源的一套协议简单说就是给大模型装了一个「万能插头」模型本身能力固定但通过 MCP 服务端它可以调用外部工具、读取本地文件、查询实时数据。你写一个 Node.js 服务注册几个 toolClaude 就能在对话里直接调用它们。适合谁适合想把 Claude 接进自己业务链路、又不想每次手动复制粘贴的开发者。但真正动手时第一个卡点往往不是协议本身而是模型调用通道。MCP 服务端要跑通通常需要一个能稳定调用 Claude 的 API 入口如果你同时还在用 Cursor、Cline、Claude Code 这些客户端每个地方都配一遍 Key、改一遍 Base URL维护成本很快就上来了。我试过把 Key 散落在四五个配置文件里改一次要翻半天。这篇就聚焦一件事用 Node.js 官方 SDK 从零搭一个基础 MCP 服务把模型调用通道统一到 TaoToken 的 Key 上交付可复制的package.json、服务端入口代码、Claude 客户端配置片段并给出启动、验证工具注册、调用返回的完整步骤。全程可跟做不需要你先懂协议细节。核心检索词先明确MCP 服务搭建、Claude 调用链、Node.js MCP SDK、TaoToken 统一 Key。读完你能得到一个能跑起来、能被 Claude 识别并调用工具的最小可用服务。2. TaoToken 前置准备统一 Key 与 API 通道在写代码之前先把「模型从哪来」这件事定下来。MCP 服务端本身不产生模型能力它只是工具层真正调用 Claude 的是客户端比如 Claude Code、Cline、Cursor。所以你需要一个统一的 API 通道让这些客户端都指向同一个入口、用同一个 Key。TaoToken 在这里扮演的就是这个统一入口的角色。它的 API 地址是https://taotoken.net/api你注册后在控制台生成一个 Key后续所有客户端都填这个 Key 和这个 Base URL。这样做的好处很直接换模型、调额度、排查调用问题都只在一个地方操作不用每个工具单独配。具体操作路径第一打开官网https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content注册并登录。第二进入控制台https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewrite在 API Keys 页面创建一个新 Key。建议命名带上用途比如mcp-dev方便以后区分。第三记下两个值Base URL 填https://taotoken.net/apiKey 填你刚生成的那串。这两个值后面会出现在客户端配置里。这里要强调一个概念区分很多人第一次会混MCP 服务端代码里不需要写 Key。服务端只负责注册工具、处理请求Key 是客户端调用模型时用的。所以本文的index.js里你不会看到任何 KeyKey 全部集中在客户端的配置文件里。这个分工想清楚了后面排错会轻松很多。如果你还想先验证模型通道是否通可以打开模型对话页面https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_contentmodel_chatutm_campaignrewrite发一条消息确认能正常返回再去配客户端。这一步能帮你把「Key 问题」和「MCP 代码问题」提前隔离开。3. 可复制配置package.json 与服务端入口代码这一节是全文的技术核心所有片段都可以直接复制。先建目录、初始化项目。mkdir mcp-demo cd mcp-demo npm init -y npm install modelcontextprotocol/sdk zod安装完成后把package.json改成下面这样。注意type字段用 CommonJS 还是 ESM 会影响你的require/import写法这里统一用 CommonJS降低新手门槛。{ name: mcp-demo, version: 1.0.0, description: 基础 MCP 服务示例统一走 TaoToken 通道, main: index.js, scripts: { start: node index.js }, dependencies: { modelcontextprotocol/sdk: ^1.6.1, zod: ^3.24.2 } }接着写服务端入口index.js。这个服务注册两个工具一个查天气模拟数据一个做加法。工具逻辑简单重点看注册结构和返回格式。const { McpServer } require(modelcontextprotocol/sdk/server/mcp.js); const { StdioServerTransport } require(modelcontextprotocol/sdk/server/stdio.js); const { z } require(zod); const server new McpServer({ name: DemoMcpServer, version: 1.0.0, }); // 工具一查询城市天气模拟数据 server.tool( getWeather, { city: z.string().describe(城市名称例如北京) }, async ({ city }) { const mock { 北京: 晴18-26℃, 上海: 多云20-27℃, 广州: 小雨24-30℃, }; const result mock[city] || 暂无该城市数据; return { content: [{ type: text, text: ${city}天气${result} }], }; } ); // 工具二两数相加 server.tool( addNumbers, { a: z.number().describe(第一个加数), b: z.number().describe(第二个加数), }, async ({ a, b }) { return { content: [{ type: text, text: ${a} ${b} ${a b} }], }; } ); async function main() { const transport new StdioServerTransport(); await server.connect(transport); console.error(MCP 服务已启动等待客户端连接); } main().catch((err) { console.error(启动失败, err); process.exit(1); });几个关键点解释一下。McpServer是 SDK 提供的服务端类server.tool()第一个参数是工具名第二个是参数 schema用 zod 定义第三个是执行函数。返回必须是{ content: [...] }结构type: text表示文本结果。日志用console.error而不是console.log因为 stdio 传输下 stdout 被协议占用往 stdout 打日志会污染通信。客户端配置片段以 Claude Code 的settings.json为例路径通常在用户目录下的.claude/settings.json{ mcpServers: { demo-mcp: { command: node, args: [/绝对路径/mcp-demo/index.js] } } }注意args里必须是绝对路径相对路径在客户端启动子进程时经常找不到文件。如果你用的是 Cline 或 Cursor配置结构类似都是mcpServers下加一个条目commandargs两件套。至于模型调用侧的 Key配在客户端自己的模型设置里Base URL 填https://taotoken.net/apiKey 填你在控制台生成的那串Model ID 按你实际要用的 Claude 模型填。这三件套Base URL Key Model ID缺一不可后面排错也围绕它们展开。4. 启动服务与验证工具注册、调用返回代码写完先单独跑一遍服务端确认它本身不报错。node index.js如果终端没有立刻退出、也没有抛异常说明 stdio 传输已经挂起等待连接这是正常现象。按CtrlC退出即可。这一步能排掉大部分语法错误和依赖缺失问题。接下来在客户端里验证。以 Claude Code 为例重启客户端后它会在启动时读取settings.json拉起你配置的 MCP 服务子进程。你可以用命令查看已注册的 MCP 服务列表确认demo-mcp出现在里面。然后直接在对话里发一句自然语言帮我查一下北京的天气正常情况下Claude 会识别到getWeather这个工具发起调用服务端返回北京天气晴18-26℃Claude 再把结果组织成回答给你。你会看到对话里出现工具调用的标记说明整条链路通了。再测一个带参数的用 addNumbers 算一下 37 加 58预期返回37 58 95。如果两个工具都能被正确调用说明工具注册、参数解析、返回格式三部分都没问题。验证成功的标志有三个客户端服务列表里能看到你的服务名对话触发工具调用时有明确的调用记录返回内容和服务端代码里的文本一致。三个都满足这条 Claude 调用链就算跑通了。5. 本篇常见错误排查401、local proxy failed、reading choices这一节按真实报错来遇到哪个查哪个。报错一401 Unauthorized。这是模型调用侧最常见的。原因通常是 Key 填错、Key 过期或者 Base URL 写成了带路径的地址。检查两点Base URL 必须是https://taotoken.net/api不要多加/v1之类的后缀Key 复制时不要带空格。改完重启客户端。报错二local proxy failed / connection refused。这个多半出在 MCP 服务端本身没起来。检查args里的路径是不是绝对路径、node命令是否在 PATH 里、index.js单独跑能不能正常挂起。如果单独跑就报错先解决代码问题单独跑正常但客户端连不上多半是路径或命令写错。报错三reading choices of undefined。这个报错说明客户端拿到了一个不符合预期的响应体通常是模型通道返回了错误结构而客户端还在按标准格式解析choices字段。根因还是通道配置Base URL 或 Model ID 不对。确认 Model ID 是你账号下真实可用的模型名不要凭记忆填。报错四OAuth 相关报错。如果你在 Claude Code 里看到 OAuth 认证失败说明客户端还在走它默认的登录流程没有用你配置的 Key。检查settings.json里模型相关的配置是否生效必要时清掉旧的登录缓存重新配。报错五工具注册了但 Claude 不调用。先确认工具名和描述是否清晰describe()里写清楚用途能显著提升调用率。其次确认参数 schema 类型正确比如数字别写成字符串。最后看服务端日志有没有收到请求收到请求但返回格式不对Claude 也会放弃调用。排查顺序建议固定下来先单独跑服务端 → 再看客户端服务列表 → 再发对话触发调用 → 最后看日志。按这个顺序问题基本能定位到具体环节不会来回瞎改。6. 把 Key 收口到一处后续扩展才不痛苦服务跑通只是起点。真正省心的地方在于你的 MCP 服务端代码里没有任何 Key所有模型调用都通过客户端统一走 TaoToken 通道。这意味着你以后加十个工具、接三个客户端Key 依然只有一份改配置只改一处。如果你打算长期做编码类、Agent 类的工作建议把通道固定下来用 Coding Plan 这类方案管理额度避免每次调用都临时找 Key。相关入口在https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding_planutm_campaignrewrite接入文档在https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewriteKey 管理在https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi_keysutm_campaignrewrite。最后给一个实用技巧把index.js里的工具逻辑拆成独立模块server.tool()只做注册和转发。这样工具一多代码不会挤在一个文件里排查也更快。我踩过的坑是早期把所有逻辑堆在入口文件加到第五个工具时已经很难读了。