Skip to content
Work in progress. These docs describe Spawnite at launch, and some parts are still being built.

MCP tools

The MCP server comes from @spawnite/mcp, and Connect your agent sets it up. Each tool’s own description, which the agent reads as it connects, is that tool’s reference. This page covers what the descriptions leave out: how a long call behaves, how a tool finds its game, and the detail of the tools with the most options. A person runs the same code as commands of the command line.

A tool that can run past a few seconds, such as profile_game, make_model, start_playtest or join_room, follows one contract. The start of its description gives its usual duration and its limit, so a client that cuts a description at 2,048 characters keeps both.

  • Progress. When the call carries a progressToken, the tool sends a notifications/progress at each phase. Its message is the phase in a creator’s words, such as Running models/imp.py in Blender or Profile run 3 of 10, replay=off, round 2: recording 20 s. While a phase lasts, the tool sends it again every 15 s with the time it has taken. The cli’s model make, model check, play profile and play join print the same lines to stderr.
  • Cancellation. When the client cancels the call, the tool ends the process or the browser it runs and sends no reply.
  • A limit. Each long tool stops at its limit and fails with the phase it was in. timeoutSeconds raises the limit for one call. It is wall-clock time. The game time a step takes is seconds on simulate and screenshot, a separate clock.
  • A run read later. profile_game also takes wait: false: it returns a run id at once, and read_run with that id returns the run’s phases so far and, once it ends, the reply the call would have given. Runs live as long as the server that started them: a restart ends them, such as a build that spawnite-mcp --watch picks up, which waits only for calls still owed a reply.

Claude Code shows each progress line in its tool view, and a progress notification resets its idle window of 30 minutes for a stdio server. A call from the main conversation that runs past two minutes moves to a background task, whose summary shows the latest line. Codex waits tool_timeout_sec for a call, 60 s unless the server’s entry sets it, so the plugin’s codex.toml sets 600. A sweep that runs longer goes through wait: false and read_run.

A game that spawnite create writes gives its agent one loop under How to work in its AGENTS.md, and the connect message points to it:

  1. Say what done looks like: a test for a rule, or one sentence and a camera for a look.
  2. Build one small change, and mount it in a scene.
  3. Run it and look: the test, or start_playtest with tab: false and screenshot, with timeline for anything that moves.
  4. Fix it and shoot again from the same camera. Keep it with the test, or with compare_shots and accept: true.
  5. After three tries that get no closer, ask the creator.

Each tool that writes into a game ends its reply with a Next: line. These tools are the add_ tools, add_item, place_loot, place_props, which also adds a shared asset, edit_terrain, edit_material and make_model. The line gives the call that shows what the tool wrote works, after what must be true first, with the names it wrote, such as Next: mount <Glow /> in a scene, then start_playtest and screenshot to see it.

screenshot ends with a line that says to shoot again with the same camera to compare.

The scaffold tools:

  • add_behaviour, add_entity, add_npc, add_dialog, add_scene, add_panel and add_shader each write one source file in the game’s shape. add_behaviour, add_npc and add_scene also write a wiki page stub at the path the Wiki tab reads: wiki/behaviour/<name>.md, wiki/npc/<name>.md and wiki/scene/<name>.md. There, a behaviour’s name is the one its trait registers under, in camel case, such as healthBar. The Wiki tab lists no entity, panel or shader, so those get no stub. A scene, an NPC and a panel also return the lines that place or register them, for the agent to paste.
  • add_dialog gives an NPC that exists a conversation. It writes src/npcs/<npc>Dialog.tsx, a Talk prompt and a short dialog() tree, and returns the lines that place it inside the NPC’s <Npc>. It refuses an NPC with no file at src/npcs/<npc>.tsx, or one whose file already holds a <Dialog>.
  • add_entity, add_npc, add_behaviour and add_scene refuse a name the engine already has, such as Pickup, or one of its exports, such as Respawn, whose trait a game’s would shadow in a dump. They compare a scene’s name in PascalCase, such as chase against Chase. They return the address of that name’s wiki page.
  • add_behaviour with system: true also writes the behaviour’s system beside it, src/behaviours/<Name>System.ts, a rule the step runs over every entity with the trait. It adds the system as the last rule of the game’s own plugin, src/rules.plugin.ts, writing that file where the game has none. It returns the lines that list the plugin in src/game.ts the first time. The rule’s entry declares no runsOn, so in a game with a room the room alone runs it.
  • add_hud writes src/hud/<Name>.tsx, a Panel at slot, top by default, with a Text and the player’s health Bar from the engine’s own parts. It returns the <Name /> line to put inside a <Hud>, in the app or a scene.
  • new_game, list_templates and the add_ tools read the platform’s registry, or the base URL or folder SPAWNITE_REGISTRY names in the server’s environment, for a fork or a test registry; they take no registry parameter.

