Your MCP server will drift from your app. Here's a build gate that stops it.

Your MCP server will drift from your app. Here’s a build gate that stops it.

你的 MCP 服务器会与应用逻辑产生偏差,这里有一个构建门禁可以阻止它。

When I added an MCP server to RyTask (an open-source project tracker), I made one promise: anything a person can do in the UI, an AI agent can do over MCP. No read-only second-class agent access. Full parity. 当我为 RyTask(一个开源项目追踪器)添加 MCP 服务器时,我许下了一个承诺:人类在 UI 上能做的任何事情,AI 智能体也必须能通过 MCP 完成。拒绝只读的“二等公民”式访问,必须实现完全对等。

The problem with promises like that is they rot. You ship a new feature, wire it into the UI and the REST API, and forget the MCP tool. Three sprints later your “100% parity” is 86% parity and you don’t know it. So I made parity a thing CI can prove, and fail the build over. 这类承诺的问题在于它们会“腐烂”。当你发布一个新功能,将其接入 UI 和 REST API 后,往往会忘记更新 MCP 工具。三个冲刺周期后,你的“100% 对等”可能就变成了 86%,而你对此一无所知。因此,我将“对等性”变成了一个 CI 可以验证的指标,并让它在不满足时中断构建。

The shape of the system: Every business module in RyTask declares the capabilities it owns and the MCP tools that expose them, in one file: 系统架构:RyTask 中的每个业务模块都在同一个文件中声明其拥有的能力以及暴露这些能力的 MCP 工具:

// work-items/module.testplan.ts
export const workItemsTestPlan = {
  capabilities: ['create', 'update', 'assign', 'comment', 'logTime', ...],
  mcpTools: ['create_work_item', 'update_work_item', 'assign_work_item', ...],
}

A registry aggregates every module’s tools. The MCP server is built from that same registry — there’s no separate hand-maintained list to drift. 一个注册中心会汇总所有模块的工具。MCP 服务器正是基于该注册中心构建的——不存在需要手动维护、容易产生偏差的独立列表。

The gate check:mcp-parity walks every capability and asserts a matching tool exists, and every tool maps back to a real capability. One missing pair fails CI: 门禁 check:mcp-parity 会遍历每一项能力,断言是否存在匹配的工具,并确保每个工具都能映射回真实的能力。只要有一对缺失,CI 就会报错:

const missingTool = capabilities.filter(c => !toolFor(c))
const orphanTool = tools.filter(t => !capabilityFor(t))

if (missingTool.length || orphanTool.length) {
  console.error('MCP parity broken:', { missingTool, orphanTool })
  process.exit(1)
}

It currently reports 49/49. The day I add a “duplicate project” feature and forget the tool, the build goes red and tells me exactly which tool is missing. Parity stopped being a docs claim and became an invariant. 目前它显示 49/49。当我某天添加“复制项目”功能却忘记添加对应工具时,构建会变红,并准确告诉我缺失了哪个工具。对等性不再仅仅是文档中的声明,而变成了一个不变的约束。

Why this matters beyond my project: AI agents are becoming real users of software. If your agent surface is a hand-curated subset of your product, it will always lag the UI, and your users’ agents will hit walls the humans don’t. 为什么这不仅仅对我的项目重要:AI 智能体正在成为软件的真实用户。如果你的智能体接口只是产品功能中人工挑选的子集,它永远会滞后于 UI,导致用户的智能体遇到人类用户不会遇到的障碍。

Treating the agent as a first-class client — held to the same coverage by the same CI that guards everything else — is, I think, where a lot of tools are going to end up. 将智能体视为“一等公民”客户端,并让它受到与其他功能相同的 CI 覆盖率约束,我认为这将是许多工具最终的发展方向。

RyTask does the same trick for a few other invariants: module boundaries (you can’t import another module’s internals), multi-tenancy (every tenant-scoped query is auto-constrained to the caller’s org at the repository layer, and tests assert cross-tenant isolation against a real Postgres), and a “closed testing” gate that fails the build if a declared-required test file is merely missing. RyTask 对其他几个不变约束也采用了同样的技巧:模块边界(禁止导入其他模块的内部实现)、多租户(每个租户范围的查询在存储库层自动限制为调用者的组织,且测试会针对真实的 Postgres 数据库断言跨租户隔离性),以及一个“封闭测试”门禁,如果仅仅是缺少了声明必须存在的测试文件,构建就会失败。

It’s all open source (AGPL-3.0), built solo. If you want to see the gates in action, the repo’s here: github.com/ali-maher-m/RyTask. I’d love feedback on the approach — especially from anyone else building MCP servers for real products. 这一切都是开源的(AGPL-3.0),由我个人开发。如果你想看看这些门禁是如何运作的,仓库地址在这里:github.com/ali-maher-m/RyTask。我很乐意听取对这种方法的反馈——特别是来自其他正在为真实产品构建 MCP 服务器的朋友。