Skip to content

Releases: wfbcargo/wfbcargo_spawn_mcp

v2.0.0

Choose a tag to compare

@github-actions github-actions released this 09 Sep 15:27
7d86a63

Spawn 6 support. Spawn 6 is not a new version of the thing this server talked to — it is a
different write path, and 1.x could not reach it at all. A 6.0 world's code is a git repository, and
the whole-document PUT /game-specs that every 1.x push used answers 409 world_is_git against one
before it reads the body. Every spawn_push aimed at a 6.0 world failed, and nothing it reported
was reaching the tree.

2.0 detects which engine a world is on and routes itself. The tool names did not change, so an
agent prompt written for 1.x keeps working on both — but the lanes underneath are genuinely
different, and the breaking changes below are the places where that shows.

It also gains a body. A room boots for a player and never for a door, so spawn_exec,
spawn_logs and spawn_rooms read nothing until someone is standing in the world. In 1.x the only
way to arrange that was spawn_play_open — a headed Chromium with a working WebGPU adapter.
spawn_client_join puts the agent's own body in the room instead: no browser, no GPU, a few
seconds.

Worlds on a pre-6.0 engine are unaffected. Every document-lane behaviour in this list is
unchanged from 1.8.0.

Breaking changes

Only on engine 6.0+ worlds; the document lane behaves exactly as it did.

  1. spawn_init clones instead of scaffolding. There is no spec document to scaffold from — the
    tree arrives whole or not at all. A project directory that is not empty and is not already this
    world's clone is refused rather than cloned over, where 1.x would have written a game.json
    that could never be pushed.
  2. spawn_push requires message. The commit's first line lands in the creator's chat under
    your name, so it is a required argument on this lane, not an optional label. Calls without one
    fail.
  3. spawn_push refuses force. It meant "whole-replace over the base-version rail", and there
    is no safe equivalent: on a git world it would mean a force-push, and the server never
    force-pushes or merges.
  4. spawn_latest refuses mode, version, updateSlug and applyLocal. They are document
    concepts. Silently ignoring "give me the published live snapshot" and handing back the dev head
    would look like it worked, so it is an error instead. With no arguments it is git pull --rebase.
  5. spawn_validate is a local pre-flight, not an authority. This lane has no server-side
    validator — the push itself is the validator. "Clean" now means only that the failures visible
    without the engine are absent.
  6. spawn_status drops the version rail. baseVersion, hasSpecRail, conflictReceipts and
    hasGameJson are absent on a 6.0 world; branch, HEAD, ahead/behind and uncommitted files take
    their place. Anything parsing those keys must handle their absence.
  7. This server's own files moved out of the world tree. A 6.0 world tracks .spawn/ — its
    engine pin, its skills index, its per-cell cost files — so the docs and caches written there in
    1.x now live under .git/spawn-mcp/. If you read .spawn/guide.md or .spawn/tome-api.md by
    path, read .git/spawn-mcp/ on a 6.0 clone (spawn_init reports docsDir, and a clone also
    carries its own AGENTS.md at the root). Screenshots moved with them.
  8. Lane-sensitive tools now make an engine-detection call (cached per process, and persisted).
    A world whose engine cannot be read from the API is now an error where 1.x would have gone ahead
    and pushed. Pass engineVersion explicitly to proceed without detection.
  9. New optional dependency: Bun, required by the four spawn_client_* tools
    and nothing else. Everything that worked in 1.8.0 still works without it.