Every tool that acts on a game finds it from the session. The session’s folders come from the client’s MCP roots, or from the server’s working directory when the client sends none; Claude Code sends the folder it was started in. A folder whose package.json depends on @spawnite/engine is a game, and so is each such folder under its games/ folder. With one game in reach, project can be left out. With several, it names one: an absolute path, a path from the session’s folder, or a game’s name under games/, such as example.

The playtest tools drive a running game: the project’s own Vite dev server and a headless Chromium page on it, one playtest at a time. The game must mount <Devtools /> in development, which hands the page the engine’s devtools store. An edit to the game reloads the page rather than patching it in place, and each tool waits for the reloaded scene. The game restarts on its start scene, so the next reply opens with a line naming the edited files and that scene. The world holds still between calls and moves only while screenshot with until or send_input steps it, so a timed round waits for the agent. The first playtest on a machine downloads Chromium, once for every game; spawnite play install fetches it ahead, or again after a download failed.

  • start_playtest bakes the game’s models and runs the game in a headless page. It returns:

    • the page’s URL
    • each warning the bake printed, on a line under the URL
    • the devtools tree, one row per line with its id

    scene goes on from the game’s start scene to the scene registered under that name, as spawnite play start --scene does, and returns that scene’s tree. A name the game never registered fails with the store’s error.

    For a game whose package.json names a spawnite.room, it also starts the game’s room on a free port, as spawnite play start does. It passes that address in the page’s room parameter, and waits until the tree holds the player’s character the room streams before it returns the tree. The returned URL carries the room’s address.

    tab: true attaches to the creator’s open tab of the game instead, as the wiki’s dev server page describes, or fails. On a tab, the world runs on between calls, and read_console returns the lines the page logged since the attach, with no file for each. A hidden tab refuses a step or a shot. A shot is the canvas and takes no size and no map.

    latency, jitter and stall start a headless page that plays a room game’s room over a slow link, as spawnite play start --latency does. They take the round trip and the jitter in milliseconds, and a stall as <milliseconds>/<every seconds>, such as 500/10.

  • stop_playtest closes the browser and ends the dev server and the room, or lets an attached tab go, playing on.

  • list_playtests lists the playtests running for the games in the session’s folders. For each, it gives the game’s address, a room game’s room, what started it and so what stops it, and when it started. What started it is this server’s own start_playtest, another MCP server’s, or spawnite play start. It reads the files each playtest leaves under the game’s node_modules/.cache/game/play, as spawnite play list does, so an agent sees a playtest another session left running before it starts a second one.

  • screenshot shoots the page, and saves the PNG under the game’s .spawnite/shots, beside the devtools page’s shots, in a file the reply names, since a terminal shows the image only as [Image].

    Given until, a JavaScript expression over entities, count(trait), has(id, trait), player() and steps, it steps the world one fixed step at a time and shoots the first step on which the expression holds, with the value it returned. Otherwise it fails naming the step it reached, with the shot of that step. It steps up to seconds of game time, 10 by default. Given seconds alone, it steps that long and shoots. A pausing modal that opens stops it on that step, which it shoots, naming the modal by its title. Given keys as well, it holds them down while it steps, so a condition can wait for the player’s character to walk somewhere.

    view shoots from a camera the map names. camera builds one with no view written first. { angle, around, zoom, ortho } frames a subject from top, front, iso or any azimuth and elevation, and { eye, target, fov } stands where it says. The camera page says what each field takes.

    map shoots that map alone through the ?map= preview, with no character, and starts the playtest when none runs, listing each model the bake warned about as start_playtest does. The page stays on the preview after a map shot, until start_playtest opens the game again. A shot through a view or a camera names the eye and the target it resolved to, to write back as a view.

  • compare_shots runs spawnite play compare for an agent: it shoots each shot the game’s test/shots.json names and compares it with the accepted image in test/shots. The reply holds, for each shot, the share of its pixels that changed, pass or fail, and the difference image, with the changed pixels in red; a shot that fails fails the call. accept: true writes the shots as the accepted set instead. Call it with accept: true once a look is right, and without it before you say a later step is done. It shoots in the game’s spawnite play start session, and starts one of its own and stops it after where none runs, so the playtest tools’ page plays on. A game with no test/shots.json gets the JSON to write there.

  • read_console returns the console lines and page errors since the last read, each pointing at the file that raised it, a line repeated in a row folded into one with its count.

  • read_frame_split returns the devtools’ frame split as the last call that stepped the world left it. The split holds a line for each part of the CPU’s frame, then the CPU, the GPU and the whole frame, each by its mean and worst milliseconds over 60 frames. It also holds a note on what the headless dev build cannot price. The world holds still between calls, and its still frames soon push the stepped ones out of the split, so the playtest reads the split right after send_input steps with keys or walkTo, and after screenshot steps with until. Step for a second or more so that all 60 frames stepped.

  • send_input clicks one of these:

    • A control a player clicks, such as a button, a link, a radio or a tab, by its accessible name, which an icon takes from its label, or by the start of a word of its name in any case, so an icon before the name is passed over.
    • Any other element, by its exact text.
    • A point in a screenshot’s pixels, refusing one outside it. On a headless page, it is a point in its 960×540 viewport, which a screenshot taken without size shows pixel for pixel and the reply names. On the creator’s tab, it is a point in its canvas’s drawing buffer, which the tab’s screenshot is, so a canvas drawn at twice its size on the page takes twice the page’s numbers.
    • { entity }, an entity in the world by its dump key or a selector one entity answers, such as { entity: "[npc.name=Mira]" }. It is clicked where it shows nearest its middle, as spawnite play press --on presses it.

    A control is scrolled into view before the click, inside a scrolled dialog too. A name that starts several fails naming them, and one that matches nothing fails naming the visible controls. A name reaches the game’s own controls. The devtools’ controls, such as the Spawnite toggle at the top left, are neither clicked by name nor named in that list. A point or their shortcut, such as ⌘E for edit mode, reaches them.

    Given type, such as { label: "Name", text: "Mira" }, it then fills the editable text field, an input or a textarea, whose label holds label, in any case, as getByLabel finds it: its <label> elements, aria-label or aria-labelledby. It then holds keys on the focused element, so keys: ["Enter"] presses the form’s default button, as a browser does, while the world steps holdSeconds of game time. It names where the player’s character stands and its pose after them, such as jump. A click that switches the scene returns the new scene’s tree.

    Given walkTo, a point on the map as the dump writes a transform, it then sends the player’s character walking there along the navmesh, as a click on the ground does. It steps the world until she stands at the route’s end, as near the point as a route reaches. The reply names where she stands and the steps taken, and the modal, by its title, where a pausing modal opened and stopped the walk. It fails where the point is off the navmesh, or where the walk outruns its budget.

  • read_game_wiki returns the running game’s own wiki as spawnite play wiki prints it, with no shot and no step: { overviewPath, kinds, entries }, the parts of the game the devtools’ Wiki tab lists. An entry carries pagePath, its page’s path from the game’s folder, where that file exists: the game’s own page for its own entry, and the page the installed engine ships for an engine entry, and overviewPath names the overview’s file where it exists. An entry’s properties hold its own data as its registration holds it, such as an item’s definition, and each of its links carries a relation where one says how it connects, such as changes from an item to the trait its use changes. query keeps the entries whose name, kind or description holds it, ignoring case, and kind keeps one kind; a kind the game lacks fails, naming the game’s kinds. read_wiki reads the platform’s published wiki, and this tool reads the game that runs.

