Skip to content

Self-management MCP toolset + online SQLite backups - #25

Open
selic wants to merge 3 commits into
mainfrom
feat/sqlite-backups
Open

Self-management MCP toolset + online SQLite backups#25
selic wants to merge 3 commits into
mainfrom
feat/sqlite-backups

Conversation

@selic

@selic selic commented Jul 30, 2026

Copy link
Copy Markdown
Member

Closes #2. Closes #3. Not deployed — prod is in use; this is merge-ready and waits for a quiet window.

Backups (#3)

src/db/backup.ts: snapshots via VACUUM INTO (a plain copy of a live database can capture a torn page mid-WAL), newest BACKUP_KEEP kept locally, optional Azure Blob shipping with DefaultAzureCredential (lazily imported — deployments without it pay nothing). Upload failures are logged but never fail the snapshot: a local copy beats none. Scheduler is unref'd and stops on shutdown; :memory: databases opt out. GET /api/backups (config + what's on disk) and POST /api/backups (snapshot now). Restore stays manual, documented in docs/backups.md — including the -wal/-shm cleanup that otherwise bites.

Defaults: daily, keep 7, <DB dir>/backups. Knobs: BACKUP_INTERVAL_HOURS, BACKUP_KEEP, BACKUP_DIR, BACKUP_BLOB_CONTAINER_URL.

Self-management toolset (#2)

src/mcp/self-tools.ts: gw_status, gw_list_servers, gw_list_tools (filters + groupsOnly summary), gw_set_tools_enabled (by tier/group/tool/whole server), gw_set_tool_tier, gw_set_grant (by role name), gw_list_presets / gw_install_preset (with dryRun), gw_set_server_enabled, gw_remove_server (confirm: true), gw_refresh_catalog, gw_backup_now.

Security posture:

  • Admin-only, and hiding is only UX: every call re-checks isAdmin and returns the same "not available" wording an unknown tool gets, so the toolset is no existence oracle.
  • gw namespace reserved in parseUpstreamSpec, so a federated server can neither shadow these names nor be shadowed.
  • No secret read-back: gw_list_servers shows header/env keys and whether each value is a kv:/bao: ref, a ${VAR}, or a literal — never the literal. (Worth noting the HTTP /api/upstreams still returns literals; that asymmetry is deliberate for the browser session but is worth a follow-up.)
  • Credential writes, secret reads and user administration stay on the HTTP surface. GATEWAY_SELF_TOOLS=off removes the surface entirely.

domain/tool-targets.ts extracts the selector→tools resolution now shared by the admin API, /me prefs, and this toolset, so the three can't drift on what "tier" or "group" means.

Tests

256 passing (26 new): 9 backup tests (snapshot is a queryable database, retention, upload failure tolerance, fake-timer schedule) and 15 toolset tests (admin gate on list and call, redaction asserts the literal is absent, group summary, every mutating tool including the confirm guard and preset dry-run), plus MCP-session integration proving a viewer sees no gw_* and is refused when calling anyway.

One dependency added: @azure/storage-blob (lazy-imported, only for off-instance backups).

🤖 Generated with Claude Code

Eugene Samotija and others added 3 commits July 30, 2026 03:02
…pies

Closes #3. The database holds everything that isn't reproducible from config —
upstream specs, roles/grants/tool settings, users, DCR clients, refresh-token
families, credential refs — and a lost file meant rebuilding it by hand.

src/db/backup.ts snapshots via VACUUM INTO (a plain file copy of a live db can
capture a torn page mid-WAL), keeps the newest BACKUP_KEEP locally, and can
ship each snapshot off-instance to Azure Blob with DefaultAzureCredential
(lazily imported, so deployments without it pay nothing). Upload failures are
logged but never fail the snapshot: a local copy beats none. The scheduler is
unref'd and stops on shutdown; :memory: databases opt out automatically.

Admin API: GET /api/backups (config + what's on disk) and POST /api/backups
(snapshot now). Restore stays manual and is documented in docs/backups.md,
including the -wal/-shm cleanup step that otherwise bites.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Closes #2. Routine gateway administration — what's connected, which groups are
enabled, turn a tier off, raise a tool's tier, close an upstream for a role,
install a preset, snapshot the database — is now doable from any MCP client
instead of a browser trip.

Boundaries, deliberately narrow: the tools are listed only for admins and every
call re-checks isAdmin (list filtering is UX, the call check is the boundary),
with the same "not available" text an unknown tool gets so the set is no
existence oracle. The `gw` namespace is now reserved, so a federated server can
neither shadow these names nor be shadowed by them. gw_list_servers redacts
header/env VALUES — keys plus ref-vs-env-vs-literal shape only, so a spec with
a literal token can't be read back out over MCP. Credential writes, secret
reads and user administration stay on the HTTP surface. gw_remove_server
requires confirm: true. GATEWAY_SELF_TOOLS=off removes the whole surface.

domain/tool-targets.ts extracts the "which tools does this selector touch"
resolution now shared by the admin API, the /me prefs API and this toolset, so
the three can't drift on what a tier or group selector means.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Backups for the gateway's SQLite database Self-management MCP: administer the gateway through its own protocol

1 participant