Added

  • Both write lanes, detected per world. Every tool that reads or writes a world routes itself:

    tool pre-6.0 (document lane) 6.0+ (git lane)
    spawn_init scaffold game.json / world/ / scripts/, pull the spec clone the repo into projectDir
    spawn_push compile + PUT /game-specs stage, commit, git push, report the rooms' verdicts
    spawn_latest pull a saved spec, sync scripts, .theirs receipts git pull --rebase
    spawn_validate server-side schema validation local tree pre-flight
    spawn_status base version, receipts, head vs published branch, HEAD, ahead/behind, uncommitted
    spawn_docs guide + tome API + skills same, and the world's era + semver
    spawn_exec / spawn_logs / spawn_rooms unchanged unchanged — identical on both lanes
  • engineVersion, an optional argument on every lane-sensitive tool. Omitted, the world's
    engine is read from the API and cached. Passed — as a semver (6.0.0, 5.4) or an era name
    (6.0, document) — it is checked against the real pin, and a disagreement fails the call
    without writing anything. It is an assertion, not an override, because the failure it exists to
    prevent is a document-lane push aimed at a git world, or a game.json scaffolded over a live
    clone. spawn_exec / spawn_logs / spawn_rooms deliberately do not take it: those endpoints
    are identical on both lanes, so the parameter would be a knob that does nothing.

    The API answers the engine question in three places now, and all three are used:
    GET /api/agent/v1/me and /worlds carry engine: { semver, era, git } per world, and
    GET /api/sdk/v1/{id}/agent/docs carries engineVersion + era. Detection reads worlds first
    (a few hundred bytes) and falls back to docs, which is authoritative for any variant the token
    can reach. A 409 world_is_git from the document lane drops the cached era, so a world migrated
    to 6.0 mid-session is picked up on the next call rather than retried into the wall.

  • spawn_client_* — the agent's own body in the world.

    tool what it does
    spawn_client_join stands your body in the world as a real player — boots the room, no browser, no GPU
    spawn_client_status which sessions are standing, and how much ttl is left
    spawn_client_leave despawns the body; the room folds when the last one goes
    spawn_client any other client verb — where, players, inputs, move, look, witness, crossing, screenshot, run

    This is the cheap way to a live room, and it is also how an agent plays the game it is
    building: the body wears your name, stands in the room's census beside the creator and Savi, and
    the world's own player hooks fire for it like anyone's. spawn_exec and spawn_logs now name the
    join in their no-live-room errors instead of sending you to open a browser.

    A body and a browser answer different questions, and the build loop now says so in that order: a
    body makes the room readable and lets you act in it; the browser is the only thing that tells you
    whether the frame is any good. Join to query, look to judge.

    Four tools rather than one per verb, on purpose: the client is served by the stack rather than
    shipped here ("the client it runs is the one the stack you join serves"), so its verb list can
    change under us. Join, leave and status carry real schemas because they are the lifecycle an agent
    has to get right; everything else goes through one passthrough that cannot drift.

  • spawn_push takes a message and a body on the git lane. The commit's first line is not a
    log entry: it lands in the creator's chat and their changes list under the agent's name, beside
    what they and Savi said. The tool asks for one plain sentence about what changed for the player.
    body carries the how — and since no agent account can wake Savi any more, that body is the only
    channel to her an agent has.

  • A local pre-flight for the git lane (spawn_validate). The push is the validator on this
    lane, and it is live in every open room the moment it lands, with no dev/live split to absorb a
    broken tree. So spawn_validate checks locally what the push law names explicitly: every script
    and template is ESM-parsed (vm.SourceTextModule in a child process — a real parse that accepts
    import/export, and never evaluates the module), .scene files are checked against their
    # spawn-scene v2 yaml <cellKey> header and the cell key their filename implies, image bytes are
    checked against their extension, and binaries under assets/ are caught before
    law.git.asset-kind refuses them. spawn_push runs the same check over the changed files and
    blocks on failure.

  • depth on spawn_init — how much history a 6.0 clone takes (default 20, 0 for all of it).

Fixed

  • The play client could not open a 6.0 world. agent/docs returns playUrl absolute on the git
    lane and relative on the document lane, and four call sites prefixed it with the API origin
    unconditionally — producing https://www.spawn.cohttps://www.spawn.co/@user/world. All of them
    now go through one helper that leaves an absolute URL alone.

  • spawn_savi reported an opaque 403. The studio-chat door is now closed by account class: a
    standalone agent account (sak_ from /signup) can never wake Savi, linked or not, while a
    human's own token still can. Retrying and re-wording both fail forever, so the tool names the
    reason and points at the channel that does work — the commit body.

  • spawn_push echoed a commit subject that had not landed. When a push sends a commit that
    already existed (usually one a refused push left behind), it reports the real HEAD subject and
    says nothing new was committed, rather than echoing back the message argument.

  • spawn_latest reported a no-op pull as a change. git rev-parse --short picks its length
    from the repo's object count, so the same commit abbreviates to 7 characters in a fresh clone and
    8 once a fetch has brought more objects in...

Read more

v1.5.0

Choose a tag to compare

@github-actions github-actions released this 12 Aug 16:32

A cross-project asset catalog, and a correction to what this server told the model about making art. Design: ASSET-BANK.md.

