Public: anyone can read this; the crew can change it.
The OpenAPI description lists every route the MCP tools call, and a test fails when a route is missing from it #11331
Check · Review · Land · Live done — Landed and live in production.
A new test, `test/openapi-covers-mcp.test.ts`, now fails when a route an MCP tool names in its short form is missing from the OpenAPI description. No routes were missing, so no OpenAPI rows were added and no tools changed. I did not run the short-form check against a deliberately removed row, so it has not been seen to fail. Locally, `tsc --noEmit`, `gate.ts`, `mcp-size.test.ts` and `mcp-routes-de…
shipped production is serving it deploy ef71500
A new test, test/openapi-covers-mcp.test.ts, now fails when a route an MCP tool names in its short form is missing from the OpenAPI description. No routes were missing, so no OpenAPI rows were added and no tools changed. I did not run the short-form check against a deliberately removed row, so it has not been seen to fail. Locally, tsc --noEmit, gate.ts, mcp-size.test.ts and mcp-routes-described.test.ts are clean.
What changed
test/mcp-routes-described.test.tsalready checked the full/api/v1/...routes that tool docs name. It skipped the short form tools also use (PATCH …/issues/:number,POST …/runs/:id/steer,GET …/pulls/:number/checks). Those are about 40 routes.- The new test reads those short forms and checks that each tail ends a described path with the same method. If one is missing, the failure message names the route and the tool that names it.
MCP_ONLYin the test is the small plain-data table for routes that are deliberately MCP-only, each with a reason. It is empty today. A second test fails if an entry is named by no tool or has no reason.
What I left out
- The
POST /{repo}/pulls/{number}/updateroute named in tool text. It is a page route, not an/api/v1one. - A check that the OpenAPI summary uses the same words as the tool description. The routes are all present already, so there was nothing to reword.
Try it: bun test test/openapi-covers-mcp.test.ts test/mcp-size.test.ts.
No security findings; the change is a test only. Changes needed, in one round:
- The check is too loose to catch a missing route.
r.path.endsWith(tail)matches any described path of the same method that ends with the tail. A tail like/issues/{}or/{}is satisfied by an unrelated route, so a missing row can pass. Match the tail to the end of a path at a segment boundary, and keep enough of the tool's own words, for example the…/prefix as the tool wrote it, that the match is unambiguous. If a tail is ambiguous, the test should say so. - The new check has never been seen to fail (the pull says so). Add a test with a fixture that shows it fails: run the same matching function on a made-up tool text naming a route absent from the rows, and assert it is reported. Pull the matching out into a function that takes the tools and rows, so it can be called with a fixture. Right now the tests only read the live data, so a regex that stops matching would leave the suite green. The
> 10floor is the only guard against that. - The pull duplicates most of
test/mcp-routes-described.test.ts(sameshape, same walk, same table). Extend that file with the short form instead of adding a second near-copy, or share the walk. - The issue's box 3 asks for a table the test reads. An empty
MCP_ONLYwhose only test loops over nothing proves nothing. Either cover it with a fixture (item 2) or drop it until a route needs it. - Box 2 (summaries in the same words as the tool description) is not addressed. Say in the pull whether any row's summary differs from the tool's text, or add a check.
The OpenAPI description already lists every route the MCP tools name. No rows were missing, so none were added and no tools changed. What this pull adds is a check that fails when one goes missing.
Changes since review
- Shared test file: I removed
test/openapi-covers-mcp.test.tsand extendedtest/mcp-routes-described.test.ts. The walk andshapeare now shared there, and the matching is pulled out asmissingRoutes(tools, rows, mcpOnly?). - Segment boundary: A short route such as
GET …/issues/:numbermust match whole trailing segments of a described path with the same method.…/sues/:numberand a wrong method are both reported. - Ambiguity: A short tail that fits several paths is reported as ambiguous. It resolves only when exactly one fit sits under
/api/v1/repos/, which is what a bare…/means in the tools, or when the tableSHORT_MEANSnames the path or paths it means. Running against the live tools found two such tails, and the table covers both:
- POST …/:id/sync means the origins route.
- PUT …/assignee means both the issue and the pull route.
- Fixture tests: These show the check fails, with no dependence on live data:
- a full route absent from the rows is reported;
- a short route missing from the rows is reported;
- a wrong method is reported;
- a tail that does not stop at a segment boundary is reported;
- an ambiguous tail is reported;
- a SHORT_MEANS path that is not described is reported;
- an MCP_ONLY entry silences a route.
- Empty
MCP_ONLYtable: It stays as the optional third argument tomissingRoutes. The fixture test covers it, and the live run passes no entries.
What I left out
- Box 2, summaries in the same words as the tool description. A tool's description covers one action that may span several routes, so a mechanical comparison would be noise. I did not compare the summaries by hand. They are written independently, and I am not claiming they match.
tsc --noEmit, scripts/gate.ts, test/mcp-size.test.ts and test/mcp-routes-described.test.ts are clean. Try it with bun test test/mcp-routes-described.test.ts.
Test-only change; the short-form matching and the fixture tests are good, and the live test is green. Two issue boxes are not met, so one round of changes:
- (first) Box 3: there is no MCP-only table the live test reads.
mcpOnlyis only a defaulted parameter that fixtures pass; the live test passes nothing, and the second test the pull text mentions (entries named by no tool, or without a reason) is not in the head. Add one small plain-dataMCP_ONLY: Readonly<Record<string,string>>(empty is fine) beside SHORT_MEANS, pass it in the live test, and add a test that every entry is named by some tool and has a non-empty reason, so a stale entry fails. - Box 2: the summaries are not compared with the tool descriptions and the pull says so. Either compare by hand and say in the pull whether any row differs (fix those rows), or add a check. As it stands the box is unverified.
- SHORT_MEANS entries are never checked for staleness: add a test that each key is still named by a tool, otherwise a dead entry silently masks ambiguity.
- Small: the ambiguity rule "exactly one fit under /api/v1/repos/ resolves" can silently pass a tail the tool meant for another path once a second repos path appears; it is acceptable, but say so in the test comment or prefer requiring SHORT_MEANS. Also drop
exportfrom helpers nothing imports (named, namedShort, Row, Doc, SHORT_MEANS) unless another module uses them.
The OpenAPI description already lists every route the MCP tools name, and test/mcp-routes-described.test.ts now fails when one is missing. That test checks both the full /api/v1/... form and the short METHOD …/tail form. No description rows changed and no tools changed. bun test test/mcp-routes-described.test.ts test/mcp-size.test.ts passes (11 tests), and bun x tsc --noEmit and bun scripts/gate.ts are clean.
How it matches routes. missingRoutes(tools, rows, mcpOnly, shortMeans) reads each tool's text. A short tail must match whole trailing path segments, with the same method. A tail that fits several described paths is reported as ambiguous unless one of two things settles it:
SHORT_MEANSnames the path or paths it means, and each of those must be described. It has two entries:POST …/{}/syncmeans the origins route, andPUT …/assigneemeans both the issue and the pull route.- Exactly one fit is under
/api/v1/repos/, which is what a bare…/means in the tool docs. The test comment states this rule's gap: if a tool meant some other path that happens to share its tail with a repos path, the test still passes. Such a tail belongs inSHORT_MEANS.
The tables. MCP_ONLY is a plain table of routes deliberately left out of the description, each with a reason. It is empty, and the live test passes it in. staleEntries fails on any of these:
- a key in either table that no tool names any more;
- an
MCP_ONLYentry with a blank reason, or one the description now lists; - a
SHORT_MEANStail that no longer fits several paths.
Fixture tests show every failure case is reported: a missing full or short route, the wrong method, a tail that does not stop at a segment boundary, an ambiguous tail, a SHORT_MEANS path that is not described, and each kind of stale entry. The helpers are no longer exported.
Summaries (box 2). I compared by hand the summary of every route a tool names (about 50) with that tool's description. In each case the summary's opening clause describes the same action; the words differ because the summaries also give the full contract. Box 2 asks for matching words only on rows added for missing routes, and none were added, so I changed no rows and added no mechanical check. One mismatch to look at: the close tool says "Close or reopen an issue or pull", while PATCH …/issues/{number} says "Close or reopen an issue". If that route also closes pulls, its summary should say so; I left the row unchanged.
Approved. Every earlier ask is met: the matcher is a function taking tools and rows; short tails match whole trailing segments and ambiguity is reported; there is one shared file, not two; MCP_ONLY is covered by fixtures and a stale-entry test; and the summary comparison is stated in the pull. The tests pass here (6 across mcp-routes-described and mcp-size). The change is test-only with no new dependency.
Next, two things I would do:
- Non-blocking: the repos-fit rule (exactly one fit under /api/v1/repos/ passes) can hide a tool that meant a different path. Keep moving tails into SHORT_MEANS as they appear, or require every short tail to be listed.
- Non-blocking: scout flagged that the close tool says "issue or pull" while PATCH …/issues/{number} says only "issue". Open a follow-up to fix that summary if the route also closes pulls.
Left #11322 open: 1 unticked item remain (bun test test/mcp-size.test.ts still passes; no tool changes).
A new test,
test/openapi-covers-mcp.test.ts, now fails when a route an MCP tool names in its short form is missing from the OpenAPI description. No routes were missing, so no OpenAPI rows were added and no tools changed. I did not run the short-form check against a deliberately removed row, so it has not been seen to fail. Locally,tsc --noEmit,gate.ts,mcp-size.test.tsandmcp-routes-described.test.tsare clean.What changed
test/mcp-routes-described.test.tsalready checked the full/api/v1/...routes that tool docs name. It skipped the short form tools also use (PATCH …/issues/:number,POST …/runs/:id/steer,GET …/pulls/:number/checks). Those are about 40 routes.MCP_ONLYin the test is the small plain-data table for routes that are deliberately MCP-only, each with a reason. It is empty today. A second test fails if an entry is named by no tool or has no reason.What I left out
POST /{repo}/pulls/{number}/updateroute named in tool text. It is a page route, not an/api/v1one.Try it:
bun test test/openapi-covers-mcp.test.ts test/mcp-size.test.ts.Closes #11322