Frontmatter
| title | chore(ai): retire manage_database_backup MCP tools (#10132) |
| author | tobiu |
| state | Merged |
| createdAt | Apr 20, 2026, 9:21 PM |
| updatedAt | Apr 20, 2026, 9:41 PM |
| closedAt | Apr 20, 2026, 9:41 PM |
| mergedAt | Apr 20, 2026, 9:41 PM |
| branches | dev ← agent/10132-reduce-mcp-backup-surface |
| url | https://github.com/neomjs/neo/pull/10133 |

Input from Claude Opus 4.7 (Claude Code):
❋ # PR Review Summary
Status: Comment (self-review — human approval required per AGENTS.md §7)
Self-Review Opening: Self-review of #10132. I chose to revert my own #10131 yaml addition + retire the pre-existing MC tool in a single cleanup pass after tobi caught that the KB yaml surface was never wired into
toolService.mjsserviceMapping. Two-PRs-from-one-ticket is the cost of not runningpr-reviewbefore #10131 handoff — worth flagging as the meta-lesson.
📊 Evaluation Metrics
[ARCH_ALIGNMENT]: 92 — Clean reduction aligned with the #9903 script-over-tool precedent and theai/services/migration direction. Minor ding: JSDoc now embeds ticket numbers (#9903,#10132) as architectural anchors, which ages poorly when tickets get re-numbered or archived. Fat-ticket repos tolerate this; worth tracking as a general hygiene concern.[CONTENT_COMPLETENESS]: 90 — Class + method JSDoc symmetrically updated; test rename carries an explanatory comment about the no-match passthrough path; PR body has empirical validation data + pre-checked AC boxes. Loses points for not documenting thatMemory_DatabaseService.manageDatabaseBackupcould use a corresponding JSDoc refresh (I didn't touch MC's class docstring in this PR — only removed registration — so the MC JSDoc may still reference MCP-tool framing in places I didn't audit).[EXECUTION_QUALITY]: 80 — Main blind spot: I observed a parallel-worker race on these specs (undefinedKB_ChromaManager.getKnowledgeBaseCollectionduring concurrentafterAllhooks) and flagged it as "pre-existing" rather than fixing it in-scope. A one-linetest.describe.configure({ mode: 'serial' })on the three affected specs would prevent CI flakes at the next run — cheap fix I should have included. Honest self-critique: deferring this to "follow-up" transfers risk to CI runners without explaining why.[PRODUCTIVITY]: 95 — Ticket #10132 filed as Fat Ticket (five-stage challenge chain), branched, edited, tested, PR'd in one contiguous pass (~25 min). All 5 AC items empirically verified pre-commit.[IMPACT]: 50 — Net −153 lines, one MCP tool slot reclaimed. Real but small. Strategic weight comes from alignment with theai/services/migration direction, not from the raw diff size.[COMPLEXITY]: 35 — Mechanical yaml block removal + one-lineserviceMappingdelete + JSDoc rewrite + test rename. Low cognitive load. The only non-trivial call was whether to tighten the test assertion (.toThrow(/Unknown action: import/)vs. the prior bare.toThrow()) — tightening trades brittleness against informativeness, which is a real judgment call but scope-adjacent to the ticket.[EFFORT_PROFILE]: Quick Win — high ROI (tool slot reclaim + migration-direction alignment + pre-existing-drift elimination), low complexity, clean tests. Textbook Quick Win.
🕸️ Context & Graph Linking
- Target Epic / Issue ID: Resolves #10132
- Related Graph Nodes: implementation session
5a521819(both #10131 and #10132 authored here), parent PR #10131 (MERGED; introduced the unused KB yaml surface this PR reverts), precedent #9903 (script-over-tool), test infra #10041 (McpServerToolLimits.spec.mjs), architectural direction:getFullDescriptionexploration +ai/services/migration (both noted by @tobiu 2026-04-20)
🧠 Graph Ingestion Notes
[KB_GAP]: In the #10131 self-review I wrote "SDK boundary is load-bearing... gives us Zod validation for free". Within one PR cycle that framing flipped: removing the openapi entries meantmakeSafe's no-match passthrough kicks in, Zod is skipped, and the dispatcher's manual throw becomes the rejection path. The "Zod for free" framing was overcautious for single-writer script paths — Zod is a feature only when the operation is actually registered as an MCP tool. Lesson to embed: be precise about wheremakeSafewrapping happens (MCP-registered operations with matching openapi ops), not where it could happen (any service method). The SDK is the boundary; wrapping is conditional.
[KB_GAP]: Yaml-without-registration is a dangerous architectural drift vector. My #10131 added the KB/db/backup/managepath toopenapi.yamlwithout adding it toknowledge-base/services/toolService.mjsserviceMapping. That made it SDK-visible (viamakeSafe) but not MCP-exposed. If tobi hadn't caught it in post-merge review, a future agent seeing the yaml might have "completed the registration" intoolService.mjswithout reviewing whether MCP exposure was actually desired — compounding the drift rather than cleaning it. Moral: never land yaml surfaces without the correspondingtoolService.mjsentry, OR don't land the yaml. Half-wired state is worse than either full state.
[TOOLING_GAP]: Playwright parallel-worker interleaving observed on backup specs — intermittentTypeError: Cannot set properties of undefinedduringafterAllhooks that restore singleton mutations onKB_ChromaManager.getKnowledgeBaseCollection/Memory_StorageRouter.getMemoryCollection. Per the "symmetric spec cleanup" lesson (beforeEach + afterEachinstead ofbeforeAll + afterAllfor cross-singleton mutations), these specs need either worker-scoped fixtures OR explicittest.describe.configure({ mode: 'serial' }). I did NOT fix this in scope; that's a deliberate punt that transfers risk to CI runners.
[RETROSPECTIVE]: Runningpr-reviewAFTER merge (as I did on #10131) means you can only file follow-up cleanup PRs — you cannot catch half-wired state before it lands. Runningpr-reviewBEFORE handoff would have caught the "yaml without serviceMapping registration" smell, and #10132 would never have been necessary. The two-PRs-from-one-ticket pattern visible here is a cost signal: for architectural-change PRs especially, the reflection pass must precede handoff.
[RETROSPECTIVE]: Script-over-tool is the default architectural choice for operator-facing infrastructure; MCP tools are for on-demand agent-triggered operations with meaningful latency constraints. The cognitive cost of two paths (MC tool + backup.mjs script) is real even when both work — agents have to choose, and the choice is not obvious from tool descriptions alone.
📋 Required Actions
None are merge-blockers. The following are self-identified gaps:
- Consider folding into this PR before merge: add
test.describe.configure({ mode: 'serial' })to the three affected backup specs (DatabaseService.backup.spec.mjs,DatabaseService.backupPath.spec.mjs,backup.spec.mjs). One line per file, prevents the observed parallel-worker race from biting CI. Low risk, high value — arguably should have been in-scope.- Follow-up ticket (non-blocking): audit
Memory_DatabaseServiceclass + method JSDoc for any residual "MCP tool" framing now that the MC tool is retired. I did not touch MC's class docstring in this PR — only removed registration — so the symmetric JSDoc cleanup is outstanding.- Follow-up ticket (non-blocking):
manage_database(Chroma lifecycle) 1161-char description — defer togetFullDescriptionexploration as agreed.Closing reflection: This PR exists because the previous PR's self-review ran post-merge instead of pre-handoff. The "two-PRs-from-one-ticket" cost is not a failure mode to hide — it's the feedback signal telling me where to place
pr-reviewin the workflow next time. For the next architectural-change PR, invokepr-reviewbeforegh pr create, not aftergh pr merge.
Summary
Follow-up cleanup to #10131 per @tobiu's post-merge review. Reclaims one MCP tool slot from the ~80-tool budget by retiring
manage_database_backupfrom both MC (pre-existing) and KB (yaml-only surface I added in #10131 that was never wired intoolService.mjs).Rationale —
npm run ai:backup(shipped in #10131) covers all five persistent AI subsystems atomically with JSONL artifacts in a unified bundle layout. The MC MCP tool only covered memories/summaries/graph and had no atomic-bundle semantics, so it's strictly less capable than the script. Per the #9903 precedent ("protect the 100-tool MCP limit while allowing Frontier models to compress their own Action Logs") the script-over-tool path is the right architectural direction for operator-facing backup operations.Zero functional regression — the
KB_DatabaseService.manageDatabaseBackupandMemory_DatabaseService.manageDatabaseBackupservice methods remain callable via theai/services.mjsSDK boundary.makeSafeapplies Zod wrapping only when a matching openapi operation is found; with the openapi entries removed,makeSafeuses its no-match passthrough, forwarding raw args to the underlying methods.backup.mjscontinues to work as-is.What changed
MC retirement
ai/mcp/server/memory-core/openapi.yaml— removed/db/backup/managePOST +BackupActionResponseschemaai/mcp/server/memory-core/services/toolService.mjs— removedmanage_database_backupentry fromserviceMapping(line 26)KB revert (undoing #10131's yaml-only addition)
ai/mcp/server/knowledge-base/openapi.yaml— removed/db/backup/managePOST +BackupActionResponseschema I added in #10131ai/mcp/server/knowledge-base/services/DatabaseService.mjs— updated class@summaryBackup Surface paragraph + method JSDoc to reflect the SDK-only reality (no more "registered in openapi.yaml / Zod-wrapped via makeSafe" claims; replaced with explicit script-over-tool rationale and #9903/#10132 cross-refs)Test
test/playwright/unit/ai/mcp/server/knowledge-base/services/DatabaseService.backup.spec.mjs— renamed the third test from "rejects unsupported actions at the SDK Zod validation boundary" to "rejects unsupported actions at the dispatcher layer" and tightened the assertion to.rejects.toThrow(/Unknown action: import/)so it verifies the specific rejection path (no-match passthrough → dispatcher manual throw) instead of any generic errorLive validation
Before committing this cleanup I ran
npm run ai:backupfrom main against the real graph / KB / MC state to establish empirical confidence that the script path is ready to fully replace the MCP tool:Wall clock: 6s Exit code: 0 { "bundleRoot": "/Users/Shared/github/neomjs/neo/.neo-ai-data/backups/backup-2026-04-20T18-59-35.772Z", "subsystems": { "kb": { "message": "Export complete. Exported 10850 knowledge base chunks." }, "mc": { "message": "Export complete. Exported 8204 memories, 794 summaries, and 0 graph elements." }, "graph": { "message": "Export complete. Exported 0 memories, 0 summaries, and 369 graph elements." }, "concepts": { "copied": 2 }, "trajectories": { "copied": 1 } } }wc -longraph/graph-backup-*.jsonlconfirms 369 real newlines — the\nfix from #10131 is live and the graph backup is now properly restorable JSONL (vs. the pre-#10131 single-line malformed files).Testing
11 Playwright specs pass under
--workers=1(single-worker serialization):DatabaseService.backup.spec.mjs(KB)DatabaseService.backupPath.spec.mjs(MC)backup.mjsorchestratorpeer-architecture.spec.mjsThe pre-existing
McpServerToolLimits.spec.mjsfailure persists and is not caused by this PR — diagnostic identified the culprit asmanage_database(the Chroma lifecycle tool) at 1161 chars, notmanage_database_backup(which was under 400 chars when it existed). That failure is deferred to thegetFullDescriptionexploration — systemic tool-description-size reduction is the right substrate for fixing it, not this single-tool retirement.A parallel-worker race I observed when running multiple backup specs concurrently (intermittent undefined on
KB_ChromaManager.getKnowledgeBaseCollectionacross afterAll hooks) is also pre-existing and tangent to this PR. Single-worker serialization sidesteps it. Flagged for follow-up.Architectural Direction
This PR makes incremental progress toward two larger architectural directions @tobiu noted on 2026-04-20:
getFullDescriptionexploration — lean advertised tool descriptions + on-demand full-prose fetcher. Defers context cost from discovery-time to invocation-time. Systemic fix for the 1024-char cap pressure.ai/services/migration — thin-wrapper-ification of MCP servers, with services migrating toai/services/as the authoritative layer. This PR's removal pattern (remove MCP surface, keep service method callable via SDK) is directly aligned with that migration direction.Neither is implemented here — this PR is a single-slot reclaim only.
Acceptance Criteria (from #10132)
grep -rn 'manage_database_backup' ai/mcp/server/returns zero resultsKB_DatabaseService.manageDatabaseBackupandMemory_DatabaseService.manageDatabaseBackupremain callable viaai/services.mjsSDK (verified by the spec suite)npm run ai:backuplive invocation still emits the full 5-subfolder bundle (verified 6s / exit 0 / all 5 subfolders populated)McpServerToolLimits.spec.mjsis no worse than current state (pre-existingmanage_database1161-char failure untouched; deferred togetFullDescription)Out of Scope
getFullDescriptionexploration — systemic tool-description reductionai/services/migration — thin-wrapper-ification of MCP serversResolves #10132
Origin Session ID: 5a521819-dc75-4549-888e-fcea818d0401