基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
AIHT
内容
精选全部 AI 动态热点榜AI 日报收藏
分类
模型行业编程产品技术观点开源
更多
Agent 接入
返回首页开发者导航
AIHT
精选 / 资讯详情
8.0
热点
AI SCORE
技术实践2026-10-31 20:23

119 行 Node.js 拆解 MCP 服务最小实现

dev.to · AI#MCP#Node.js#协议
Editor brief · 编辑速览

作者用单文件、零外部依赖实现 stdio MCP 服务,为草稿事实核验和链接检查提供工具接口。文章拆解 JSON-RPC 消息处理、初始化与工具方法、通知静默规则,以及标准输出和日志的分离要求。

文章思维导图
Knowledge map
拖拽缩放
AI deep read

AI 深度解读

把发布前的两项检查接进 MCP,需要多大的工程?作者给出的答案是:一个文件、119 行代码,只导入 node:fs 和 node:readline。它检查草稿里的数字是否能在事实文件中找到,以及链接能否正常访问。

这篇最有价值的地方,是把 stdio MCP 服务端需要处理的消息、错误和测试拆开讲清楚。作者还记录了一次协议实现错误。对想把现有 Node 脚本接入 AI 编程工具的开发者,这比照抄一个服务端模板更有参考意义。

两个工具,服务端究竟要处理什么

作者的需求很窄:让 MCP 客户端调用发布检查。check_numbers 接收草稿和 facts.md,支持传入文本或文件路径;check_links 接收草稿,检查链接。

在这个仅提供工具、采用标准输入输出传输(stdio)的实现中,服务端通过 stdin 逐行读取 JSON-RPC 2.0 消息,再向 stdout 逐行输出 JSON 回复。日志要写入 stderr,因为 stdout 是协议通道,混入一条调试日志,也会让客户端收到非 MCP 内容。

消息分发集中在四个方法:initialize、tools/list、tools/call 和 ping。初始化返回协议版本、capabilities: { tools: {} },以及名称为 publish-gate、版本取自 VERSION 的 serverInfo;列举工具返回 TOOLS;调用工具执行检查;心跳返回 {}。

还有一个容易漏掉的边界:没有 id 的消息属于通知,例如 notifications/initialized,服务端不能回复。

作者用 readline 驱动读循环,通过 JSON.parse 解析消息,空行或解析失败的行直接忽略。这描述的是他们的简化实现,不能据此推导出完整的协议异常处理要求。

初始化也有捷径:直接回传客户端的 params?.protocolVersion,没有提供时使用 2025-06-18。作者承认,依赖更新功能的服务端应该正式协商版本。回传一个版本号,并不能证明实现支持那个版本的全部要求。

请求有问题,和工具执行失败,要分开返回

作者第一版把所有错误都装进 isError: true 的工具结果,包括工具名不存在、调用时没传草稿。这是他们明确修正过的协议问题。

两类错误的区别在于:请求是否已经构成一次有效的工具调用。工具名未知、参数不合法,属于协议错误,应该返回 JSON-RPC error;请求有效,但执行过程中失败,才返回带 isError: true 的正常工具结果。

下面这段代码先拦截错误调用,再区分执行失败:

const invalidParams = (message) => Object.assign(new Error(message), { code: -32602 });

// 在 callTool 中:执行前拒绝错误调用
if (TOOLS.some((t) => t.name === name) === false) throw invalidParams(`unknown tool: ${name}`);
if (args.draft_text == null && args.draft_path == null) throw invalidParams("give draft_text or draft_path");

// 在 handle 的 case "tools/call" 中:
try { return await callTool(params?.name, params?.arguments); }
catch (e) {
  if (e.code === -32602) throw e;                       // 协议错误
  return { content: [{ type: "text", text: `error: ${e.message}` }], isError: true };
}

关键是 if (e.code === -32602) throw e:它让参数错误继续交给外层读循环,转换为 JSON-RPC error,而不是被包装成工具执行失败。

修正后,未知工具和缺少草稿返回 -32602,未知方法返回 -32601;draft_path 指向无法读取的文件时,返回以 error: 开头、带 isError 的工具结果。外层错误兜底码是 -32603,实际返回仍取决于异常在哪一层被捕获。

请求没成立,与请求执行失败,是两个不同的接口契约。