The replay and director tools read what a dev build kept of its play and hold a moment of it open, as spawnite replay and spawnite play do, one tool for each command group, with action naming the command. A field the action does not take, or one it needs and lacks, fails the call naming it.

  • replay runs spawnite replay list, log, shot, state, rerun and check and replies with the command’s lines, shot’s image beside them; check fails the call where a continue parts. replay names a page’s replay, latest where left out, and run a room run in its place.
  • director runs spawnite play start, stop, pause, resume, step, seek, mark, screenshot, look, see, aim, eval and set on the session spawnite play start holds, which a shell’s spawnite play commands share. It replies with the command’s lines, screenshot’s image beside them. start with replay and at opens a replay’s moment held. The playtest tools above act on start_playtest’s own page, not this session; set writes a room game’s world, which set_trait refuses.

The simulate tool needs no playtest, no browser and no GPU: it mounts a scene in Node through the engine’s headless mount, by way of the cli’s simulate command, and takes milliseconds for seconds of game time.

  • simulate runs the scene named scene for seconds of game time, or until an expression over the same entities, count(trait), has(id, trait), player() and steps holds within seconds of game time, 10 by default. It holds keys down for every step, and returns the world’s dump with its hash and the steps’ wall time. It fails naming the step it reached where the expression did not hold.

    The scene is the component its file, src/scenes/<Name>.tsx, exports under that name, mounted with nothing drawn: no model or sound loads and no UI mounts, and a modal that opens on a step, a round screen for one, holds no step up. Two runs on one input return one hash.

    With profile, it times each system of the step and adds a line a system under the hash, slowest first: the system’s name, then its mean and worst milliseconds per step over the last 60 steps, as spawnite simulate --profile prints them. fields narrows the dump to the traits and fields it names, such as ["wallet.coins"], and the entities that hold one, as spawnite simulate --fields does.

