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.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.## 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:
// 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.
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:
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.## 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. 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.
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-requiredtest file is merely missing.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.