至于检查发现不合格内容,工具输出的是纯文本:通过时为 PASS,不通过时为 FAIL (n),随后逐行列出问题。作者认为这便于模型阅读,但没有提供效果对照,不能把它当成已经验证的最佳格式。

测试协议,不必先启动一个 MCP 客户端

stdio 的一个好处是可以直接从 shell 输入消息。作者用 printf '%s\n' 依次发送 initialize、不带 id 的 notifications/initialized、tools/list,再通过管道交给 node server.js。

这样就能观察客户端实际收到的内容,包括通知是否保持静默、请求是否获得对应回复。

异步处理还会让回复乱序。作者在测试中看到,请求 4 的未知方法错误,先于请求 3 调用 check_numbers 的结果返回。这符合协议:客户端按 id 匹配请求,而不是按到达顺序。只有工具业务确实要求顺序执行时,才需要额外排队。

测试被分成两部分。服务端导出 checkNumbers、checkLinks 供单元测试使用,其中 checkLinks 接收可注入的 fetchFn,所以 node --test 不需要真实网络。另有 stdio 测试覆盖未知工具、缺少草稿、文件不可读和未知方法。

作者报告共有 5 个测试,在 Node v26.7.0 上通过。客户端方面,只验证了 Claude Code,claude mcp list 显示 Connected;其他 stdio 客户端尚未实测。

仓库尚未发布到 npm,原文给出的接入方式如下:

git clone https://github.com/skibuff22/agent-ops-kit-lite
claude mcp add publish-gate -- node "$PWD/agent-ops-kit-lite/mcp/publish-gate/server.js"

要看清第二行:Claude Code 启动的命令是 node,参数是 server.js 的路径。这里接入的是一个本地进程。

我的判断

我认可作者按需求控制实现范围的选择。已有确定性检查函数,只想向 MCP 暴露两个工具,直接处理协议能让边界清楚,也省去 SDK 的依赖树。

但 119 行是这个项目的结果,不是一般团队应当追求的指标。作者没有给出手写协议与官方 SDK 在开发时间、维护成本、兼容性上的对照数据。采用 SDK 还是自行实现,仍要看团队愿意承担多少协议维护工作。

更需要打问号的是兼容范围:测试通过发生在 Node v26.7.0,连接验证发生在 Claude Code。它们不足以证明其他 Node 版本、其他客户端或更多协议能力都能正常工作。

数字检查也有明确边界。数字能在 facts.md 中找到,不等于事实文件正确,更不等于数字在句子里的使用正确;链接能访问,也不等于目标页面支持草稿中的论断。

原文同时带有产品推广。MIT 开源代码旁的付费 Publish Gate 标价为一次性 $19,增加风格检查、逐条论断的 LLM 检查、SHA-256 锁,以及拒绝发布检查后被修改草稿的发布器。作者称其中的 LLM 检查采用失败即拒绝放行的方式,但没有给出这些能力的对照评测。文末那句建议屏蔽或举报他人的文字与技术内容无关,应当剔除。

能拿来做什么

  • 把已有 Node 检查脚本拆成可直接测试的函数,再包一层 MCP 调用入口,先让草稿检查这类范围清楚的任务进入 AI 编程流程。
  • 为工具接口分别验证参数错误、执行失败和检查不通过,避免模型或客户端把三种状态混在一起处理。
  • 测试链接检查时注入 fetchFn,稳定模拟请求结果;另外通过 stdio 测试验证消息格式、通知静默和错误码。
  • 接入自己的客户端前,先确认 Node 版本和实际连接结果;若开始使用更多 MCP 能力,就重新评估版本协商和官方 SDK。
Original source

本文由 AI 通读原文后重新编写,观点部分为站内判断,细节以原文为准,来源 dev.to · AI,原文版权归原作者所有。

阅读原文
上一篇
用进程内熔断拦住大模型失控账单
下一篇
让AI重写旧代码前,先设行为等价门禁

资讯速览

dev.to · AI · 2026-10-31 20:23

AI 评分 8.0 / 10

相关标签

#MCP#Node.js#协议

继续阅读

  • 用双时间轴防止知识图谱旧事实覆盖新状态
  • 用进程内熔断拦住大模型失控账单
  • 让AI重写旧代码前,先设行为等价门禁
  • AI写的测试全绿,如何验证需求真被满足
  • 把Twist讨论接入代理记忆,检索团队决策