The profile tool measures the game’s build on the real GPU, by way of the cli’s spawnite play profile.

  • profile_game serves the build with vite preview and plays it in a headless Chromium at 1920x1080 for about half a minute. It returns the fields spawnite play profile --json prints, as JSON. These are the frame times against budget, the main thread’s, each function’s CPU milliseconds a frame. With gpu, gpuPasses, render, alloc or trace, they also hold the GPU’s time, the GPU’s time by pass, the draw counts, the garbage by call site or the collections.

    It takes the command’s options by the same names, apart from --probe and the visible windows of --headed and --window:

    • vsync, cpu and jump false for --no-vsync, --no-cpu and --no-jump
    • functions for each --function
    • queries for each --query
    • gpuPasses for --gpu-passes
    • scene for --scene
    • mapCamera for --map-camera, which serves the dev page in place of the build

    With repeat above 1 or several queries, it runs the sweep --repeat runs, at most 10 rounds of 5 queries, and returns its JSON. A sweep of four runs or more passes two minutes, so start it with wait: false and read it with read_run. Build the game first, unminified with a source map: pnpm build --minify=false --sourcemap=true in the game’s folder.

The map tool writes a map file in Node, through the game’s own Vite, with no playtest and no browser.

  • edit_terrain runs one of the map editor’s tools, sculpt, smooth, flatten, paint or scatter, along at or along with the editor’s brush, or fill over a rect or a region. It writes the edit under terrain in src/maps/<map>.json, as spawnite map terrain does.

    • scatter with erase takes out each instance of its families under the brush, which a multiplier over 1 grows back. Each write drops an erased spot that no longer names an instance since the map’s seed or scatter changed.
    • sea sets the map’s sea to level, written as sea beside the terrain. A map that sets none has its sea 1 m below the lowest point of its base.
    • keep-heights, move-with-ground and resample rewrite the terrain after the map’s base or grid changed. keep-heights keeps each edited point’s height, and move-with-ground its offset.

    map_query then answers the material and edit at a point, and map_describe summarises the terrain and lists each edited area, never the grid’s rows.

