Skip to content

Expose database load/unload across all user-facing surfaces#153

Merged
ccomb merged 7 commits into
mainfrom
mcp_load_unload
Jun 25, 2026
Merged

Expose database load/unload across all user-facing surfaces#153
ccomb merged 7 commits into
mainfrom
mcp_load_unload

Conversation

@ccomb

@ccomb ccomb commented Jun 25, 2026

Copy link
Copy Markdown
Owner

The engine already loads and unloads databases at runtime (Database.Manager,
REST POST /api/v1/db/{name}/load|unload), but those endpoints lived only in
Routes.hs — invisible to MCP, the CLI, pyvolca and the OpenAPI spec.

Loading/unloading changes which databases are in the working set, not the
read-only data itself, so it is a legitimate analyst action rather than an
infrastructure-only endpoint. This promotes both operations to the canonical
Resource registry, which drives every surface from one definition.

Final state

  • MCP: load_database / unload_database tools, each taking a database
    argument. The load handler catches exceptions (a fresh load parses from disk
    and can throw) and surfaces them as a tool error instead of crashing.
  • CLI: database load <db> / database unload <db>, in both local manager
    and HTTP-client modes.
  • pyvolca: the load_database / unload_database methods now route through
    the generic operationId dispatcher instead of bespoke direct-HTTP wrappers.
  • OpenAPI: both operations are stamped with their operationId.

The underlying load/unload logic (dependency auto-loading, refusal to unload a
database others still depend on) is unchanged — this is purely surface exposure.
A dispatch test pins the registry-vs-dispatch invariant that a tool-name typo
would otherwise hide until runtime.

ccomb added 7 commits June 25, 2026 18:50
Loading and unloading a database change which databases are in the working
set, not the (read-only) data — an analyst action, not infrastructure. Add
LoadDatabase/UnloadDatabase to the canonical Resource registry so they reach
MCP, CLI and OpenAPI from one definition instead of living only as REST routes.

The MCP load handler catches exceptions the way the REST handler does (a fresh
load parses from disk and can throw) and surfaces them as a tool error. A
dispatch test pins the registry-vs-dispatch invariant that a tool-name typo
would otherwise hide until runtime.
Wire the two new resources through the CLI on the model of `database copy`:
local manager mode loads/unloads in place; HTTP client mode posts to the
running engine.
load_database / unload_database now carry an operationId, so replace their
bespoke direct-HTTP wrappers with the generic _call path and guard them in the
drift test. README api-reference block regenerated.
A fresh load parses from disk and can throw, so the REST and MCP handlers
each wrapped the call in their own `try`; the CLI handler didn't, so a
throwing load crashed the process instead of reporting a clean error.

Fold the exception into the `Left` that `loadDatabase` already returns, so
every surface gets one total `Either` and the two duplicated catches
collapse to a plain two-arm match.
The load endpoint returns HTTP 200 even on failure, with a bare
`{"error": …}` body and no `success` field. The remote CLI piped that
through plain `output`, which printed the error and exited 0 — unlike
local mode and remote `unload`, which both exit non-zero.

Add `outputLoad`, mirroring `outputStatus`: fail loudly when the error key
is present so a failed remote load exits non-zero.
resultText returned "" on any unexpected reply shape, so the
"no Unknown tool gap" assertion (shouldNotSatisfy … isPrefixOf) passed
even on a malformed reply — the exact stranded-tool regression the test
exists to catch. Return `Maybe Text` and assert presence so a broken
envelope fails the test.
The live spec stamps 22 operationIds (22 of 25 Resources carry an apiPath;
3 are MCP-only), but the backstop asserted only `>= 19` and its comment
miscounted — it would silently tolerate up to 3 dropped operationIds from a
path-template mismatch. Assert `>= 22`.

Also drop the stale test_db_write docstring clause: load/unload now route
through the operationId dispatcher, not direct URLs.
@ccomb ccomb merged commit 5f8ba35 into main Jun 25, 2026
10 checks passed
@ccomb ccomb deleted the mcp_load_unload branch June 25, 2026 19:32
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.

1 participant