Preview environments for coding agents
Claude Code, Codex, Cursor and other coding agents can run the app they change in a Spawner preview environment, with its own URLs, database and logs. Spawner builds it on your server from the agent’s worktree, uncommitted changes included, and the agent drives it with the spawner CLI or through its MCP (Model Context Protocol) server: it opens the page it changed, calls the API, looks at the database, reads the error.
The agent’s machine needs no Docker and runs none of the app: it needs Node.js, the CLI and a token. Each branch gets its own copy of the whole Docker Compose stack, so agents on different branches never compete for ports or share a database, and teammates open the same URLs to check the result.
What an agent does
Section titled “What an agent does”# the environment of the current branch, from this worktreespawner up --wait --json
# the preview token that opens its protected URLs, valid one hourtoken=$(spawner url --with-token --json | jq -r .header.value)curl -H "X-Spawner-Preview: $token" \ https://feat-login--blog.preview.example.com/api/users
spawner exec feat-login db -- psql -U app -c "select count(*) from users"
# only the error lines, a stack trace kept wholespawner logs feat-login api --errors
# restarts, out-of-memory kills, failing healthchecksspawner status feat-login --json
# a link for a reviewer without an accountspawner share feat-login
spawner down feat-loginCommands print JSON with --json, and exit with a code the agent can act on (all of them):
| Code | What the agent does |
|---|---|
| 4 | The environment failed: read the end of the build log on stderr (all of it: spawner logs <env> --job), fix the cause, run spawner up again |
| 6 | The quota or the server’s capacity is reached: spawner ls --all --mine --json lists the environments of the token’s owner; delete those whose work is done (spawner down <env>), or ask a person. spawner capacity says how many more fit, and what limits them |
| 7 | .spawner/ was refused: fix each issue it lists, at its path |
| 5 | The job keeps running on the server: follow it with spawner status <env> --json |
| 3 | Not logged in, or not allowed: ask a person. A login needs someone to approve it in the dashboard |
Setting an agent up
Section titled “Setting an agent up”-
Install the CLI on the machine the agent runs on (Node.js 20 or later):
npm install -g spawner-cli, or the copy every server serves at/api/v1/cli/spawner(install). -
Log in:
spawner login https://spawner.preview.example.com, then type the code it shows in the dashboard and approve the login. The CLI stores a personal API token named after the machine, and the agents of that machine use it. An agent running elsewhere (a container, a cloud agent) gets a token of its own instead, given as environment variables:# the token is shown oncespawner token create --name claude --project blog --expires 30dexport SPAWNER_URL=https://spawner.preview.example.comexport SPAWNER_TOKEN=spn_...Environments it creates show its name (“Ada via claude”). A token restricted to a project cannot touch the others; without
envs:exec(--scopes envs:read,envs:write,preview) it cannot run commands. A token made withspawner token createexpires with your login at the latest: create one that must last longer on the dashboard’s Account and tokens page. -
Tell it how: when it creates
.spawner/,spawner initoffers to add a short section toCLAUDE.mdorAGENTS.mdthat tells the agent when and how to use Spawner (the section). Commit it with the rest: every agent working on the repository reads it. For a project set up already, copy the section by hand. -
Optionally, the MCP server, for agents that prefer tools to commands:
spawner mcp, configured as below, with the same credentials as the CLI. It has nine tools for the main operations:spawner_up,spawner_status,spawner_list,spawner_logs,spawner_exec,spawner_stats,spawner_url,spawner_shareandspawner_down(their parameters). For the rest, such ascapacity,wakeorextend, the agent runs the CLI.
Claude Code reads CLAUDE.md; Codex, Cursor, Copilot in VS Code and Devin Desktop read AGENTS.md, and Gemini CLI does once told to (below). A project that keeps its instructions in AGENTS.md gives them to Claude Code with a CLAUDE.md holding the line @AGENTS.md, as Spawner’s own repository does.
Claude Code
Section titled “Claude Code”The CLI works as is. For the MCP server, run claude mcp add spawner -- spawner mcp, or share it with the team in .mcp.json at the root of the project, which VS Code reads too:
{ "mcpServers": { "spawner": { "type": "stdio", "command": "spawner", "args": ["mcp"] } }}Claude Code asks before it first uses the servers of a project’s .mcp.json. Permission rules in .claude/settings.json let the agent work without asking each time, and still ask before deleting an environment or handling tokens:
{ "permissions": { "allow": ["Bash(spawner *)", "mcp__spawner__*"], "ask": [ "Bash(spawner down *)", "Bash(spawner token *)", "mcp__spawner__spawner_down" ] }}An ask rule wins over an allow rule. Remove the two down rules to let the agent delete its environments once the work is validated, as its instructions say; add mcp__spawner__spawner_exec to confirm each command it runs over MCP.
These rules are a convenience, not a security boundary: a Bash rule matches the command as the agent writes it, so spawner --json down feat-login escapes the ask rule above. What bounds an agent is its token: its scopes, its project and its expiry (below).
Codex reads AGENTS.md. codex mcp add spawner -- spawner mcp adds the MCP server to ~/.codex/config.toml, where its tool timeout needs raising: spawner_up waits up to 20 minutes for a build (timeout_sec, 1200 seconds by default), and Codex gives up on a tool call after 60 seconds unless told otherwise.
[mcp_servers.spawner]command = "spawner"args = ["mcp"]tool_timeout_sec = 1500Codex passes MCP servers only a few environment variables, HOME and PATH among them. An agent that uses SPAWNER_URL and SPAWNER_TOKEN rather than a login needs env_vars = ["SPAWNER_URL", "SPAWNER_TOKEN"] in that table, plus SPAWNER_CONFIG_DIR or XDG_CONFIG_HOME if you set them.
For the CLI, mind the sandbox: in Codex’s default sandbox mode, workspace-write, the commands it runs have no network, so spawner cannot reach the server. Allow it in the same file:
[sandbox_workspace_write]network_access = trueCursor
Section titled “Cursor”Cursor reads AGENTS.md. The MCP server goes to .cursor/mcp.json in the project, or ~/.cursor/mcp.json for every project:
{ "mcpServers": { "spawner": { "type": "stdio", "command": "spawner", "args": ["mcp"] } }}Cursor asks before it uses an MCP tool, unless you allow it.
VS Code with GitHub Copilot
Section titled “VS Code with GitHub Copilot”Copilot reads AGENTS.md (the chat.useAgentsMdFile setting turns it on or off). VS Code reads the MCP servers of .mcp.json at the root of the project: the file of the Claude Code section serves both. VS Code’s own .vscode/mcp.json works too, with the same server under a top-level servers object instead of mcpServers.
Gemini CLI
Section titled “Gemini CLI”Gemini CLI reads GEMINI.md unless told otherwise, and stops waiting for an MCP tool after 10 minutes, half of what spawner_up may wait. In .gemini/settings.json:
{ "context": { "fileName": ["AGENTS.md", "GEMINI.md"] }, "mcpServers": { "spawner": { "command": "spawner", "args": ["mcp"], "timeout": 1500000 } }}timeout is in milliseconds. Gemini CLI keeps the variables whose names look like secrets, such as SPAWNER_TOKEN, from the servers it starts: an agent that uses one names it in the server’s env, as "env": { "SPAWNER_URL": "$SPAWNER_URL", "SPAWNER_TOKEN": "$SPAWNER_TOKEN" }.
Devin Desktop (formerly Windsurf)
Section titled “Devin Desktop (formerly Windsurf)”Devin Desktop reads AGENTS.md. Its MCP servers are set for the user, in ~/.config/devin/mcp_config.json (%APPDATA%\devin\mcp_config.json on Windows):
{ "mcpServers": { "spawner": { "command": "spawner", "args": ["mcp"] } } }Other agents
Section titled “Other agents”Any agent that runs shell commands can use the CLI; any MCP client can start spawner mcp over stdio. The working directory decides the project and the branch, so start the agent in its worktree, or pass -C <dir> to the CLI. Over MCP, tools work in the first root the client shares, else in the directory the server started in: a client that starts servers outside the project, or shares no root, gives each tool the worktree as path.
Windows
Section titled “Windows”On Windows, an agent started in a worktree looks for spawner, and the node it runs, in that worktree before the PATH, where a branch could commit a spawner.cmd. Give the MCP client absolute paths instead (npm root -g prints the directory holding spawner-cli):
{ "mcpServers": { "spawner": { "command": "C:\\Program Files\\nodejs\\node.exe", "args": [ "C:\\Users\\ada\\AppData\\Roaming\\npm\\node_modules\\spawner-cli\\spawner.cjs", "mcp" ] } }}Set NoDefaultCurrentDirectoryInExePath=1 in the user’s environment as well. The CLI itself runs git and the browser by their absolute paths.
Several agents at once
Section titled “Several agents at once”Give each agent its own git worktree and branch: each gets its own environment, named after the branch, with its own database. Two agents that deploy at the same time never share a working copy on the server, and their builds queue when the server is busy. When the front lives in another repository, spawner up --source front=../front-feat-login sends that worktree too; an admin lists that repository among the project’s source repositories first.
The quota (5 environments per person by default) counts the environments of all the agents of a person, sleeping ones included: spawner ls --all --mine lists them, and the instructions ask agents to delete their environment once the work is validated. Environments nobody uses go to sleep, then expire.
What an agent cannot do
Section titled “What an agent cannot do”An agent acts with its token’s rights, as its owner would: it can create, change and delete its owner’s environments, and run commands in them. It cannot reach the server itself: compose files are checked against a policy (no host mounts, no privileged containers, no host network), and an environment does not reach the networks of the others. Give agents tokens restricted to a project and with an expiry, and revoke them on the Account and tokens page when they are no longer needed.