The model tool builds a model from a script in the creator’s own Blender, through the cli’s spawnite model make.

  • make_model runs models/<name>.py in the installed Blender with no window, from an empty scene, and exports the whole scene to public/assets/<name>.glb, then rebakes the game’s model manifest and registers the model as spawnite add asset does. It returns a Workbench still of the model, then the file, its triangles, its clip names and its size, and any lines to paste that register it. Where the game keeps the model’s concept beside the script, as models/<name>-concept.png, .jpg or .webp, its Next: line is the check_model call that lays the concept over the model. A script that throws returns Blender’s traceback, which names the script’s line; with no Blender, it returns the install command for the OS.

  • check_model judges a model against the Models page and its concept in one call, as spawnite model check does. It returns one sheet rendered in headless Chromium:

    • a column for each of angles, front, three-quarter, right and back by default
    • a row for the rest pose and one for each of atSeconds into clip
    • a band for each of styles, shaded by default, silhouette, outline, wireframe or vertices

    Each file is one unit tall and orthographic. Each of references, an image with the angles its figures show left to right and an optional crop, is laid over the rest row in red. The reply prints the overlap of the two silhouettes, their runs across at ten heights, the heights of the crotch, the shoulders and the neck on each, and the heights where the body sits furthest from the concept, in centimetres. fit also fits each concept by the scale and offset that overlap best, for a concept whose height an antenna or a raised weapon sets, and the widths and landmarks then read the fitted concept. With a clip, the whole clip is checked 30 times a second for a forearm, hand, shin or foot that passes into the head or torso.

    Then it prints each file’s triangles, its size in metres, its clips with their lengths, its bones and each rule of the Models page the bake reads that it breaks. A .blend in files is refused with the export step to take first. When Chromium cannot download, it returns the rest and says why there is no sheet. The reply’s structured content carries the same numbers as spawnite model check --json: models, comparisons, crossings and the sheet’s path. A missing file or a file that is not a model returns an error that says what to pass. A .vrma clip among files returns its motion table instead, its length, where the arms peak, which a release mark starts from, the hips’ drop and travel, and each humanoid bone’s largest turn and when, and renders nothing.

The wiki tools read the platform’s wiki from the deployed site, or from the base URL or folder SPAWNITE_WIKI names in the server’s environment, for a fork or a test wiki. They take no wiki parameter, and the add_ tools’ name check reads the same wiki. The wiki’s pages come from its pages.json, read once a process. One of them writes the game’s own pages instead.

  • write_wiki_page writes one of the game’s own wiki pages, which the devtools’ Wiki tab shows: an entry’s page at wiki/<kind>/<name>.md, by the kind and name read_game_wiki gives the entry, or, with no kind and no name, the overview at wiki/index.md. It is not for an engine entry, whose pagePath is the engine’s own page: read that page, and never rewrite it. It replaces a page that exists, and needs no playtest. It refuses a kind or a name holding /, \ or .., or starting with a dot, so the page stays under wiki/.

  • search_wiki ranks the wiki’s sections, a page’s opening and each ## and ### heading, by BM25 on any word of the query, with prefix and fuzzy matching. It returns up to ten pages, each once, at the address of its best section, such as engine/entities/player#the-jump, with a line from it. A page whose title equals the query comes first, the guide page before the reference page of that name, so an export’s name finds its page. scope is guides, reference or all, the default.

  • read_wiki returns one section with its subsections by an address with an anchor, such as engine/entities/player#the-jump, or a whole page by its address, such as engine/behaviours/spin. A page or a section past 60,000 characters, about 17,000 tokens under Claude Code’s 25,000-token cut of a tool’s reply, reads in parts. Each reply says which part it is and how many there are, and part asks for the next. A long page’s reply also lists its sections, so an agent can read only the one it needs. An unknown page lists the guide pages and points at search_wiki; an unknown anchor lists the page’s sections.

  • list_assets searches the shared assets in the manifest at assets/index.json, on the platform’s bucket or the base URL or folder ASSETS_URL names in the server’s environment, for a fork or a test bucket. It searches by query and the filters kind, category, pack, style, licence, source, rigged and animated, the best matches first. Each match gives its id, kind, name, category, pack, licence, source, what the file told the publish (triangles, rigged, clip names, resolution, seconds, whether an effect loops), size, URL and the spawnite add asset command that downloads it.

The sign-in tools sign the creator in to Spawnite on the login the cli keeps, so a creator signed in by spawnite login is signed in here too, and the other way round. A tool that needs an account reads it through the cli’s requireSignIn, which throws an error that names spawnite login and the login tool when there is none.

  • logout ends the session and forgets the kept login, as spawnite logout does, and cancels a sign-in login started that is still waiting, so a later approval keeps no login.

set_visibility, and publish_version given a version, send nothing from the game’s folder, so with game given they need no game in the session. upload_version and publish_version are long calls: each sends its phases, the build, the upload and the publish, as progress notifications, holds up to 600 s or the timeoutSeconds given, and with wait: false starts as a run that read_run reads. Each needs the sign-in.

A read-only tool reads a published game’s numbers on the same sign-in, as text and as JSON. For the game and for each published version, it reads:

  • its players
  • its sessions and how long they last
  • how many players came back the next day and the next week
  • rounds finished where the game has rounds
  • purchases with what was earned

The numbers are totals for the game, start with its first player, and identify no player, as What you can see about your game says. It changes nothing.