
1. 从零构建 NodeJS 自定义 MCP 服务本地调试到 NPM 发布全流程MCPModel Context Protocol是让大模型调用外部工具的一套标准协议你可以把它理解成「给 AI 装插件」的通用插座。NodeJS 自定义 MCP 服务就是用 JavaScript 写一个符合协议的小程序暴露若干工具函数让 Cherry Studio 这类支持 MCP 的客户端调用。它适合谁适合手里有内部 API、数据库查询、天气/翻译/工单系统等私有能力想让 AI 直接调用的开发者也适合想把工具打包发布到 NPM、给团队或社区复用的同学。这篇教程我会带你走完整条链路初始化项目、写 MCP 服务端代码骨架、用 Cherry Studio 本地 stdio 调试、发布到 NPM、再用 npx 方式远程连接最后用 TaoToken 统一 Key/API 通道接入模型侧。全程可复制踩过的坑我会在排障章节标出来。先明确几个关键概念避免后面懵MCP Server你写的 NodeJS 程序通过 stdio标准输入输出或 HTTP 与客户端通信。Tool服务端注册的函数比如query_weather客户端会把它的描述喂给模型模型决定何时调用。Transport传输层本地调试用StdioServerTransport发布后客户端用npx拉起进程本质还是 stdio。Cherry Studio支持 MCP 的桌面客户端配置里填 command args 即可连接。环境要求Node.js 18推荐 20 LTSnpm 9一个 NPM 账号Cherry Studio 最新版。下面开始动手。2. 初始化项目与 package.json 关键字段配置含 MCP 发布必填项新建一个空文件夹打开终端执行mkdir mcp-weather-server cd mcp-weather-server npm init -y这会生成一个默认package.json。但默认内容不能直接用于 MCP 发布需要替换成下面这份。注意bin、files、type三个字段是 MCP 能被客户端拉起的核心{ name: mcptest-lmk, version: 1.0.0, description: 测试mcp服务发布, type: module, bin: { mcptest-lmk: build/weather-server.js }, files: [ build ], scripts: { dev: nodemon build/weather-server.js }, keywords: [ mcp, weather, ai, claude, cline ], author: yourname, license: MIT, dependencies: { modelcontextprotocol/sdk: ^1.7.0, node-fetch: ^3.3.2, zod: ^3.22.4, dotenv: ^16.4.7 }, devDependencies: { nodemon: ^3.0.3 } }逐字段说明这几个是新手最容易配错的字段作用注意点nameNPM 包名全局唯一先到 npmjs.com 搜一下是否被占用type模块规范写module才能用import语法bin命令映射键是命令名值是入口 JS 路径files发布白名单只上传build目录源码不上传version版本号每次 publish 必须递增bin的含义是用户在终端执行mcptest-lmk时实际运行的是build/weather-server.js。Cherry Studio 用npx mcptest-lmk拉起进程走的就是这个映射。安装依赖npm install如果你用国内镜像装依赖更快但发布前必须切回官方源否则npm publish会失败npm config set registry https://registry.npmjs.org/ npm whoaminpm whoami能打印出你的用户名说明登录态正常。没登录先npm login。3. 编写 MCP 服务端代码骨架与工具注册可复制 weather-server.js在项目根目录新建build文件夹里面创建weather-server.js。为什么直接放build因为files只白名单了build发布时只有这个目录会被打包。生产项目更规范的做法是用tsc或esbuild编译到build这里为了聚焦 MCP 链路直接手写。完整代码骨架如下可直接复制#!/usr/bin/env node import { McpServer } from modelcontextprotocol/sdk/server/mcp.js; import { StdioServerTransport } from modelcontextprotocol/sdk/server/stdio.js; import fetch from node-fetch; import { z } from zod; const server new McpServer({ name: weather-mcp, version: 1.0.0 }); const CITY_MAP { 北京: 101010100, 上海: 101020100, 武汉: 101200101 }; server.tool( query_weather, { city: z.string().describe(要查询天气的城市名称如北京、上海) }, async ({ city }) { try { const cityId CITY_MAP[city]; if (!cityId) { return { content: [{ type: text, text: 暂不支持查询${city}。目前支持: ${Object.keys(CITY_MAP).join(、)} }] }; } const url http://aider.meizu.com/app/weather/listWeather?cityIds${cityId}; const response await fetch(url); if (!response.ok) { throw new Error(天气API返回错误: ${response.status}); } const data await response.json(); if (data.code ! 200 || !data.value || !data.value[0]) { throw new Error(获取天气数据失败: ${data.message || 未知错误}); } const weatherData data.value[0]; const realtime weatherData.realtime; let result ${weatherData.city} ${weatherData.provinceName} 实时天气\n; result 温度: ${realtime.temp}℃ (体感: ${realtime.sendibleTemp}℃)\n; result 天气: ${realtime.weather}\n; result 湿度: ${realtime.sd}%\n; result 风向风力: ${realtime.wd} ${realtime.ws}\n; return { content: [{ type: text, text: result.trim() }] }; } catch (error) { console.error(获取天气出错:, error); return { content: [{ type: text, text: 查询天气出错: ${error.message} }], isError: true }; } } ); server.tool( getEmployeeInfo, { emName: z.string().describe(要查询的员工姓名例如张三) }, async ({ emName }) { return { content: [{ type: text, text: 成功进入工具AI识别到的参数: ${emName} }] }; } ); async function main() { const transport new StdioServerTransport(); await server.connect(transport); console.error(MCP服务已启动等待连接); } main();几个关键点解释McpServer是服务端实例name和version会暴露给客户端。server.tool()第一个参数是工具名第二个是参数 schema用 zod 定义第三个是异步处理函数。返回值必须是{ content: [...] }结构type: text是文本结果isError: true表示这次调用失败。注意console.error而不是console.log。因为 stdio 传输下stdout 被协议占用任何console.log都会污染协议流导致客户端解析失败。日志一律走 stderr。#!/usr/bin/env node这行 shebang 必须有否则npx拉起时不知道用 node 执行。4. Cherry Studio 本地 stdio 连接配置与验证请求代码写完后先在本地验证别急着发布。打开 Cherry Studio进入「设置」→「MCP 服务器」→「添加服务器」选择 stdio 类型填入{ mcp本地测试: { name: mcp本地测试, type: stdio, isActive: true, command: node, args: [ D:\\Zszj2025D\\MCPServerTest\\build\\weather-server.js ] } }args里的路径必须是绝对路径指向你刚写的weather-server.js。相对路径在客户端进程里解析会出错这是新手第一个大坑。保存后配置项旁边会亮绿灯下方出现可用工具列表query_weather、getEmployeeInfo说明连接成功。接下来验证请求。在 Cherry Studio 对话界面顶部选择一个支持函数调用的模型右侧带小扳手图标。只有支持 function calling 的模型才能触发 MCP 工具。然后点击菜单栏的 MCP 服务器按钮勾选刚添加的服务开始提问帮我查一下北京的天气模型会识别到query_weather工具自动填入参数city: 北京调用后返回实时天气。如果问「查一下杭州的天气」因为CITY_MAP里没有杭州会返回「暂不支持查询杭州」的提示这正好验证了错误分支。这一步验证通过说明服务端逻辑和 stdio 通信都没问题。接下来发布到 NPM。5. 发布到 NPM 与 npx 远程连接含 401/EPUBLISH 报错排查发布前确认三件事name没被占用、version是新的、registry 指向官方源。npm config set registry https://registry.npmjs.org/ npm whoami npm publish发布成功后到 npmjs.com 搜你的包名或在个人空间能看到刚发布的包。然后测试远程连接。先全局安装npm i mcptest-lmk -g在 Cherry Studio 里新增一个配置和本地不同的是command改成npxargs填bin里定义的命令名{ mcp发包测试: { name: mcp发包测试, type: stdio, isActive: true, command: npx, args: [ mcptest-lmk ] } }保存后同样亮绿灯工具可调用。这里本质还是本地 stdio只是包从 NPM 拉取方便别人一条npx就能用。下面是真实会遇到的报错和排查401 Unauthorizednpm publish时出现说明没登录或 token 过期。执行npm login重新登录或检查npm whoami是否有输出。EPUBLISHCONFLICT / 403版本号已存在。改version为1.0.1再发布。NPM 不允许覆盖已发布版本。local proxy failed / ECONNREFUSEDCherry Studio 连接时出现多半是args路径写错或npx找不到包。先手动在终端跑npx mcptest-lmk看能否启动。如果报「command not found」检查bin字段和files是否包含入口文件。reading choices of undefined模型侧返回结构异常通常是模型不支持 function calling或 MCP 工具返回格式不对。确认返回值是{ content: [{ type: text, text: ... }] }。OAuth / 鉴权失败如果你接的是需要鉴权的模型通道检查 Key 是否有效、Base URL 是否完整。这里可以用 TaoToken 统一管理 Key 和 API 通道避免每个客户端重复配置。工具调用成功但内部报错比如天气 API 挂了返回isError: true。这不是连接问题是工具内部逻辑问题看 stderr 日志定位。6. 用 TaoToken 统一 Key/API 通道接入模型侧MCP 服务解决的是「工具调用」但模型本身从哪来、Key 怎么管是另一条线。如果你在 Cherry Studio、Cline、Claude Code 等多个客户端里都要配模型每个都填一遍 Key 很烦。TaoToken 提供统一的 API 通道把 Base URL 和 Key 收敛到一处。接入方式在 Cherry Studio 的模型设置里把 API 地址填成 TaoToken 的 API 端点Key 填你在控制台生成的 Key。模型 ID 按你实际使用的填。三件套对齐即可Base URLhttps://taotoken.net/apiAPI Key在控制台创建形如sk-xxxModel ID如claude-sonnet-4-5等你实际调用的模型如果你用 Claude Code 或 Codex 这类 CLI 工具配置写在对应的 settings 或auth.json里Base URL 同样指向 TaoToken 的 API 端点。这样 MCP 工具走本地 stdio模型请求走 TaoToken 统一通道两边解耦换模型不用改 MCP 代码。需要生成 Key 的话到控制台的 API Keys 页面创建想先验证模型通不通可以用模型对话页面直接测长期跑编码或 Agent 任务Coding Plan 更划算。接入文档里有各客户端的详细配置示例照着填即可。回到 MCP 本身一个实用技巧把CITY_MAP这类映射表抽到独立配置文件用dotenv读环境变量这样发布到 NPM 的包不硬编码业务数据别人npx时通过环境变量注入自己的配置。另外server.tool()的 description 写得越清楚模型选工具的准确率越高别偷懒写「查询天气」四个字把参数含义、返回内容都描述上。最后一步把build/weather-server.js里的 API 换成你自己的内部接口改个包名npm publish你的第一个自定义 MCP 服务就上线了。