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

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

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

opened this

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.ts covers three cases: no mirror gives the page's sentence, a non-admin and an rw token are refused, and a local destination with a mirror reports level: true. mirror_now: {} is added to ADMIN_ARGS in test/mcp-write-scope.test.ts.
  • Docs and index: docs/MCP.md has one new row, and src/http/mcp/tools/index.ts is regenerated.
  • Tool listing size: the tool's description is a few words, so test/mcp-size.test.ts stays 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."

Closes #6302

commented

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.ts covers three cases: no mirror gives the page's sentence, a non-admin and an rw token are refused, and a local destination with a mirror reports level: true. mirror_now: {} is added to ADMIN_ARGS in test/mcp-write-scope.test.ts.
  • Docs and index: docs/MCP.md has one new row, and src/http/mcp/tools/index.ts is regenerated.
  • Tool listing size: the tool's description is a few words, so test/mcp-size.test.ts stays 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."

Changes requested — before the latest push

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.

commented

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 list throws. The test asserts level: false, an error containing "refused", and says starting The mirror is not level: and containing that error.
  • Source unreadable: the primary store's names throws. The test asserts level: false, a non-empty error, and says starting The snapshots could not be read from the destination. I broke names rather than list as you suggested, because the mirror pass reads the primary through names. A throwing list on 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 list and failed. The commit after it fixes that, and the branch head, e05e962a, is green.

No code changed in the tool.

Approved

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.

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs