Summary
On the local daemon, every toolkit-scoped MCP endpoint (/mcp/toolkits/<slug>) returns a 500 (-32603 Internal server error), while the default /mcp endpoint works fine. The daemon logs:
[mcp] handleRequest error: Error: Failed to open local SQLite data. Close other Executor processes and retry, or run with --log-level debug for details.
This happens on a completely clean daemon with no other Executor processes running.
Repro
executor daemon run (v1.5.32, macOS), create a toolkit (e.g. slug radial) via the web UI.
POST /mcp with a valid bearer + initialize → 200.
POST /mcp/toolkits/radial with the same bearer + initialize → 500, and the error above in daemon.error.log.
The slug resolves fine (GET /api/toolkits lists it); any slug fails the same way.
Root cause
The daemon's shared boot executor holds the data-dir ownership lock for its whole lifetime — a BEGIN EXCLUSIVE held open on data.db.owner-lock (apps/local/src/db/data-dir-ownership.ts, acquireDataDirOwnership).
The toolkit MCP path doesn't reuse that executor. createServerHandlers wires:
// apps/local/src/main.ts
createConfigForResource: async (resource) => {
if (resource.kind === "default") return { config: { engine } };
const handle = await createExecutorHandle({
activeToolkitSlug: resource.slug,
});
...
createExecutorHandle → createLocalExecutorLayer → openOwnedLocalDatabase → acquireDataDirOwnership, which tries to take the same exclusive lock the daemon already holds (per-connection SQLite lock, busy_timeout = 0) → SQLITE_BUSY → LocalExecutorCreateError with the "Close other Executor processes" message → 500.
So the default resource reuses the shared engine, but any toolkit resource attempts a second owned open of the same data dir from inside the process that owns it — it can never succeed while the daemon is running, which is always.
Expected
Toolkit resources should derive their engine from the already-open shared bundle (thread activeToolkitSlug into an engine/executor over the existing DB handle) rather than opening a second owned database.
Notes
e2e/local/toolkits-mcp.test.ts exercises exactly this path (/mcp/toolkits/<slug> against a spawned local server), so it presumably isn't running in CI — it should reproduce this.
- Self-host is unaffected as far as I can tell; this is the local daemon wiring only.
- This was drafted by a clanker. Hope that is ok. Feel free to close if not.
Summary
On the local daemon, every toolkit-scoped MCP endpoint (
/mcp/toolkits/<slug>) returns a 500 (-32603 Internal server error), while the default/mcpendpoint works fine. The daemon logs:This happens on a completely clean daemon with no other Executor processes running.
Repro
executor daemon run(v1.5.32, macOS), create a toolkit (e.g. slugradial) via the web UI.POST /mcpwith a valid bearer +initialize→ 200.POST /mcp/toolkits/radialwith the same bearer +initialize→ 500, and the error above indaemon.error.log.The slug resolves fine (
GET /api/toolkitslists it); any slug fails the same way.Root cause
The daemon's shared boot executor holds the data-dir ownership lock for its whole lifetime — a
BEGIN EXCLUSIVEheld open ondata.db.owner-lock(apps/local/src/db/data-dir-ownership.ts,acquireDataDirOwnership).The toolkit MCP path doesn't reuse that executor.
createServerHandlerswires:createExecutorHandle→createLocalExecutorLayer→openOwnedLocalDatabase→acquireDataDirOwnership, which tries to take the same exclusive lock the daemon already holds (per-connection SQLite lock,busy_timeout = 0) →SQLITE_BUSY→LocalExecutorCreateErrorwith the "Close other Executor processes" message → 500.So the default resource reuses the shared engine, but any toolkit resource attempts a second owned open of the same data dir from inside the process that owns it — it can never succeed while the daemon is running, which is always.
Expected
Toolkit resources should derive their engine from the already-open shared bundle (thread
activeToolkitSluginto an engine/executor over the existing DB handle) rather than opening a second owned database.Notes
e2e/local/toolkits-mcp.test.tsexercises exactly this path (/mcp/toolkits/<slug>against a spawned local server), so it presumably isn't running in CI — it should reproduce this.