spawn-mcp
Local MCP server for the Spawn Games agent API, enabling LLMs to manage game projects, push updates, and interact with live games via a Playwright Chromium client.
README
spawn-mcp
Local Model Context Protocol server for the Spawn Games agent API, plus a Playwright Chromium play client so the LLM can open the live game, screenshot it, drive input, and debug without asking you to look.
New here? Read GETTING-STARTED.md instead: a plain-language walkthrough from install to your first game, no MCP experience assumed. The rest of this file is the technical reference.
Install
git clone https://github.com/wfbcargo/wfbcargo_spawn_mcp.git
cd wfbcargo_spawn_mcp
npm install
npm run build
npm run setup # one-time: downloads Chromium (~150MB) for the play client
npm run setup is separate on purpose, so npm install never downloads a browser behind your back. The API tools work fine without it; only the spawn_play_* tools need Chromium. Equivalent: npx playwright install chromium.
Cursor config
{
"mcpServers": {
"spawn": {
"command": "node",
"args": ["/absolute/path/to/wfbcargo_spawn_mcp/dist/index.js"],
"env": {
"SPAWN_PROJECT_DIR": "/absolute/path/to/your-spawn-game"
}
}
}
}
See mcp.example.json. On Windows use forward slashes (C:/Users/you/...).
| Env var | Default | Purpose |
|---|---|---|
SPAWN_PROJECT_DIR |
process cwd | Game project holding game.json / .env |
SPAWN_PLAY_HEADED |
1 |
0 forces headless (see the warning below); games will not render |
SPAWN_TEAM |
unset | 1 enables team mode: adds spawn_team_* and the session latch |
SPAWN_TEAM_DIR |
shared .git/spawn-team |
Ledger location, for agents that are not worktrees of one repo |
SPAWN_HTTP_TIMEOUT_MS |
60000 |
Abort API calls that hang |
SPAWN_API_URL |
pinned in src/config.ts |
Dev override only; must be https (or localhost) |
PLAYWRIGHT_BROWSERS_PATH |
Playwright default | Override where Chromium is installed |
Loop
edit → spawn_validate → spawn_push
→ spawn_play_open (once)
→ spawn_play_screenshot / spawn_play_input
→ spawn_logs / spawn_play_console / spawn_exec if broken
→ fix → push → screenshot again
Spawn is WebGPU/canvas, so accessibility snapshots won't see the world. Screenshots are the ground truth.
Play browser rules
Three things that cost real debugging time if you learn them the hard way:
- Headed only. Headless Chromium gets no WebGPU adapter (
requestAdapter()returnsnull, SwiftShader flags included), so Spawn refuses to start and every screenshot is its "One graphics fix away" gate rather than your game.spawn_play_openprobes this and reportswebgpu: "ok" | "unavailable"with an explanation. LeaveSPAWN_PLAY_HEADEDunset, and only useheaded: falseto reach a non-Spawn page. spawn_play_evalcannot touch your game UI.ui.jsrenders into a cross-origin sandboxed iframe, sodocument.querySelectorin the top frame finds none of your buttons and reaching into the frame throws. Click UI withspawn_play_inputcoordinates: screenshot, read the button's position off the image, click it. (spawn_play_evalalso takes an expression, not a function body: wrap statements in an IIFE.)spawn_execneeds a live room and cannot read your database. Rooms exist only while a player is connected, so callspawn_play_openfirst or you get a 5xx (the error says so). The endpoint is read-only server-side and refusesapi.sqloutright, evenSELECT, so there is no way to query the game's SQLite from this server. Verify persistence through replicated state instead.
First connection
- Spawn gear → Build with a coding agent → fresh
sbk_…key (~5 min, once). spawn_bootstrap→ token lands in project.env(masked in tool output). Use a distinctnameper agent.spawn_me, thenspawn_create_game(or list +spawn_set_variant).spawn_init, read.spawn/guide.md+.spawn/tome-api.md.spawn_play_open: agent joins as its own browser client (creator can still keep their tab open).
Multi-agent
Same creator account needs no crew setup. Each agent gets its own key (settings → build with your own agent) and can push the same game concurrently, the same model Savi's background builders use.
- Publish in the Spawn UI before unleashing agents. Published (
mode=live) stays stable for players while agents mutate dev head. There is no agent publish API; agents only read live viaspawn_latest/spawn_status. - One project dir (or worktree) per agent. A shared
SPAWN_PROJECT_DIRwill thrashgame.json, scripts, and.spawn/base-version. SameSPAWN_VARIANT_IDfor everyone. Credentials resolve from the project's own.envfirst, so a git worktree each (.envand.spawn/are untracked) is what makes them separate connections. A key in the MCP config env is only a fallback for projects that carry none. - Start with 2 to 3 agents, partition script/area ownership, treat 409
version_conflictas normal:spawn_latest→ merge.theirs→ push. - Label bootstraps (
terrain-agent, …) and callspawn_saviafter meaningful pushes.
spawn_status # head vs published, local base, .theirs, credential source
spawn_latest # pull head (conflict recovery)
spawn_latest mode=live # inspect published (no local write)
spawn_latest mode=live applyLocal=true # reset local to published snapshot
Team mode
Opt-in bookkeeping for the above. Set SPAWN_TEAM=1 in the first session's MCP config and run spawn_team_init in each agent's worktree. That writes a roster into the repo's shared .git/spawn-team/, which every worktree finds with no configuration and nobody can commit by accident, so later sessions pick the mode up on their own and need no extra config.
| Tool | Purpose |
|---|---|
spawn_team_init |
Create the ledger if absent, register this worktree under a label |
spawn_team_status |
Every agent, how far behind head each rail is, who has unresolved receipts, open claims, recent pushes |
spawn_team_claim / spawn_team_release |
Take or give up ownership of game.json key paths and scripts/ globs |
spawn_team_add |
Stand up a new agent's worktree: variant, own token, scaffold, roster entry |
spawn_team_brief |
Ready-to-paste opening prompt for one builder, or the whole team |
Four behaviours change while it is on:
- One session drives one agent.
spawn_push,spawn_latest applyLocal,spawn_revoke, andspawn_play_openlatch to the first project directory they see and refuse a second one. Identity, the version rail, and the single Chromium session all belong to one directory, so driving two from one session pushes one agent's work onto the other's rail and points its screenshots at the wrong client. Read-only tools stay free to inspect any worktree, andspawn_bootstrap/spawn_initstay free so a new worktree can be provisioned from anywhere. - A globally configured
SPAWN_PROJECT_DIRis refused, with an explanation, rather than used. It would resolve every session to one.env, so every agent would push as the same connection while appearing to work in its own worktree. An explicitprojectDirargument is never refused. - Pushes serialise and rebase.
spawn_pushtakes 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 you nothing and the 409 never happens. A rebase that collides stops the push with your work intact and the conflicts named, because that needs a decision no server should make.force: trueskips the rebase, since it is a deliberate whole-replace. - Claims warn on push. Changes are diffed against the base rails, so what gets checked is exactly your own edits, and touching another agent's claim is reported alongside the successful push. Advisory by design: a stale claim must never become a hostage situation.
Claim game.json key paths (entities.player, world.terrain) and script globs (scripts/hud/**). Everything except scripts/** is claimed by key path, because spawn_init puts the whole spec in game.json; a world/foo.json-style pattern is rejected rather than silently never matching.
Solo, none of this exists: the tool list stays at 27, nothing latches, and pushes take no lock.
Adding an agent is two calls plus one command you run yourself:
spawn_team_add label="terrain" worktreePath="../game-terrain" branch="terrain"
→ returns: git worktree add -b terrain ../game-terrain # run it; this server never executes git
spawn_team_add label="terrain" worktreePath="../game-terrain" bootstrapKey="sbk_…"
→ writes its variant, trades the key for its OWN token, scaffolds, registers it
spawn_team_brief label="terrain"
→ the opening prompt to paste into a session started in that worktree
Mint the sbk_ key just before the second call: they are single-use and expire in about five minutes. spawn_team_brief with no label briefs the whole team at once.
The fuller design, including what is deliberately not built and why, is in TEAM-MODE.md.
What a pull merges
spawn_latest three-way merges against the last-seen upstream, tracked in .spawn/base-scripts.json and .spawn/base-game.json. Disjoint edits compose; only genuine overlap conflicts.
| Content | Merged | Conflict lands as |
|---|---|---|
scripts/** |
per file, by content | <file>.theirs beside it |
game.json |
per key path | game.json.theirs (upstream's whole spec) |
world/*.json |
no | nothing, see below |
A conflict always keeps your value and names what collided (a path, or a dotted key like entities.player.hp). spawn_push refuses to run until every receipt is resolved and deleted.
world/*.json overlays are the gap. They are deep-merged onto game.json at compile time and never reconciled, so a stale overlay re-applies over freshly pulled content and pushes back up. Disjoint overlays are fine; two agents writing the same key are not, and nothing will warn you.
Projects created before this rail existed have no .spawn/base-game.json. Their first pull keeps the old whole-replace behaviour, copies the previous game.json to .spawn/replaced-game.json if that drops anything, and establishes the rail. spawn_status reports hasSpecRail.
Tools
API
| Tool | Purpose |
|---|---|
spawn_bootstrap |
Trade sbk_… → durable token in .env |
spawn_me |
Whoami |
spawn_list_games / spawn_create_game / spawn_set_variant |
Pick a game |
spawn_getting_started |
Whole workflow + what this project already has (no credentials needed) |
spawn_init |
Scaffold project + docs |
spawn_docs |
Guide, tome API, skills index |
spawn_skills / spawn_skill |
Browse the skill menu / load a set of skills by id |
spawn_latest |
Pull head / published / version / updateSlug (+ script sync) |
spawn_validate / spawn_push |
Compile + schema check / live push |
spawn_exec / spawn_logs / spawn_rooms |
Live world inspect (needs a live room; no SQL) |
spawn_savi |
Background context for Savi |
spawn_revoke / spawn_status |
Disconnect / local + head/published health |
Play browser
| Tool | Purpose |
|---|---|
spawn_play_open |
Launch Chromium on the play URL (headed; screenshot by default) |
spawn_play_screenshot |
See the world after a push (jpeg by default; format:"png" for flat art) |
spawn_play_input |
Keys/mouse (WASD, click, drag, type); the only way to click game UI |
spawn_play_reload |
Hard reload if the client didn't reshape |
spawn_play_console |
Page console / pageerror |
spawn_play_eval |
Top-frame page JS only; cannot see or click game UI |
spawn_play_status / spawn_play_close |
Session health / teardown |
Also: spawn_session prompt with the full loop (including multi-agent). spawn_getting_started returns the same text as a tool call, because most clients never surface prompts to the model.
Art and UI
The most common quality gap in an agent build is visual, and it has two causes worth knowing.
The engine's craft lives in skills, not in the API reference. There are ~60 of them, and the visual cluster (drawn-art, game-ui, looks, custom-materials, fx, 3d-sprites, world-composition, match-a-reference) is where textures, HUDs, colour grade, and shader surfaces are actually explained. An agent that skips them writes untextured primitives and default DOM.
Rather than rely on a prompt telling the model to go and read them, the endpoints are shaped to pull skills in: spawn_skill takes ids: [...] so the natural call carries the whole set (mechanic and look), spawn_push and spawn_play_screenshot say in their own descriptions that a plain-looking result is a missing skill rather than a missing feature, and a wrong id answers with the full menu, so guessing is cheaper than looking up.
This MCP has no asset-generation lane. Conjuring a 3D model from a prompt or generating a texture belongs to Savi in the studio; there is no agent API for it. An agent's art levers are code-drawn textures (drawn-art), scripted materials, composed primitives, and cdn/ assets that already exist. When a build genuinely needs a generated asset, ask Savi in the studio for it and let the agent wire it in.
spawn_skill ids=["game-ui","drawn-art","looks"] # load a set; a bad id returns the menu
spawn_skills # all 60, id + name + description
spawn_skills search="ui" # filter over id, name, description
spawn_skills detail="brief" # id + name only (the full index is ~9k tokens)
The index is read from .spawn/skills.json when spawn_init / spawn_docs has already saved it, so browsing costs no network call; refresh: true re-fetches.
Development
npm run typecheck # tsc over src/ + test/
npm test # node:test suite (no browser needed)
npm run check # both
npm run build # emit dist/
Tests cover the parts that silently corrupt a project when they regress: the spec compiler, the script path guards, and the three-way pull/merge in syncPulledScripts. CI runs them on Node 20/22 across Linux and Windows.
Release notes live in CHANGELOG.md. Versions are tagged v<major>.<minor>.<patch>.
The
testscript lists test files explicitly rather than globbing, becausenode --testonly expands globs itself on Node 21+, and Windows shells don't expand them either. Add newtest/*.test.tsfiles to that script or they won't run.
Security
SPAWN_AGENT_KEYlives in the game project's.envonly.- Tools never echo the full token;
spawn_bootstrapandspawn_statusreturn a masked prefix. .envand.spawn/are gitignored by init/bootstrap.- The play browser is a normal player client. It runs in a fresh, credential-free context and does not inject the agent key into the page.
Trust model
This server hands an LLM real capabilities on your machine. Worth knowing before you run it:
- Filesystem writes. Every tool takes a
projectDirand writes.env,.gitignore,game.json,scripts/**, and.spawn/**under it. There is no sandbox beyond the path you pass. - Code execution.
spawn_execruns JS in your live room;spawn_play_evalruns JS in the play page;spawn_play_openwill navigate to any URL it's given. - Untrusted text flows back to the model.
spawn_logs,spawn_exec, andspawn_play_consolereturn server- and player-influenced content. Treat it as data, not instructions. - The API origin is pinned to
https://www.spawn.coinsrc/config.ts. It is deliberately not read from the project.envand not a tool argument, so neither a cloned game repo nor the model can redirect your bearer token. Only theSPAWN_API_URLprocess env, set by whoever wrote the MCP config, can override it, and only to anhttpsorigin (or localhost).
License
MIT. See LICENSE. Unofficial community project; not affiliated with or endorsed by Spawn.
推荐服务器
Baidu Map
百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。
Playwright MCP Server
一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。
Magic Component Platform (MCP)
一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。
Audiense Insights MCP Server
通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。
VeyraX
一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。
graphlit-mcp-server
模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。
Kagi MCP Server
一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。
e2b-mcp-server
使用 MCP 通过 e2b 运行代码。
Neon MCP Server
用于与 Neon 管理 API 和数据库交互的 MCP 服务器
Exa MCP Server
模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。