Public: anyone can read this; the crew can change it.

spawnForge has callers: `POST /api/v1/forges` and an MCP `spawn` tool, both dry-run first #581

Merged opened this

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

Merged

`spawnForge` now has two callers: `POST /api/v1/forges` and an MCP `spawn` tool. Both plan first, and nothing is created unless `dryRun: false` is sent. `test/spawn-api.test.ts` (7 tests) passes, along with `test/spawn.test.ts` and `test/mcp.test.ts`. I did not run the full gate, and `bun x tsc --noEmit` fails here because the `bun` type definitions aren't found. The sandbox also blocked the test…

shipped production is serving it deploy 76ab860

opened this

spawnForge now has two callers: POST /api/v1/forges and an MCP spawn tool. Both plan first, and nothing is created unless dryRun: false is sent. test/spawn-api.test.ts (7 tests) passes, along with test/spawn.test.ts and test/mcp.test.ts. I did not run the full gate, and bun x tsc --noEmit fails here because the bun type definitions aren't found. The sandbox also blocked the test server from listening on its socket, so I ran the tests with it disabled.

  • POST /api/v1/forges {name, apps[], crew[], dryRun} takes an rw token and an admin user. dryRun defaults to true and returns the plan from planSpawn plus the copied, left-behind and byte preview. dryRun: false returns the Spawned result: 201 when made, 200 if the child already existed.
  • Validation at the boundary: the name pattern, apps the caller can read, and crew that exist on this forge. An unreadable app reads as "not a known app", so it doesn't leak.
  • GET /api/v1/forges lists the children from the spawn ledger (admin only).
  • MCP spawn tool takes the same input and is dry-run by default. An ro token can plan but gets "forbidden" when it tries dryRun: false. docs/MCP.md and docs/SPAWN.md are updated.
  • Error mapping in spawnWords, one status and one sentence per SpawnError kind:

- unauthorized: 401 - forbidden: 403 - cross_origin: 403 - invalid: 422 - refused: 409 - exists: 409 - failed: 500

  • Tests cover dry-run, bad input, non-admin, ro token, no token, cross-origin, a real spawn through a fake placement (including the idempotent retry and the ledger listing), the error table, and the MCP round-trip.
  • Gate: I added the new test to scripts/gate.ts. I also added spawn to the tool list that test/mcp.test.ts expects.
  • Beyond the issue's file list: the shared logic is in a new src/spawn/door.ts so the API and MCP don't duplicate it. src/actions/spawn.ts also gained listChildren, which is more than an export, because the ledger helpers weren't exported.
  • Org and briefs: the child's org is its name, and crew[] picks whose briefs travel.
  • Placement: the default is the hosted placement, built per app. Its child-count store is in memory, so after a restart the 5-child cap starts again from zero. The ledger is still what decides whether a child already exists.

I left out subscriptions in this call (the plan's subscriptions stay empty), the studio's "+ New forge" button, and any HTTP-level ro dry-run for the API: the endpoint needs rw, as the issue says. Only the MCP lets ro plan.

To try it: curl -XPOST -H "authorization: Bearer $RW" -d '{"name":"kid","apps":["alice/notes"]}' $FORGE/api/v1/forges returns the plan. Add "dryRun":false to make the child.

🤖 Generated with Claude Code

Closes #577

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs