MCP tool guide
A human-readable map of the project, scene, object, resource, spatial, physics, scripting, and support tools.
This page describes what each tool is for. The scopes that control which of them a connection can see, and the error codes they all share, are on Connect an MCP client.
Workspace and projects
accounts_list, projects_list, projects_create, projects_update, and projects_configure_multiplayer.
Updating a project
projects_update changes a project's own settings: its title, and the same multiplayer fields projects_configure_multiplayer writes.
Both tools write only the fields you pass
An omitted field keeps its current value. This is what makes the two safe to mix: renaming a project through projects_update cannot disturb its multiplayer configuration, and reconfiguring multiplayer cannot rename it. Sending title alone is a rename and nothing else — it will not blank an action schema you set earlier.
The distinction that matters is between omitting a field and clearing it. To clear a definition, pass an empty object; to leave it alone, do not pass the key at all.
Configure multiplayer
projects_configure_multiplayer sets private-room availability, player limits, late joining, the authority-action schema, release-frozen weapon and entity templates, and the presentation event schema. It takes account_id and project_id plus any of the settings below; like projects_update, it writes only what you pass. Pass an empty object to clear a schema or a set of templates. Changes affect the next release, so republish before expecting new rooms to use them.
| Argument | What it sets |
|---|---|
enabled | Whether players can create rooms for this game. |
minimum_players | 1 through 8. 1 lets a room start with one player and fill up through late joining — the setting to use when one game should be playable both solo and together. |
maximum_players | 2 through 8, and at least minimum_players, because a room that cannot hold a second player is a single-player game. |
late_join_enabled | Whether players may join a room that has started. |
action_schema | The requests a player may send with multiplayer.sendAction, keyed by action name and then field name. Up to 32 actions of up to 16 fields each; names are lowercase identifiers of at most 48 characters. Each field is {type, required}, where type is boolean, number (optional minimum/maximum), enum (values: 1–32 unique lowercase identifiers), or object_ref. An action with no fields is {}. Free-form strings are deliberately unsupported. |
weapon_templates | Up to 32 weapons, keyed by lowercase ID. Each needs kind (hitscan or projectile), cooldownMs (0–60000), range, damage, teamPolicy (enemies_only, everyone, or self_only), and ammo — {"type": "unlimited"} or {"type": "finite", "capacity": n, "costPerShot": n}. A projectile weapon also names a projectileTemplate; a hitscan weapon may not. A weapon names no action and no owner types: an object fires only the weapons its multiplayer.weapons lists, from the mount each binding gives - where a shot leaves from is the object's, not the weapon's. The project page edits the same library as forms. |
entity_templates | Up to 64 projectiles, keyed by lowercase ID. Each needs objectType (sphere, cube, or mesh, which also needs visualAssetId), an optional materialId, a collider (sphere with radius, box with extent, or capsule with radius and halfHeight, each with an optional offset), maxSpeed, gravityFactor (−10 to 10), lifetimeMs (up to 60000), collisionPolicy (first_impact ends it on anything it touches; bounce needs restitution (0–1) and maxBounces (1–16) and rebounds off scenery while a player still ends it; pierce needs maxTargets (1–8) and passes through players, damaging each once, while scenery ends it — each policy takes exactly its own options), maxActive (1–16: how many of this projectile one player may have live at once), and lateJoinPolicy (replicate). migrationPolicy and scenePolicy are optional and have no effect, because a room has no host migration and projectiles never survive a scene change; they are still accepted for older templates. maxActive is per player, 1 to 16 for each template; a room holds at most 16 live projectiles across every player and template, and refuses a launch past that. |
event_schema | The presentation events the room may send players, keyed by lowercase event name, up to 32. Each needs audience (owner, one player, or observers, everyone), delivery (reliable, resent until acknowledged, or unreliable, sent once), and fields in the action_schema field shape, plus an optional coalesce that lets a newer event replace an unsent one of the same name. action_refused, weapon_fired, and projectile_impact are sent by the room itself and cannot be declared. Events are for feedback only: anything a late joiner needs belongs in replicated state. |
{
"account_id": "acct_...",
"project_id": "prj_...",
"enabled": true,
"minimum_players": 1,
"maximum_players": 4,
"late_join_enabled": true,
"action_schema": {
"enter_car": {}
},
"weapon_templates": {
"rifle": {
"kind": "hitscan",
"cooldownMs": 250, "range": 80, "damage": 25, "teamPolicy": "enemies_only",
"ammo": { "type": "finite", "capacity": 30, "costPerShot": 1 }
}
},
"event_schema": {
"round_over": {
"audience": "observers", "delivery": "reliable",
"fields": { "winner_slot": { "type": "number", "required": true, "minimum": 1, "maximum": 8 } }
}
}
}
The response, like projects_list, projects_create, and projects_update, carries the project's complete multiplayer block.
Scene objects carry the rest of a multiplayer game in their multiplayer field, through scene_objects_create and scene_objects_update — for example "multiplayer": {"playerSlot": 1, "weapons": [{"weapon": "rifle", "mount": {"x": 0, "y": 1.4, "z": 0.5}}]}. These are typed settings, not object variables, and a malformed one is refused when it is saved:
playerSlot, 1 through 8, binds a character or vehicle to a player. Only those two kinds can hold a slot. The slot decides who controls the object when a room starts and whose input its script reads; it does not move anything, so the object also needs a control script. A vehicle players will get into needs a slot too, or must be spawned from a spawn template, because only a slotted or spawned character or vehicle can be handed over withmultiplayer.setController.scriptRole,"authority"or"presentation", is required on every scripted object that is not slotted and is not a camera or GUI element."authority"runs its script on the room server and shares its movement with every player, at most 32 per scene;"presentation"runs it in each browser as decoration, with no collider in a room.spawnTemplate,true, makes an object a spawn template: a room never builds it where it stands, and an authority script copies it, children included, withmultiplayer.spawn(name, x, y, z, options)— pickups, enemies, crates, cars, or each player's character, spawned with thecontrolleroption fromonPlayerJoined. A template has no slot, its name is unique in its scene, and only its root may have a script; a character or vehicle template's script is a control script any player may be given. A template'srespawnDelayMs(0–60000) andkillHeightare its copies' defaults; a spawn's own options win.replicate,true, shares an unscripted physics object — a crate, a ball — from the room server without a script, within the same 32 per scene.weapons, a list of{weapon, mount}bindings, is what a character or vehicle may fire and where from:weaponis an ID fromweapon_templates, andmountis the muzzle's{x, y, z}offset in this object's own frame (+Z forward, +Y up; a character's origin is at its feet), its origin when omitted. Two objects carry one weapon at different mounts without a second definition.multiplayer.firerefuses any other asnot_equipped, and publication refuses an ID the project does not define.- The active camera's
targetCharacterIdshould name a slotted object or a character or vehicle spawn template, or the camera should havefollowLocalPlayerset totrue. Each browser then retargets the camera to its own player, and keeps it on whatever that player is handed, such as a vehicle.
Only a scene script receives actions, through onPlayerAction, so a game that declares actions needs scene_script_id set with scenes_update. The scene script is also the only script that may change scene in a room, with scene.loadScene or scene.restart; publication refuses either call in any other authority script. To give each player a character of their own instead of placing slotted ones, mark a character as a spawn template, call multiplayer.spawn(name, x, y, z, { controller = participant.id, respawnDelayMs = 3000 }) from the scene script's onPlayerJoined, and point the camera at the template. scene.createObject in a room makes an object only the room server sees. In Lua, a control script calls multiplayer.fire(templateId) and the room server resolves the shot; authority scripts send declared events with multiplayer.sendEvent. See Build and test a multiplayer game.
Publishing
projects_publish_check, projects_publish, projects_publication_status, projects_unpublish, and projects_script_diagnostics. Publishing builds an immutable release of the project and serves it at the game's stable public URL — see Publish a game. For an agent the workflow is: check, fix, publish, poll.
Checking a game before publishing
projects_publish_check (projects:read) runs the same checks as the project page's Publishing section. It returns ready, which is true when projects_publish would start a build now, and eligibility, a code and message. The code is allowed, or why publishing is blocked: publishing_disabled, preflight_failed, publication_limit_reached (the plan's live-game limit), or publish_in_progress. It also returns three lists:
errors— problems that block publishing.warnings— the "Before you share" list: things a player would run into, such as players with nothing to control or a camera that does not follow each player.notes— informational, such as which multiplayer runtime each script runs in.
Every issue has a code and a message. Where it is about a specific thing it also names it — scene_id/scene_name, object_id/object_name, script_id/script_name, asset_id/asset_name, or material_id — plus code-specific keys such as slot or missing_slots. Errors and warnings with an obvious fix carry a hint:
{
"code": "incomplete_multiplayer_slots",
"message": "Scene Initial Scene binds no object to player 2, 3, and 4, who will join with nothing to control.",
"scene_id": "…",
"scene_name": "Initial Scene",
"missing_slots": [2, 3, 4],
"hint": "Give each listed player something to control: …"
}
The check walks every scene and asset, so run it after a change rather than in a loop.
Publishing and following the build
projects_publish (publishing:write, account admin) publishes the project, or republishes it with the editor's current changes. It returns at once with the new release — status: "building" and its progress — and a next_step. A refusal is ok: false with the same codes as eligibility above; a preflight_failed refusal also carries the hinted errors and warnings.
projects_publication_status (projects:read) says what is live and what is building: published, publication (public_url, active, published_at; null if the game was never published), current_release, building and the building_release itself, the five most recent releases in recent_releases, and unpublished_changes. Each release has a status — building, ready, active, superseded, or failed — its progress, and an errorCode and errorMessage when it failed. It is cheap: poll it every few seconds after publishing until the release is active or failed.
projects_unpublish (publishing:write, account admin) takes the game offline. The URL is kept, so publishing again restores the same address. Calling it on a game that is not live does nothing, and the response's was_published says which case it was.
Errors from live rooms
projects_script_diagnostics (projects:read or scripts:read) returns the Lua errors the published game's multiplayer room servers caught — the same data as the project's room script errors page. Each distinct error appears once however many rooms hit it, with the script, callback, message, stack, scene and object, the number of occurrences and rooms, when it was first and last seen, the release it was last seen in (releaseId), and an editorPath to its scene in the editor, which is null once that scene has been deleted. A game that was never published returns not_published.
Scenes
scenes_list, scenes_create, scenes_update, scenes_delete, and scenes_open_editor_link.
Creating a scene
scenes_create takes a name and gives the scene the same starter content the editor does: lights, a camera, a platform, and a couple of shapes. Pass skip_default_objects: true to start empty, for example for a menu or title screen. set_as_initial: true makes the new scene the one the game starts in, inherit_sky_from copies the sky settings of another scene by its ID, and display_order sets its position in the scene list. A scene's name is set here and cannot be changed through scenes_update.
Scene settings
scenes_update is where everything that belongs to a scene rather than to an object is set. Each argument is independent: omit one to leave it untouched, or pass null to clear it.
| Argument | What it sets |
|---|---|
scene_script_id | The scene script, from scripts_list; null detaches it. A scene with no scene script never receives onPlayerAction, so authoring a script and forgetting this step is the usual reason a game looks unresponsive. |
collision_groups | The Jolt collision-group filter tables. Each entry defines one group_id's exempt subgroup pairs; objects opt in through their own collisionGroupId and collisionSubGroupId. See Collision groups. |
navigation | The walking agent the navigation mesh is baked for — radius, height, max climb, and max slope, plus an optional cell_size (by default half the agent radius). Defaults suit a person (0.4, 1.8, 0.4, 45°); set it when agents are much larger or smaller. null restores the defaults. |
music | Background music that autoplays when the scene loads: an audio asset_id, plus optional volume, loop, and fade-in. |
player_controls | The player input overlay — movement and look sticks, joystick layout and mode, handedness, jump, primary fire, and up to six authored action buttons, each with up to four desktop keys. pointer_lock (also accepted as desktop_mouse_look) captures the mouse on click so it drives look input; it is off by default, and a first-person scene needs it to be playable with a mouse. Passing player_controls replaces the whole overlay rather than merging into it: send the sticks and buttons you want to keep, because a list you leave out is dropped. Only enabled and mobile_controls_enabled are required. A movement stick in sticks sets mobile_layout, joystick_mode and handedness, and buttons sets jump_enabled, primary_fire_enabled and primary_fire_label; those legacy keys are then ignored if sent, and the response lists any it overruled under warnings. Without the arrays the legacy keys still work, and any you leave out keep the scene's current value, as pointer_lock does. When you send buttons, jump and primary fire are turned on only if the list includes a jump or primary_fire button. See Player input and touch controls. |
Deleting a scene
scenes_delete soft-deletes: the scene's objects are kept so it can be restored, but it stops appearing in scenes_list and can no longer be loaded.
A project's initial scene is refused. Make another scene the start scene first — a project with no entry point cannot be played, so there is no state in which deleting the last way in is the right thing to do. scenes_list marks the current one with is_initial. No tool moves the start to an existing scene: do that with Set as Start in the editor's scene list, or create the replacement with scenes_create and set_as_initial: true.
Opening the editor in another browser
physics_run_simulation, scenes_take_screenshot, and scripting_run_editor_lua run inside a visible editor tab with the matching scene open. When the only such tab is in the background, or your client drives a browser that is not signed in (an automation profile, for example), call scenes_open_editor_link with account_id, project_id and scene_id. It returns an editor_url and its expires_at. Open the URL in a browser you control and keep that tab visible; those three tools then run in it.
- It does not sign the browser in. The link's token goes with each request the page makes, and the page sets no cookies. Closing the tab, or letting the link expire, leaves the browser as signed out as it was.
- It covers one project. The tab can reach that project and nothing else in the account.
- It needs
objects:write, because the page is the full editor and can change the scene. - It lasts 60 minutes by default. Pass
expires_inin minutes, up to 480. An open tab stops working when the link expires. - Do not reload the tab. The page removes the token from its address bar so other scripts never see it, so a reload goes to the sign-in page. Open
editor_urlagain instead, or ask for a new link.
Treat editor_url as a credential: do not share it or write it to a log.
Scene objects
scene_objects_list, scene_objects_create, scene_objects_update, scene_objects_update_batch, scene_objects_delete, and scene_objects_delete_batch. Use a stable idempotency_key when creating objects so a retry returns the original result instead of duplicating it.
scene_objects_list filters by name_prefix, exact object_type, or a query matched against ID, name, and type, and returns at most limit objects (up to 1000; every match when omitted), with matched_count, returned_count, and truncated to say whether more matched. In a large scene, pass detail: "summary" to get only each object's ID, name, and type. scene_objects_delete_batch takes exact object_ids or the same filters, matched the same way. It needs at least one of them, and an ID given together with filters is deleted only if it also matches the filters. scene_objects_update merges the fields you send into the existing object, so send only what changes.
scene_objects_create and scene_objects_update_batch check each entry on its own. The response lists what was applied (created_objects and reused_objects, or results) next to errors, one for each refused entry with its index, and the call fails with validation_error only when every entry was refused. Read errors even when ok is true. A new parent link is refused when the parent is a water object, or when it would join a GUI element and a 3D object, since GUI elements nest through parentObjectId. To move an object to the top level of the scene, send parent_id: null (an empty string also works). Deleting an object keeps its children, which become top-level objects. Changing an object's type, for example from empty to cube, rebuilds it in an open editor as the new type, and its children stay attached.
Every field you send must apply to the object's type. hasPhysics on a cube, for example, is refused with validation_error and a message naming the field and the types it does apply to (Fields do not apply to cube: hasPhysics (hasPhysics applies to camera)), instead of being saved and ignored. Only the fields a request sends are checked, so an object that already stores such a field still saves. On a type change, the fields sent are checked against the new type. To make a cube, sphere, cylinder or capsule visual-only, with no collider, send collisionType: "none"; that is the only collisionType a primitive accepts. See Physics, collisions, and triggers.
A create only has to carry the fields you want to differ from the editor's own defaults. Presentational, tuning and generator settings are filled in server-side when omitted, with the values the editor itself uses for a new object: a GUI element's padding, opacity and borderRadius; a height field's terrain and noise settings; a character's collider, movement and animation settings (a 1.5-unit capsule, walkSpeed 4, runSpeed 8, jumpSpeed 6, mass 70); a light's color and intensity; a camera's cameraType ("perspective"); water's buoyancy, drag and flow; and a cloth's segments and pinning. An explicitly supplied value always wins, including a falsy one.
What stays required is what only you can choose: a shape's dimensions (a cube's or water's extent, a sphere's radius, a cylinder's or capsule's radius and halfHeight, a cloth's width and depth), asset bindings (a mesh's or ragdoll's meshAssetId, a GUI image's imageAssetId), a mesh's collisionType, a constraint's constraintType and a ragdoll's mode. The scene_objects_create description lists them per type, so a client can read them before its first call.
A camera's built-in controller is set on the camera object. controlMode is free, first_person, first_person_absolute, or third_person, and the last three follow targetCharacterId. first_person_absolute takes its world yaw from the target once, then keeps look independent of the body's turning: use it when a control script sets the body's yaw from input.aimYaw. first_person adds look to the body's rotation. In both first-person modes hideFollowedObject (default true) hides the followed object and its visual children while that camera renders; set it to false to show the body. minPitch and maxPitch, in degrees from −90 to 90 with positive up, limit how far the built-in controller looks down and up. Without them first person allows about ±87°, and third person orbits ±60° around its authored boom. A camera with a script attached does not use the built-in controller, so none of these apply to it.
Project resources
- Materials:
materials_list,materials_create,materials_update,materials_delete. Create and update take the same colour and finish fields —specular,shininess,emissive,emissiveIntensity,reflectivity,roughness,metalness,clearcoat,clearcoatRoughness,ior,sheen,sheenRoughness— so a material can be finished in one call. Any material except a shader can carry a colour map,mapId. Standard and physical materials can also carrynormalMapId,roughnessMapId,metalnessMapIdandaoMapId, each an image asset ID, withnormalScale(-10 to 10, default 1) andaoMapIntensity(0 to 10, default 1); those six fields are refused withvalidation_erroron a phong, basic or lambert material, which never renders them.textureRepeatandtextureOffset({x, y}) andtextureRotation(degrees, counter-clockwise) tile every map on the material. Onmaterials_update, an empty string for a map ID removes that map. - Assets:
assets_list,assets_upload_from_url,assets_generate_height_map,assets_generate_mesh,assets_generate_track,assets_get_generated_recipe,assets_create_upload_session,assets_get_upload_session,assets_delete - Scripts:
scripts_list,scripts_create,scripts_update,scripts_delete - Shaders:
shaders_list,shaders_create,shaders_update,shaders_delete
Uploading files
assets_upload_from_url imports a file that is already at a public http(s) URL. Pass filename when the URL path has no file extension. For a file on your own computer, which a chat client usually cannot read, call assets_create_upload_session: it returns an upload_url to open in a browser and upload from. The link lasts 60 minutes unless you set expires_in (up to 1440), and can be limited to certain file extensions or a max_files count. Afterwards, assets_get_upload_session lists what was uploaded and the asset IDs to bind.
Editing a shader
shaders_update takes the fields you are changing — name, shaderType, or glsl. Editing the source of a shader that a material already references re-renders every object using that material; there is no need to recreate the material to pick up new source.
Deleting a resource, and what it leaves behind
Deletion never cascades into scene objects, so each of these leaves a specific kind of dangling reference. None of them refuses on account of one: the delete goes through, and the response names what it has just left dangling in referenced_by (at most 25 of them), with reference_count for how many there were in all. Read that back, or repoint before deleting:
materials_delete— objects bound to the material fall back to the default white material. A surface binding on a mesh keeps the binding and shows as a missing material, which means pointing it at a replacement is a single edit rather than a rebuild.shaders_delete— a material still referencing it throughvertexShaderIdorfragmentShaderIdwill not render at all. Repoint or delete those materials too.assets_delete— objects bound throughmeshAssetId,heightMapAssetId,colorMapAssetId,imageAssetId,hoverImageAssetId,fontAssetId, or ananimationAssetsentry, and materials bound throughmapIdor one of the PBR map IDs, keep the now-dangling binding. A mesh whose asset is gone does not appear at all, and a height field falls back to generated noise without saying so. Deleting a generated mesh or track also discards the stored recipe, soassets_get_generated_recipecan no longer return it and the shape cannot be rebuilt by amending what it was made from.scripts_delete— does not reportreferenced_by. Objects the script was attached to, and a scene whosescene_script_idnamed it, keep the deleted ID. Before deleting, find those objects withscene_objects_listand detach it from the scene withscenes_update.
Height maps
assets_generate_height_map creates a deterministic grayscale PNG for a height field without an image model. It accepts a name plus structured controls for sample_count, seed, frequency, octaves, persistence, lacunarity, ridge_weight, island_weight, terrace_steps, and smoothing_passes. Pixel values from 0 through 255 map from the terrain base through its heightScale.
Set generate_color_map to true to also create a pixel-aligned RGB companion. color_palette accepts terrain, island, desert, or alpine. Apply the returned heightMapAssetId and colorMapAssetId bindings to the same height-field object so its geometry and visuals match.
The height-field object itself needs only what you want to set. sampleCount, terrainSize, heightScale, noiseSeed, noiseFrequency and noiseOctaves are all defaulted when omitted — so a height field built around an authored heightMapAssetId does not have to invent noise settings for a generator that is never going to run. The noise triple only describes how to synthesise heights; supplying a height map says they are already authored.
Generated meshes
assets_generate_mesh builds a GLB from either a tree of solids combined with Boolean operations or a high-level seeded rock recipe. assets_generate_track sweeps a road cross-section along a centreline. Both return an ordinary project asset you bind to a mesh object through meshAssetId.
The solids are box, sphere, cylinder, cone, capsule, torus, and wedge, plus three built from a 2D profile: extrude pushes a closed outline along its depth, with optional twist and top scale; revolve turns a [radius, y] outline around the vertical axis; and sweep carries an outline along a path of 3D points. Operations are union, difference, and intersection. The response includes a rendered preview image of the result, so check it for a hole in the wrong face before binding the asset.
A track is a list of straight and arc sections, with optional rise, banking, and width taper per section. Set close and mark three or more sections with adjust to have the circuit solved so it meets its start. For a road that wanders, give a path of control points instead of sections. Optional barriers add solid walls along either edge.
Seeded rock recipes
A rock recipe uses the same tool rather than a separate endpoint. Its kind is boulder, outcrop, arch, bridge, cliff_wall, or cliff_freestanding. The result stays inside its declared size, has a flat base, and exposes rock, top, and ground material slots. Reusing the seed reproduces the same geometry.
{
"rock": {
"kind": "arch",
"size": [12, 7, 4],
"seed": 2033752418,
"detail": 2,
"roughness": 0.45,
"asymmetry": 0.4,
"erosion": 0.3,
"top_flatness": 0.15,
"opening_width": 5,
"opening_height": 4,
"uv_mode": "atlas",
"atlas_size": 2048
}
}
For an arch or bridge, opening_width and opening_height are guaranteed gameplay clearance, not approximate decoration. detail accepts 0 through 3. roughness, asymmetry, erosion, and top_flatness accept 0 through 1; clustered formations can also set cluster_count from 2 through 12.
Leave collision out for an exact static triangle mesh, which is normally right for arches, bridges, and cliffs. Add "collision": {"decompose": true} at the recipe root for a movable boulder or outcrop. Read geometry.warnings: reaching the hull budget can make the collider more solid than the rendered model.
uv_mode: "tiling" is the default and uses uv_scale as metres per texture tile. uv_mode: "atlas" creates unique padded facet islands; atlas_size accepts 512, 1024, 2048, or 4096. The editor can reproduce and download the colour-coded UV guide from the stored recipe.
The recipe is stored with the asset. assets_list identifies generated meshes and tracks; call assets_get_generated_recipe for the complete recipe, amend it, then pass it with the same asset_id to assets_generate_mesh or assets_generate_track. Reusing the ID rebuilds the asset without breaking scene-object bindings.
The same height-map, shape/rock, and track generators are available in the editor's Assets panel and produce identical assets — see Assets, materials, and shaders.
Specialized creation
soft_bodies_create, soft_bodies_update, vehicles_create_wheeled, and vehicles_create_tracked supply safe defaults for complex Jolt objects.
vehicles_create_wheeled takes a preset of car (the default), truck, buggy, race, or motorcycle, and an optional even num_wheels from 2 to 16. vehicles_create_tracked takes tank (the default), bulldozer, or apc, and an optional num_wheels_per_track from 2 to 12. Both accept a spawn position and a chassis mass, and attach a WASD driving script unless you pass your own Lua in script.
soft_bodies_create takes a type of softSphere, softCube, softCylinder, softCapsule, softMesh, or softCloth. Apart from cloth, each one persists as an ordinary shape carrying a soft block — a softCylinder is stored as a cylinder — so scene_objects_list reports the rigid type and you find soft bodies by the presence of soft, not by type. Rest dimensions stay on the shape itself, which is why a soft cylinder takes radius and half_height rather than repeating them inside the block.
Because of that, scene_objects_create and scene_objects_update can add or remove soft on an existing cube, sphere, cylinder, capsule, or mesh directly — the same switch the editor's inspector offers. A soft body is always dynamic and can be neither a trigger volume nor a compound parent; sending soft alongside isSensor, compoundChildren, or a non-dynamic motion is rejected. soft_bodies_update only accepts an object that is already a soft body.
Pushing one from a script uses the ordinary object API — addForce, addImpulse and addTorque all work on a soft body, and there is no applyImpulse on anything. Size the numbers by vertex count, not by the shape: a soft body's mass is the number of vertices it simulates, one kilogram each, and the object's mass field is ignored. A softSphere at the default tessellation builds 162 vertices and so weighs 162 kg, and raising segments_theta or segments_phi makes it heavier — so an impulse sized for a rigid body of the same dimensions moves it imperceptibly. addForce and addTorque are also per-frame, cleared by each step, while addImpulse is the one-shot. See Physics, collisions, and triggers.
Spatial planning and verification
Use spatial_inspect_scene, spatial_plan_layout, spatial_solve_relative_placement, spatial_check_clearance, and spatial_convert_point before guessing transforms. spatial_inspect_scene reports objects' local and world transforms, bounds, corners, and named world-space surfaces, such as a ramp's world_geometry.surfaces.top. spatial_solve_relative_placement places a subject against a reference object with a requested clearance, spatial_check_clearance tests a proposed transform for bounding-box overlaps, and spatial_convert_point converts a point between an object's local space and world space through its parents. spatial_plan_layout hands a plain-language request to a read-only planning specialist. None of them changes the scene. physics_run_simulation runs the real scene in a visible matching editor tab, samples selected objects, and restores the scene afterward. It runs for duration_ms (100 to 10,000, default 3,000), samples at least one and up to 20 objects named in object_ids or object_names every sample_interval_ms (16 to 1,000, default 100, with the first sample one interval in), and applies an input_sequence of up to 50 timed input changes, each an at_ms plus the inputs to set: movement and look axes or keys, jump, primaryFire, and authored actions.
Vehicles are driven through the shared input bus in a simulation run — moveAxisY for throttle, moveAxisX for steering — because a vehicle's own script re-derives its driver input from that bus every frame. Vehicle samples report vehicle_wheel_contact, vehicle_driver_input and body_active, which is what tells a stationary vehicle with no throttle apart from one whose wheels are not touching the ground. A vehicle drops onto its suspension in the first fraction of a second, so early samples without contact are normal. Read the summary's wheels_ever_in_contact instead: a wheel that never touches the ground during the whole run points to a geometry or spawn-height problem. See Troubleshooting.
Object and scene Lua run during a simulation, and every error a script raises comes back in script_errors, with the script's label, the callback it was in, the message, and how many times it happened. Read that list first when a contact is recorded but nothing moved: a callback that ran and threw leaves exactly the same trace in the samples as a callback that never fired, and only script_errors tells the two apart.
Screenshots
scenes_take_screenshot captures an image from an open editor tab — either from a named scene camera (camera_id) or whatever the editor is currently showing. Like physics_run_simulation, the matching project and scene must already be open and visible in an editor tab. Returns an image_url that expires after 24 hours.
Editor Lua
scripting_run_editor_lua runs a snippet of Lua inside an open editor tab and returns whatever that snippet returned, together with anything it passed to editor.log. It takes account_id, project_id, scene_id, code (1 to 50,000 characters), and an optional mode.
mode: "inspect"(the default) can read the scene but not change it — use it to answer questions about what is actually loaded.mode: "apply"additionally allowseditor.scene.create,update, anddelete. Writes are refused while a simulation is running.
The two modes need different permissions. The tool is available to a client holding either objects:read or objects:write, because inspect cannot change anything; apply is checked separately and refuses with insufficient_scope unless the connection holds objects:write. A read-only client can therefore inspect a scene through this tool without being granted write access to it.
The code is ephemeral: it is never attached to an object and never stored as a project script. The editor API includes editor.scene.list, get, create, update, delete, editor.log, and navigation inspection through editor.navigation.bake():await(), stats(), show(), hide(), and findPath(from, to). It is documented under "Editor Lua REPL" in Editor basics and behaves identically here.
Like physics_run_simulation and scenes_take_screenshot, this tool needs the matching project and scene already open in a visible editor tab. The request is offered to that tab and expires after 30 seconds; if nothing claims and completes it in time the call fails with editor_unavailable.
Support
support_contact files a request from a message and an optional context, such as error output. Its status is pending until support has been emailed, then sent; failed means it was not delivered, and resolved that support has closed it. support_list and support_get retrieve your own requests in that account; the list returns the 100 most recent. Each request includes replies: the replies support has emailed you about it, oldest first, each with id, body and sent_at. A request marked resolved usually has a reply explaining why, so read it before filing again.
Pass project_id to support_contact when the request is about a project you are working in. The request is tagged with it and comes back with project (id and title); an untagged request has project: null. Pass the same project_id to support_list to see only that project's requests and their replies. An archived project, or one in another account, fails with not_found and nothing is filed.
Preview destructive queries
Before scene_objects_delete_batch, run the same filter through scene_objects_list and inspect the exact matches. The same care applies to scripting_run_editor_lua in apply mode: run the snippet in inspect mode first and read what it reports before letting it write.
The resource deletes — materials_delete, shaders_delete, assets_delete — do not cascade, but each reports what it left dangling in referenced_by — read it, and repoint what it names. scenes_delete is the gentler one: it soft-deletes and keeps the objects.
Upcoming: platform model administration
These tools are pending deployment. They belong to the separate /api/v1/admin/mcp server and require OAuth authentication as a platform administrator. They are unavailable on the standard MCP server.
admin_plans_listlists all plans, including hidden ones, with publicplan_idvalues and assigned model counts.admin_models_listsearches the catalog using optionalqueryand exactproviderfilters. Passplan_idto list only that plan's assigned models. Results include provider, model ID, capabilities, pricing, and configured-provider and tool-support flags.admin_models_refreshrefreshes RubyLLM and mirrors the available models into the application catalog. It takes no arguments and returnsadded,updated,unchanged, andtotalcounts. Existing models and plan assignments are preserved; new models are not automatically assigned to plans. Registry failures returnrefresh_failed.admin_plan_models_addandadmin_plan_models_removetakeplan_id(plan_...),provider, and the provider'smodel_id, as returned by the list tools. They change only that plan's assignment and returnchangedandmodel_count. Repeating a call succeeds withchanged: false. A missing plan or model returnsnot_found.
The list tools require admin:debug:read, default to 25 results, cap limit at 100, and accept a nonnegative offset; they return page count and filtered total. Refresh and assignment changes require the new admin:models:write scope. Request both scopes to discover and manage assignments; existing connections must authorize the new scope. The legacy mcp scope grants neither. Assigning a model still requires its provider to be configured and the model to support function calling before it is usable in chat.