Spawn generates a cdn/ asset on first fetch of its path and caches it there forever — the path is the asset — and it has no catalog API. So sharing across projects already costs nothing (the same path is the same asset, which is what Spawn's own "use the same file name and it will be the same asset" means), but nothing anywhere records which names worked. A name cannot be re-rolled: a path that produced something good is unrecoverable knowledge once you forget the spelling, and a path that produced something bad is permanently bad. That judgment is what the bank keeps.

Added

  • The asset bank, a user-level catalog in ~/.spawn-mcp/assets/ (SPAWN_ASSET_BANK overrides; a setting pointing at a .json file is read as its parent directory). User-level rather than per-repo on purpose: the team ledger lives in a repo's shared .git because a team is scoped to one game, which is exactly wrong for a store whose value is crossing games. Concurrent MCP processes share it through the same cross-process lock the team ledger uses. Five tools, taking the solo list from 27 to 32.
    • spawn_asset_scan: harvest every cdn/ path from one or more directories, recording which files cite it and which game it belongs to. Reads raw file text rather than the compiled spec, so it also catches paths assembled in script strings and works on a project that does not compile. A ?animations= query normalizes away, so one asset never becomes three rows.
    • spawn_asset_search: query by text, name, category, filename prefix, kind, slug, namespace, verdict, project, game, or reuse count. Ranks named, described and known-good assets above bare path matches and sinks bad ones, and reports the true match count rather than the page size, so a truncated result never reads as "that is all there is". facets: true returns counts by category, kind, style family and namespace over the whole match set, which is why there is no separate listing tool.
    • spawn_asset_note: a name, a category, description, tags, a good/bad verdict, and replacedBy — including for a path that has not been scanned or even used yet, since the moment before the first fetch is when the name is still changeable. There is deliberately no delete: a bad name is the one record that cannot be recovered by scanning again.
    • spawn_asset_preview: existence check plus, for images, the bytes inline so the model can judge the art instead of inferring it from a filename. Checks the storage host rather than the /cdn/ cook route, because the cook route generates on first fetch and answers 401 for anything it will not cook anonymously — it conflates "does not exist" with "not allowed to ask", and asking there is not side-effect-free. Storage answers a plain 200/404 and never cooks. The storage prefix carries an environment and a version segment, so it is resolved by following one redirect and re-resolved when a cached prefix starts missing, rather than hardcoded.
  • spawn_asset_sync, the authoritative fill. There is no asset API — /api/agent/v1/assets, /media and /cdn all answer 404 not_found, and the only account-wide endpoint is the game list — so the closest thing to a catalog is the set of specs your own games have pushed. The sync lists every game on the account, fetches each one's current server-side spec, and harvests its cdn/ paths, attributing each to the script it appears in. This sees what a local scan structurally cannot: games with no checkout on this machine, and assets a teammate or Savi pushed that never reached your disk. On the account it was built against, a local scan of three projects found 169 assets and the sync found 408 — the same game with 105 references on disk had 298 in its pushed spec. Slow on purpose (one spec fetch per game, each carrying every script source; four at a time, six games ≈ 3s). Specs are fetched outside the bank lock so a multi-minute sync cannot stall other agents' notes, and a game that errors is reported and skipped rather than losing the whole run.
  • Every asset tool recommends a sync when the bank is empty, never synced, or over a week stale — not just search. The damage happens at the moment of invention: a stale bank answers "no match", the model coins a fresh path, and an asset that already exists under a good name is regenerated under a second one. Because a path cannot be re-rolled, those two names can never be merged afterwards, so the warning belongs on every surface that could precede it.
  • Names. A name is a short unique handle, and every tool that takes a path takes a name instead — spawn_asset_preview path="knight" rather than sixty characters of style family and hyphenation, which is the same problem the bank exists to solve. Collisions are refused rather than silently reassigned, since a handle that resolves to two things is not a handle. Lookup is case-insensitive, and replacedBy accepts a name too.
  • Your own categories, kept separate from the filename's. The token a path starts with (model-, texture-) is now prefix, and category is free-form and assigned by you ("enemies", "ui-icons"). They are different axes — one is a type that largely restates kind, the other is what the asset is for — and both are searchable. Assigned fields are the only ones written to disk; derived ones are recomputed on read, so a rescan can never overwrite a judgement and a grammar change cannot leave an asset mis-filed.
  • Reuse is counted in games, not directories. In team mode one game is several worktrees, each its own project directory, so counting directories reported a three-agent team as three games — a headcount of your own agents dressed up as a reuse signal. Each use now records the variantId from that project's own .env (never the process fallback, which during a multi-directory scan would stamp one variant onto every project), and the count collapses uses that share one. A project naming no variant counts on its own rather than being quietly merged. Reuse across games feeds ranking, and minGames filters on it.
  • Sharded storage, one file per style family plus _meta.json, with the root namespace split by filename prefix (root-effect.json, root-sfx.json) because it has no style family and is typically the largest group — on the bank this was developed against, 116 of 170 assets, which would have recreated the single oversized file sharding exists to avoid. Measured at 821 bytes/asset a 10,000-asset bank is ~7.8 MB and parses in ~19 ms, so this is explicitly not a search optimisation: it keeps a one-field note from rewriting the whole catalog, and keeps each file openable by a human. A corrupt shard loses that one family rather than the catalog.
  • Namespace classification and collision warnings. Paths are read as moodboard (the documented moodboard-<slug>/<category>-<name>.<ext> form), root, custom, or ingested. A root path is a bare global name shared with every other Spawn game — if naming is creating and nothing namespaces it, cdn/model-tree.glb is whatever the first fetch anywhere produced — so it warns on every retrieval. A moodboard slug outside the nine canonical families is noted but allowed. ingested (public.<base64>) paths are opaque uploads, not names, and are ranked last so they are never offered as a style example.

Fixed

  • The session guide no longer tells the model it cannot make art. spawn_session and spawn_getting_started both claimed "you CANNOT generate images or conjure 3D models through this MCP … your art levers are cdn/ assets that already exist", and pointed at Savi instead. Naming a path is the generation lane, so this steered agents away from the engine's main art affordance and toward untextured primitives — the exact failure the 1.3.0 skills work existed to prevent. It now explains the mechanism, the moodboard convention, the canonical slugs, that a path cannot be re-rolled, and that bare names are globally shared.

v1.4.0

Choose a tag to compare

@github-actions github-actions released this 11 Aug 22:38

Running several agents against one game (see TEAM-MODE.md). A Spawn identity is a property of a project directory, not of a session, so one git worktree per agent gives you a team with no connection registry and no agent runtime in this server.

Added

  • Team mode, off unless SPAWN_TEAM=1 or a ledger already exists. Adds six tools and leaves the solo tool list at 27.
    • spawn_team_init: create the shared ledger if absent and register this worktree under a label. The ledger lives in the repo's common .git/spawn-team/, so every worktree resolves to the same path with no configuration, it is scoped to one game, and it cannot be committed by accident. SPAWN_TEAM_DIR overrides for agents that are not worktrees of one repo. Warns when a worktree has no key of its own, when its key came from the MCP config rather than its .env, and when its variant differs from the rest of the team.
    • spawn_team_status: the whole team at a glance: every agent, how far behind head each local rail is, whose worktree has unresolved conflict receipts, open claims, recent pushes, and head vs published.
    • spawn_team_claim / spawn_team_release: ownership of game.json key paths (entities.player) and scripts/ globs (scripts/hud/**). Everything except scripts/** is claimed by key path, because that is where spawn_init puts the whole spec; a pattern that looks like a file path outside scripts/ is rejected rather than silently never matching.
    • spawn_team_add: stands up a new agent in one call: writes its variant, trades its one-time key for its own token, scaffolds the project, and registers it. Called before the worktree exists it returns the exact git worktree add command instead of running it, because this server executes no subprocesses. It refuses to proceed without a distinct key for the new agent: sharing one token would make every agent indistinguishable on the version rail.
    • spawn_team_brief: a ready-to-paste opening prompt for one builder or the whole team, covering who it is, its worktree, what it owns, what teammates own, whether it is behind head, and the working rules. Text out; the LLM decides what to do with it. This is the dispatch affordance, and it is deliberately not an agent runtime.
  • Pushes serialise and rebase in team mode. spawn_push takes a ledger-wide lock, and from inside it "behind head" can only mean a teammate landed a push since your last sync, so it pulls first. A clean rebase costs nothing and the 409 never happens; a rebase that collides stops the push with your work intact and the colliding paths named. force: true skips it, being a deliberate whole-replace. The lock's stale window is longer than the HTTP timeout so a slow push cannot have its lock stolen mid-flight.
  • Claim warnings on push, and attribution instead of "someone else". Changes are diffed against the base rails, so exactly this agent's own edits get checked against other agents' claims, and collisions are reported alongside the successful push rather than blocking it. Every push is logged to pushes.jsonl, so a 409 names the teammate who took the version and what they touched, and spawn_latest says whose push you just pulled.
  • The session latch. spawn_push, spawn_latest applyLocal, spawn_revoke, and spawn_play_open bind to the first project directory they see and refuse a second, because identity, the .spawn/base-version rail, and the single Chromium session all belong to one directory. Read-only tools still inspect any worktree, and provisioning (spawn_bootstrap, spawn_init) stays unlatched so a new worktree can be set up from anywhere.
  • A globally configured SPAWN_PROJECT_DIR is refused in team mode, with the reasoning in the error rather than a bare rejection: it would resolve every session to one .env, so every agent would push to the same rail as the same connection while appearing to work in its own worktree, and nothing would error. An explicit projectDir argument is unambiguous and never refused.

Fixed

  • A pull no longer silently discards your game.json edits. spawn_latest used to overwrite the file wholesale, with no receipt and no mention in the sync summary, even though spawn_init puts the entire spec in it. Script sources were the only content with a merge story. There is now a .spawn/base-game.json rail and a three-way merge by key path: keys only you moved stay, keys only upstream moved fast-forward, and a key both sides moved keeps your value, lands in the reported conflicts list as a dotted path, and writes a game.json.theirs receipt that blocks spawn_push until you resolve it. Same contract the script receipts already had.
  • The project .env now wins over the process env for SPAWN_AGENT_KEY and SPAWN_VARIANT_ID; process env remains the fallback for a project that carries none. A key in the MCP config used to override every project, which made spawn_bootstrap look like it had done nothing and pinned every checkout to one connection. spawn_status reports where each credential came from.

Notes

  • Existing projects have no .spawn/base-game.json, so the first pull after upgrading keeps the old whole-replace behaviour, copies the previous game.json to .spawn/replaced-game.json when it would drop anything, and establishes the rail. Pulls merge from then on. spawn_status reports hasSpecRail.
  • world/*.json overlays are still not reconciled: they re-apply over pulled content at compile time. Disjoint overlays compose fine, overlapping ones do not. In practice spawn_init puts the whole spec in game.json and leaves world/ empty, so the key-path merge covers the common case.
  • The shared .git is located by reading .git directly (a directory in a main checkout, a gitdir: pointer plus commondir in a worktree) rather than by running git rev-parse. This server still spawns no subprocesses.
  • Tool count is unchanged at 27 solo, and 33 with team mode on.
  • spawn_init's scaffolding is now shared with spawn_team_add, so provisioning a teammate's worktree cannot drift from provisioning your own.

v1.3.0

Choose a tag to compare

@wfbcargo wfbcargo released this 11 Aug 20:36

First tagged release. Everything in this version is about closing the quality gap between what an agent builds through this server and what Savi builds in the Spawn studio. Two causes, addressed separately.

The engine's craft lives in its ~60 skills, not in the API reference, and an agent that never loads them writes code that validates and pushes but lands as untextured primitives and default DOM. Rather than add a prompt telling the model to go read them, the endpoints themselves are now shaped to pull skills in.

Added

  • spawn_getting_started: the whole workflow in one tool call, plus a checklist of what the project already has (token, variant, game.json, docs) and which step is next. Needs no credentials. Most MCP clients never surface prompts to the model, so the spawn_session prompt was effectively unread; both now serve the same text from one constant.
  • spawn_skills: the menu of skill ids with descriptions, so a build can be planned against what actually exists. Reads .spawn/skills.json when spawn_init or spawn_docs already saved it, so browsing costs no network call and no credentials, and falls back to the API. Supports search, detail: full|brief, and refresh.
  • CHANGELOG.md (this file).

Changed

  • spawn_skill now takes ids: [...] and loads a set in one call, each skill labelled in the response. Passing several is the natural shape rather than something to repeat. id still works as a single-value alias, and duplicates collapse.
  • A wrong skill id answers with the full menu, so guessing costs one call instead of two and there is no reason to skip loading for want of an id. A partial miss keeps whatever did load and reports the rest.
  • Tool descriptions name the missing craft at the moment it shows up. spawn_play_screenshot says that grey boxes or default browser UI is a missing skill rather than a missing feature; spawn_push says a successful push only proves the spec parsed; spawn_validate says schema-valid is not the same as good; spawn_init hands over a concrete spawn_skill call.
  • The session guide gained an art, UI, and look section, including the fact that image generation and model conjuring are not available through this server at all. That lane belongs to Savi in the studio, so an agent's levers are code-drawn textures, scripted materials, composed primitives, and existing cdn/ assets.
  • spawn_savi is now described accurately as a one-way note into the creator's studio chat. There is no reply channel and no way to hand Savi a task from here, so an agent should not wait on one.
  • README gained an "Art and UI" section; the getting-started guide gained the plain-language version of the same two limits.

Notes

  • Skills are long documents, roughly 7k tokens each, so spawn_skill asks for 2 to 4 ids for the work in front of you rather than the whole menu.
  • Tool count goes from 25 to 27.