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.
Long calls
Section titled “Long calls”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 anotifications/progressat each phase. Itsmessageis the phase in a creator’s words, such asRunning models/imp.py in BlenderorProfile 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’smodel make,model check,play profileandplay joinprint 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.
timeoutSecondsraises the limit for one call. It is wall-clock time. The game time a step takes issecondsonsimulateandscreenshot, a separate clock. - A run read later.
profile_gamealso takeswait: false: it returns a run id at once, andread_runwith 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 thatspawnite-mcp --watchpicks 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.
The loop
Section titled “The loop”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:
- Say what done looks like: a test for a rule, or one sentence and a camera for a look.
- Build one small change, and mount it in a scene.
- Run it and look: the test, or
start_playtestwithtab: falseandscreenshot, withtimelinefor anything that moves. - Fix it and shoot again from the same camera. Keep it with the test, or with
compare_shotsandaccept: true. - 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_panelandadd_shadereach write one source file in the game’s shape.add_behaviour,add_npcandadd_scenealso write a wiki page stub at the path the Wiki tab reads:wiki/behaviour/<name>.md,wiki/npc/<name>.mdandwiki/scene/<name>.md. There, a behaviour’s name is the one its trait registers under, in camel case, such ashealthBar. 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_dialoggives an NPC that exists a conversation. It writessrc/npcs/<npc>Dialog.tsx, a Talk prompt and a shortdialog()tree, and returns the lines that place it inside the NPC’s<Npc>. It refuses an NPC with no file atsrc/npcs/<npc>.tsx, or one whose file already holds a<Dialog>.add_entity,add_npc,add_behaviourandadd_scenerefuse a name the engine already has, such asPickup, or one of its exports, such asRespawn, whose trait a game’s would shadow in a dump. They compare a scene’s name in PascalCase, such aschaseagainstChase. They return the address of that name’s wiki page.add_behaviourwithsystem: truealso 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 insrc/game.tsthe first time. The rule’s entry declares norunsOn, so in a game with a room the room alone runs it.add_hudwritessrc/hud/<Name>.tsx, aPanelatslot, top by default, with aTextand the player’s healthBarfrom the engine’s own parts. It returns the<Name />line to put inside a<Hud>, in the app or a scene.new_game,list_templatesand theadd_tools read the platform’s registry, or the base URL or folderSPAWNITE_REGISTRYnames in the server’s environment, for a fork or a test registry; they take noregistryparameter.
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_playtestbakes 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
scenegoes on from the game’s start scene to the scene registered under that name, asspawnite play start --scenedoes, and returns that scene’s tree. A name the game never registered fails with the store’s error.For a game whose
package.jsonnames aspawnite.room, it also starts the game’s room on a free port, asspawnite play startdoes. It passes that address in the page’sroomparameter, 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: trueattaches 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, andread_consolereturns 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 nosizeand nomap.latency,jitterandstallstart a headless page that plays a room game’s room over a slow link, asspawnite play start --latencydoes. They take the round trip and the jitter in milliseconds, and a stall as<milliseconds>/<every seconds>, such as500/10. -
stop_playtestcloses the browser and ends the dev server and the room, or lets an attached tab go, playing on. -
list_playtestslists 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 ownstart_playtest, another MCP server’s, orspawnite play start. It reads the files each playtest leaves under the game’snode_modules/.cache/game/play, asspawnite play listdoes, so an agent sees a playtest another session left running before it starts a second one. -
screenshotshoots 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 overentities,count(trait),has(id, trait),player()andsteps, 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 tosecondsof game time, 10 by default. Givensecondsalone, 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. Givenkeysas well, it holds them down while it steps, so a condition can wait for the player’s character to walk somewhere.viewshoots from a camera the map names.camerabuilds one with no view written first.{ angle, around, zoom, ortho }frames a subject fromtop,front,isoor any azimuth and elevation, and{ eye, target, fov }stands where it says. The camera page says what each field takes.mapshoots that map alone through the?map=preview, with no character, and starts the playtest when none runs, listing each model the bake warned about asstart_playtestdoes. The page stays on the preview after amapshot, untilstart_playtestopens 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_shotsrunsspawnite play comparefor an agent: it shoots each shot the game’stest/shots.jsonnames and compares it with the accepted image intest/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: truewrites the shots as the accepted set instead. Call it withaccept: trueonce a look is right, and without it before you say a later step is done. It shoots in the game’sspawnite play startsession, and starts one of its own and stops it after where none runs, so the playtest tools’ page plays on. A game with notest/shots.jsongets the JSON to write there. -
read_consolereturns 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_splitreturns 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 aftersend_inputsteps with keys orwalkTo, and afterscreenshotsteps withuntil. Step for a second or more so that all 60 frames stepped. -
send_inputclicks 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
sizeshows 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, asspawnite play press --onpresses 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
⌘Efor 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 holdslabel, in any case, asgetByLabelfinds it: its<label>elements,aria-labeloraria-labelledby. It then holds keys on the focused element, sokeys: ["Enter"]presses the form’s default button, as a browser does, while the world stepsholdSecondsof game time. It names where the player’s character stands and its pose after them, such asjump. 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_wikireturns the running game’s own wiki asspawnite play wikiprints it, with no shot and no step:{ overviewPath, kinds, entries }, the parts of the game the devtools’ Wiki tab lists. An entry carriespagePath, 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, andoverviewPathnames the overview’s file where it exists. An entry’spropertieshold its own data as its registration holds it, such as an item’s definition, and each of itslinkscarries arelationwhere one says how it connects, such aschangesfrom an item to the trait its use changes.querykeeps the entries whose name, kind or description holds it, ignoring case, andkindkeeps one kind; a kind the game lacks fails, naming the game’s kinds.read_wikireads 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.
replayrunsspawnite replay list,log,shot,state,rerunandcheckand replies with the command’s lines,shot’s image beside them;checkfails the call where a continue parts.replaynames a page’s replay,latestwhere left out, andruna room run in its place.directorrunsspawnite play start,stop,pause,resume,step,seek,mark,screenshot,look,see,aim,evalandseton the sessionspawnite play startholds, which a shell’sspawnite playcommands share. It replies with the command’s lines,screenshot’s image beside them.startwithreplayandatopens a replay’s moment held. The playtest tools above act onstart_playtest’s own page, not this session;setwrites a room game’s world, whichset_traitrefuses.
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.
-
simulateruns the scene namedsceneforsecondsof game time, oruntilan expression over the sameentities,count(trait),has(id, trait),player()andstepsholds withinsecondsof game time, 10 by default. It holdskeysdown 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, asspawnite simulate --profileprints them.fieldsnarrows the dump to the traits and fields it names, such as["wallet.coins"], and the entities that hold one, asspawnite simulate --fieldsdoes.
The profile tool measures the game’s build on the real GPU, by way of the cli’s spawnite play profile.
-
profile_gameserves the build withvite previewand plays it in a headless Chromium at 1920x1080 for about half a minute. It returns the fieldsspawnite play profile --jsonprints, as JSON. These are the frame times againstbudget, the main thread’s, each function’s CPU milliseconds a frame. Withgpu,gpuPasses,render,allocortrace, 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
--probeand the visible windows of--headedand--window:vsync,cpuandjumpfalse for--no-vsync,--no-cpuand--no-jumpfunctionsfor each--functionqueriesfor each--querygpuPassesfor--gpu-passesscenefor--scenemapCamerafor--map-camera, which serves the dev page in place of the build
With
repeatabove 1 or severalqueries, it runs the sweep--repeatruns, at most 10 rounds of 5 queries, and returns its JSON. A sweep of four runs or more passes two minutes, so start it withwait: falseand read it withread_run. Build the game first, unminified with a source map:pnpm build --minify=false --sourcemap=truein 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_terrainruns one of the map editor’s tools,sculpt,smooth,flatten,paintorscatter, alongatoralongwith the editor’s brush, orfillover arector aregion. It writes the edit underterraininsrc/maps/<map>.json, asspawnite map terraindoes.scatterwitherasetakes out each instance of itsfamiliesunder the brush, which amultiplierover 1 grows back. Each write drops an erased spot that no longer names an instance since the map’sseedorscatterchanged.seasets the map’s sea tolevel, written asseabeside the terrain. A map that sets none has its sea 1 m below the lowest point of its base.keep-heights,move-with-groundandresamplerewrite the terrain after the map’s base or grid changed.keep-heightskeeps each edited point’s height, andmove-with-groundits offset.
map_querythen answers the material andeditat a point, andmap_describesummarises 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_modelrunsmodels/<name>.pyin the installed Blender with no window, from an empty scene, and exports the whole scene topublic/assets/<name>.glb, then rebakes the game’s model manifest and registers the model asspawnite add assetdoes. 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, asmodels/<name>-concept.png,.jpgor.webp, itsNext:line is thecheck_modelcall 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_modeljudges a model against the Models page and its concept in one call, asspawnite model checkdoes. 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
atSecondsintoclip - 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, animagewith theanglesits figures show left to right and an optionalcrop, 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.fitalso 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 aclip, 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
.blendinfilesis 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 asspawnite model check --json:models,comparisons,crossingsand the sheet’s path. A missing file or a file that is not a model returns an error that says what to pass. A.vrmaclip amongfilesreturns 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. - a column for each of
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_pagewrites one of the game’s own wiki pages, which the devtools’ Wiki tab shows: an entry’s page atwiki/<kind>/<name>.md, by the kind and nameread_game_wikigives the entry, or, with no kind and no name, the overview atwiki/index.md. It is not for an engine entry, whosepagePathis 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 underwiki/. -
search_wikiranks 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 asengine/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.scopeisguides,referenceorall, the default. -
read_wikireturns one section with its subsections by an address with an anchor, such asengine/entities/player#the-jump, or a whole page by its address, such asengine/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, andpartasks 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 atsearch_wiki; an unknown anchor lists the page’s sections. -
list_assetssearches the shared assets in the manifest atassets/index.json, on the platform’s bucket or the base URL or folderASSETS_URLnames in the server’s environment, for a fork or a test bucket. It searches byqueryand the filterskind,category,pack,style,licence,source,riggedandanimated, 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 thespawnite add assetcommand 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.
logoutends the session and forgets the kept login, asspawnite logoutdoes, and cancels a sign-inloginstarted 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.