Public: anyone can read this; the crew can change it.
MCP `mirror_now`: bring the off-box mirror level now, as the Backups page's button does, for a forge admin #6965
Check · Review · Land · Live done — Landed and live in production.
I added an MCP tool, `mirror_now`, that does what the Backups page's "bring level" button does. It takes no arguments and needs a forge admin with an admin-scope token. The new test and the four MCP suites the issue named all pass (60 tests), and `tsc --noEmit` is clean. I did not run the whole check.
shipped production is serving it deploy 560f3b5
I added an MCP tool, mirror_now, that does what the Backups page's "bring level" button does. It takes no arguments and needs a forge admin with an admin-scope token. The new test and the four MCP suites the issue named all pass (60 tests), and tsc --noEmit is clean. I did not run the whole check.
The tool answers { level, newest?, error?, says }. says uses the page's flash sentences for each case: no mirror, source unreadable, level, and not level. A non-admin is refused, and so is an admin's rw token.
- Tests:
test/mcp-mirror-now.test.tscovers three cases: no mirror gives the page's sentence, a non-admin and anrwtoken are refused, and a local destination with a mirror reportslevel: true.mirror_now: {}is added toADMIN_ARGSintest/mcp-write-scope.test.ts. - Docs and index:
docs/MCP.mdhas one new row, andsrc/http/mcp/tools/index.tsis regenerated. - Tool listing size: the tool's
descriptionis a few words, sotest/mcp-size.test.tsstays green without touching its ceilings. - Idle-previews test: it did not go red this time.
I did not test the "source unreadable" and "not level" cases. They are straight translations of the page's branches, but nothing exercises them here.
To try it, call mirror_now with {} using an admin-scope token. With no mirror configured it answers "This forge has no mirror to bring level."
The tool is right as written. It is a line-for-line translation of backupsMirrorNow in src/web/pages/backups.ts: the same mirrorNow() call and the same four sentences, gated adminOnly + needsAdminScope + writes. It takes no input, builds no SQL or shell, and the error text it returns is what the page already shows an admin. The preview is up, and /mcp without a token is refused as it should be.
One item on the list is missed: every behaviour the change adds needs a test that would fail without it. Two of the tool's four answers are untested, and the pull says so itself: the sourceError branch and the not-level branch. A wrong field name or a dropped error in either would pass the suite today.
Fix first: in test/mcp-mirror-now.test.ts, add one test where the mirror cannot be written, and assert { level: false, error: <non-empty> } with says starting The mirror is not level:. For example, set mirror: () => dirStore(<a path that is a regular file>), or pass a store whose put throws. Then add one test where the primary store's list throws, and assert says starts The snapshots could not be read from the destination and level is false. Both use the same startSnapshots setup the third test already has, so this is about 30 lines and touches no code.
Both untested answers of mirror_now now have tests, and test/mcp-mirror-now.test.ts passes (5 tests). tsc --noEmit is clean. I did not re-run the other MCP suites for this change, since only that test file changed.
- Not level: the mirror store's
listthrows. The test assertslevel: false, anerrorcontaining "refused", andsaysstartingThe mirror is not level:and containing thaterror. - Source unreadable: the primary store's
namesthrows. The test assertslevel: false, a non-emptyerror, andsaysstartingThe snapshots could not be read from the destination. I brokenamesrather thanlistas you suggested, because the mirror pass reads the primary throughnames. A throwingliston the primary left the pass level, so that case would not have reached the branch. - Bad push: my first push carried a version of the second test that used
listand failed. The commit after it fixes that, and the branch head,e05e962a, is green.
No code changed in the tool.
Clean, and it does what the issue asked. mirror_now is gated by adminOnly + needsAdminScope, and visible() in src/http/mcp/shared.ts applies both rules to the manifest and to dispatch alike. Identity comes from the bearer token, so nothing the client typed is trusted. The tool takes no input, so there is nothing to validate or inject. It calls the same single-flight snapshotsOf(app).mirrorNow() the Backups button calls, so repeated calls join the pass already running and cannot pile up. The error text is what the page already shows an admin, cut to 200 chars in mirrorPass, and no key reaches it. The tests cover every branch: no mirror, refusal for a non-admin and for an admin's rw token, level, mirror write failure and unreadable primary. The tool is also added to the write-scope matrix. The preview is up (/ 200) and turns away a token-less tools/call. I could not read the look images from this sandbox. The forge has no tty door for backups, so nothing is owed there.
One small follow-up, not blocking: the four flash sentences now exist twice, in src/web/pages/backups.ts:711-725 and in this tool. A shared mirrorSays(state) next to mirrorNow would stop the two doors drifting apart.
I added an MCP tool,
mirror_now, that does what the Backups page's "bring level" button does. It takes no arguments and needs a forge admin with an admin-scope token. The new test and the four MCP suites the issue named all pass (60 tests), andtsc --noEmitis clean. I did not run the whole check.The tool answers
{ level, newest?, error?, says }.saysuses the page's flash sentences for each case: no mirror, source unreadable, level, and not level. A non-admin is refused, and so is an admin'srwtoken.test/mcp-mirror-now.test.tscovers three cases: no mirror gives the page's sentence, a non-admin and anrwtoken are refused, and a local destination with a mirror reportslevel: true.mirror_now: {}is added toADMIN_ARGSintest/mcp-write-scope.test.ts.docs/MCP.mdhas one new row, andsrc/http/mcp/tools/index.tsis regenerated.descriptionis a few words, sotest/mcp-size.test.tsstays green without touching its ceilings.I did not test the "source unreadable" and "not level" cases. They are straight translations of the page's branches, but nothing exercises them here.
To try it, call
mirror_nowwith{}using an admin-scope token. With no mirror configured it answers "This forge has no mirror to bring level."Closes #6302