Creator Hub MCP — live scene editing
The Creator Hub runs an MCP server inside the app (localhost, streamable HTTP, bearer token). Its tools read and mutate the scene that is
open in the editor: changes apply to the live engine, show up in the viewport immediately, autosave to
assets/scene/main.composite
, and every one of them is a normal undo step for the user. The same server powers the Creator Hub's built-in AI assistant and can be exposed to any external MCP-capable tool.
Two ways you can be running:
| You are… | How the tools reach you |
|---|
| Inside the Creator Hub's AI assistant (Settings > AI > Enable AI Assistant; on by default since Creator Hub 0.50.0 — it is no longer experimental) | Pre-wired. The app launches your / / / CLI with the server registered as , links these skills into the scene, and injects the same rules as this skill. Nothing to set up, for any of the four providers. |
| In another tool (Claude Code terminal or VS Code extension, Cursor, Codex, Claude Desktop, …) | Connect to the running Creator Hub — see . The user copies a JSON snippet from Settings > AI > Expose AI assistant MCP server. Skills are not wired up for you here — see Where the skills come from. |
In Claude Code the tools appear as
; inside the Creator Hub's assistant and in other clients they appear under their bare names (
,
, …). The connected tools are self-describing — treat the live catalog as authoritative over the table below.
Files the user attaches to a prompt
Inside the Creator Hub's assistant the user can attach files to a turn (creator-hub
): the
Attach files paperclip left of the composer input opens a multi-select picker, files can be dropped onto the composer (overlay:
Drop files to attach), and images can be pasted from the clipboard. Attachments appear as removable chips;
8 per prompt is the cap. What this means for you:
- A file picked or dropped from disk arrives as its real absolute path — no copy, no base64 round-trip, no size cap. Read it in place; do not ask the user to re-paste its contents or to move it into the project. A pasted (clipboard) image has no path, so the app writes it to a temp file and hands you that path instead.
- Any file type is accepted. The app classifies it only for the chip icon: (png, jpg, jpeg, gif, webp, bmp, svg, avif), (glb, gltf), (mp3, wav, ogg, m4a, flac), for everything else.
- This is the natural way to hand over a reference image or a GLB. A dropped is a model the user wants in the scene: it is still outside the project, so copy it under before referencing it from (see add-3d-models for the bounding-box and collider audit). A reference image is usually art direction for a UI or material, not a file to import.
- A prompt may arrive with attachments and no text at all — the attachment is the request. Ask what to do with it rather than guessing.
RULE — the MCP is the way to change the scene graph; the file is the fallback
Never write assets/scene/main.composite
, , or by hand while the Creator Hub MCP is available. Reasons, all verified in the Creator Hub source:
- The editor owns those files. It regenerates wholesale from its in-memory engine on every autosave (~100 ms after any editor transaction) and never re-reads the file from disk. A hand edit to an open scene is silently lost; there is no error.
- The tools do the bookkeeping the file workflow makes you do by hand: registration, ,
inspector::TransformConfig
, , entity-id allocation, on non-core components.
- Every mutation is undoable by the user (Ctrl+Z / the assistant's Undo AI changes button). A file edit is not.
Decision ladder — pick the first row that matches:
- Tools present and answering ( or returns) → use them for every entity/component/settings change. Read the composite from disk only as a read-only cross-check.
- Tools present but failing (
No editor window is open.
, connection refused, ) → the Creator Hub is closed, was restarted (URL and token rotate per launch), or no scene is open. Say so and ask the user to open the scene in the Creator Hub (or re-copy the connection snippet). Do not fall back to editing the file silently.
- Tools absent, but the user has the Creator Hub → tell them the MCP is the safe route and point them at . Hand-edit the file only if they decline, and only with the scene closed in the Creator Hub (the composites skill's edit-mode rules then apply in full).
- No Creator Hub in play (CLI-only project, CI) → the composites skill's file workflow applies.
What still lives in files and is yours to edit directly:
(scene code; saving triggers a rebuild automatically),
(script components — write the file, then
), and asset files you add under
,
, etc.
RULE — search the catalog before manually building any standard object
Before creating an entity and manually assembling its components with
+
,
always first to check whether a Smart Item already exists for the object. This applies especially to:
- Lights — the catalog has Spotlight and Point Light Smart Items (category ) that bundle a real GLB model, a pre-configured component, and built-in Turn On / Turn Off / Toggle actions. Manually creating a entity misses the visual model, inspector integration, and action wiring.
- Interactive furniture — doors, chairs, platforms, chests, buttons, levers, etc.
- Media — screens, image displays, NFT frames.
- Triggers — trigger areas, click triggers. The category has exactly one entry named "Trigger Area" (
e9fa0eab-44de-4efe-af77-a71171a1a73f
), a script-based item: an invisible box or sphere the user places and resizes, with a control that seeds the AI-assistant composer with a prompt describing what should happen on enter/leave. The older non-script item of the same name (1ab2733f-1782-4521-9eda-6aa8ad684277
) and the interim label "Trigger Area (Script)" were both removed in creator-hub — do not search for either.
handles all the boilerplate that manual
calls miss:
- Downloads the item's GLB files and resolves with the real on-disk path (e.g.
assets/asset-packs/spotlight/spotlight.glb
).
- Sets up , , and components so the item works in the Creator Hub UI with configurable actions and triggers.
- Registers the entity in automatically.
Only build manually when no catalog item fits the requirement.
Media the user already has: tell them to drag it from the Local Assets tab onto the viewport. Since creator-hub
that is the fastest path and it spawns a
catalog item pointed at their file, not a bare component — so the entity arrives with the same component set and basic-view
a hand-placed item has:
| Dropped file | Item spawned | What gets repointed |
|---|
| / / | Image (37460a1e-affc-4f87-b725-118cc11d86fc
) | the PBR texture ; the plane is also lifted so it stands on the ground instead of being half-buried |
| / / | Ambient Sound - Forest Birds (5c8b4646-6ec0-41a0-8e9b-415a58728a9e
) | |
| Video Screen (0201653b-bd38-48af-b9ef-a902a7e8bc9c
) | and asset-packs::VideoScreen.defaultURL
— the admin message bus re-seeds from at runtime, so setting only is silently reverted |
The entity is named after the file with the extension stripped, de-duplicated against existing names, and the template's own placeholder media file is skipped on import.
/
still drop as plain models. You can reproduce this through the tools (
the same asset id, then
the field above), but the drag is one gesture for the user — prefer it when they are at the keyboard.
Tool catalog
Names as registered on the
server (
packages/creator-hub/main/src/modules/scene-mcp.ts
).
| Group | Tool | What it does |
|---|
| Read | | scene.json metadata (name, parcels, base, spawn points), SDK version, dependencies. Use it for Step 0 bounds. |
| Read | | Roster of authored entities: id, name, kind, world transform, component list, GLTF src, Smart-Item flag. Truncated to the first 200 entities. |
| Read | | Every component value on one entity, by id or Name. Call it to learn a component's exact shape before . |
| Read | | Entities currently selected in the editor. Resolve "this" / "the selected one" with it before acting. |
| Read | | Live budget (triangles, entities, bodies, materials, textures) vs per-scene limits, plus entities out of bounds. |
| Read | | Image of the editor viewport (no preview needed). |
| Mutate | | New entity, optional and id. Returns the id. |
| Mutate | | Deletes an entity and its children. |
| Mutate | | Reparents (world position preserved). = scene root. |
| Mutate | | Create or update a component. accepts short () or full () names; is the same JSON the composite stores. On update the keys you pass are merged. |
| Mutate | | Removes a component by name. |
| Assets | | Search the Creator Hub asset-packs catalog by name/category/tag. Despite the name it covers static items as well as Smart Items. Omit to list everything. |
| Assets | | Place a catalog item by at a world (default 8,0,8). Downloads the item's files into assets/asset-packs/<pkg>/
and spawns it — Smart Items arrive with their behaviour. Most items now arrive as code (no ); configure them through the Script's params, and wire "When X" hooks via their optional params. See script-components. |
| Scripts | | Adds an component pointing at a file. Write the file first (under ), then call this. Optional . |
| Settings | / | Read / change scene.json fields (name, description, categories, tags, age rating, spawn points, skybox, terrain, , flags). Each passed field replaces the current one wholesale — read first. The is editor-managed; leave it. |
| Preview | , , | Start / poll / stop the scene in the Decentraland Explorer with its MCP server on. |
| Preview | (dynamic), | While a preview runs, the Explorer's runtime tools (, , , , …) are registered live; explorer_call(tool, arguments)
is the fallback if they haven't appeared yet. |
| Chat | | Blocks on a question shown in the Creator Hub chat. Only meaningful inside the embedded assistant. |
Workflow
Read before you write. for parcels (compute bounds exactly as in the
composites skill Step 0 — the tools do not stop you from placing entities outside the scene, and
is a weaker safety net than it looks — see the Bevy-renderer gotcha below).
to see what exists and avoid duplicate names.
whenever the user's request refers to what they have selected.
Adding a model.
- Catalog item → then . Files land in
assets/asset-packs/<pkg>/
automatically.
- Architecture (rooms, corridors, doorways, anything the avatar walks through or into) → size the envelope for the third-person camera before placing; realistic dimensions read cramped in-world. The dimensions are in add-3d-models → "RULE: Scale interiors up for the third-person camera".
- Model the user already has, or one you authored → put the GLB under (see the add-3d-models skill for Blender authoring and the bounding-box audit), then (with a ), (
{position:{x,y,z}, rotation:{x,y,z,w}, scale:{x,y,z}}
), and ({src, visibleMeshesCollisionMask, invisibleMeshesCollisionMask}
— mask rules and the animation/collider checks from add-3d-models still apply; add when the GLB has clips).
- Lights → with category to find Spotlight and Point Light Smart Items, then . These include the light model, , and toggle actions. Only create a bare entity manually when you need an invisible light source or non-standard parameters -- see lighting-environment for the full API and the Smart Item rule.
- Behaviour on it → a Smart Item if one fits ( first), otherwise write a script under and (rules in script-components), or reference the entity by name from code (
engine.getEntityOrNullByName
).
Component shapes. validates against the real schema. Before your first write of a component type,
on any entity that already has it — or read its entry in the
composites format catalog. For
, params are positional inside
(see
script-components); prefer
and let the editor build the layout.
After writing. Read-after-write is safe
while the data layer is alive — the server waits for the autosave before returning from a mutation, so a following
is fresh. It stops being safe the moment that process dies (next rule), which is why the check below is on disk and not on a tool answer. Check
when you added geometry.
shows the result without a preview;
+
when you need the running scene (walk, click, logs, FPS). The iteration loop, camera framing, and performance references in the
unity-explorer-mcp skill apply to the
tools unchanged — skip that skill's Setup section (no
, no bind gate) because the tools arrive through the
server. When done, leave the camera in third person and
if you launched it only to check.
Mistakes. Undo is the user's — tell them what to undo, or
/
back to the previous value. Never "repair" a mistake by editing
.
RULE — confirm a batch landed on disk; the data layer can die silently
The thing that holds the editor's engine and writes
assets/scene/main.composite
is the
@dcl/inspector data layer, and under the
Bevy renderer it runs in a
separate child process — logged by the app as
[UtilityProcess] Running "sdk-commands start --port <n> --no-browser --no-client --data-layer"
(assembled in
main/src/modules/bevy-realm.ts
; the port is ephemeral, the binary is the
scene's node_modules/@dcl/sdk-commands
). Under the Babylon renderer the same data layer runs inside the inspector iframe instead. Either way the Electron main process never writes the composite — it only reads it.
When that data layer dies, the MCP server in the Electron app stays up and keeps answering every call as if nothing were wrong. Nothing in a tool result tells you the scene stopped changing.
So after every batch of mutations, check the file, not the tool: assets/scene/main.composite
's modification time must have advanced (
/
). An unchanged mtime across a batch means the edits exist only in a process that is gone.
bash
stat -f '%m %N' "assets/scene/main.composite" # macOS; coreutils: stat -c '%Y %n'
powershell
(Get-Item "assets\scene\main.composite").LastWriteTime # Windows (PowerShell)
Observed symptoms of a dead data layer (2026-10-08 session; check these when the mtime is stale):
- , , and friends all still return success.
- / keep returning the pre-crash values — a read-back "confirming" your write is confirming the old snapshot.
- returns a blank image.
- returned all zeros with an empty — but do not use that as the tell — under the Bevy renderer those fields are hardcoded zeros anyway (see Gotchas). The mtime check is the reliable one.
Recovery is the user's, and your in-memory edits are lost. There is no tool that restarts the data layer. Stop writing, look at the Creator Hub main log (macOS:
~/Library/Logs/creator-hub/main.log
; Windows:
%APPDATA%\creator-hub\logs\main.log
) for an
Exiting "sdk-commands start … --data-layer" … exit code=1
line, tell the creator to
close and reopen the scene in the Creator Hub, then
re-apply every mutation made since the crash — compare against the composite on disk to find where the truth stops.
Observed crash, for recognition only — not a claim about the only way it fails: TypeError: Cannot read properties of undefined (reading 'deserialize')
inside
node_modules/@dcl/inspector/dist/tooling-entrypoint.js
(
→
→
→
), immediately after a
call that rewrote
with range-valued (
) positions, and shortly after six
calls. Treat range spawn points as a
suspect, not a proven cause; if you hit it, report it via
report-sdk-issue.
Preview lifecycle — does not close the Explorer
never closes the Explorer — by design, and there is no tool that does. Verified in
main/src/modules/explorer-gateway.ts
: it closes the MCP client/transport and
s the
sdk-commands start --explorer-alpha --hub … --mcp --mcp-port <n>
child (logged as
[UtilityProcess] Killing process "…"
), then answers
. The Explorer app is
not in that process tree — sdk-commands launches it with
, which hands off to the OS and exits — and nothing in the Creator Hub kills it.
does not notice, either:
is computed from
the gateway object existing plus
on that same sdk-commands handle —
not a probe of the Explorer or its port. So "no preview running" is a statement about the dev server, never about the app.
Why the next launch then fails — verified parts first: each
grabs a
fresh ephemeral MCP port (
[Gateway] launching preview … with MCP on :53880
) and passes it as
, and the desktop Explorer is
single-instance, so a second launch's deep link is handed to the window already open. The inference (not read off the Explorer source) is that the open window keeps its MCP on the
old port and never binds the new one. Either way, after ~45 s of retries the Hub returns:
The preview launched but its MCP server never answered. The Explorer may still be starting, or this build has no MCP support. (TypeError: fetch failed)
and — note — that failure path
also kills the dev server it just started, leaving the Explorer orphaned on a dead realm. Observed 2026-10-08: two launches in a row reproduced it. (Related:
can block up to
90 s polling an Explorer that is up but not ready, so a long hang there is not a crash.)
Rule — before , check both the tool and the OS:
bash
pgrep -fl '[M]acOS/Explorer' # the Explorer app itself
pgrep -fl '[d]cl_watchdog' # its watchdog, from /Applications/Decentraland.app
Keep the brackets in these patterns.
/
match against every process's full command line, and many agent shell tools run commands as
, so a plain pattern also matches the wrapper shell: the check reports a live Explorer that isn't there, and
kills the shell running it.
still matches the real process, but never the command text itself.
saying "not running" is
not evidence the Explorer is closed. If either process is alive and you need a clean preview, close the stale instance,
confirm it exited, then
once:
bash
pkill -f '[d]cl_watchdog'; pkill -f '[D]ecentraland.app/Contents/MacOS/Explorer'
for i in $(seq 1 30); do pgrep -f '[M]acOS/Explorer' >/dev/null || break; sleep 1; done # wait up to 30 s
On
Windows the Explorer is
and the watchdog
. In PowerShell (matching by process name, so the check never matches the shell running it):
powershell
Get-Process Decentraland, dcl_watchdog -ErrorAction SilentlyContinue # is either alive?
Stop-Process -Name dcl_watchdog -Force -ErrorAction SilentlyContinue # watchdog first: it reacts to a non-zero Explorer exit
Stop-Process -Name Decentraland -Force -ErrorAction SilentlyContinue
Wait-Process -Name Decentraland -Timeout 30 -ErrorAction SilentlyContinue # confirm it exited
Connected first try on both attempts after that. Prefer reusing a healthy running preview (hot reload +
) over restarting — only kill when it is stale or the MCP never answered.
When the user owns that Explorer window, ask before killing it.
A re-baked GLB can keep rendering its old contents in a running preview. Not the dev server's doing:
7.30.1 re-walks the project on every content request, serves current bytes
, versions each file's id by
, and runs a chokidar watcher the Hub never disables — so the staleness is
downstream, in the Explorer's own asset cache. Observed 2026-10-08: GLBs added or renamed after
were missing in-world until
(one case) or a full preview restart (another), and a GLB
overwritten in place under the same name kept showing the old mesh.
hot reload is unaffected. Practical rule: after re-baking a model, export it under a
new file name and repoint
— that is the form that reloaded reliably; otherwise
and, if the old mesh persists, restart the preview. (Symptom: the mesh you just exported is absent or visibly stale while
shows no load error. Also check
— an ignored path is never served at all.)
Where the skills come from
How these skills reach the agent depends on which of the two setups above you are in.
Inside the Creator Hub's AI assistant the app installs them — never hand-copy them. The installer lives in
packages/creator-hub/main/src/modules/skills.ts
:
- It downloads main into a per-user cache (keyed on the upstream commit SHA plus the app version, swapped atomically, tolerant of being offline).
- It symlinks that cache into the open scene as and . Links the app created are added to a . If the user already has a real directory, it links each skill in individually and skips names the user already owns.
In that setup, do not copy skill directories into the scene and do not tell the user to: a hand-placed copy shadows the managed symlink and then silently goes stale; the app is the updater.
From an external client (Claude Code CLI or VS Code extension, Cursor, Codex, Claude Desktop, …) the skills are the user's to install. The MCP snippet only connects the tools; it does not bring the skills along. Two cases:
-
If the Creator Hub currently has the scene open, the app-managed
/
symlinks already exist in the scene folder, so a Claude Code session started
in that folder picks them up. Do not duplicate them.
-
Otherwise (a different working directory, the scene not open in the Creator Hub, or a client that reads skills from elsewhere), install them the normal way, from the scene folder or the user's skills root:
bash
npx skills add decentraland/sdk-skills --all
Re-run the same command to update;
only refreshes skills already present and misses newly added ones. See the repo
README for per-skill installs.
Four skills are deliberately denylisted inside the Creator Hub agent: ,
,
,
. The Creator Hub owns scaffolding and publishing through its own UI, and it is SDK7-only. The installer handles them in two ways:
- A denylisted skill that has supporting files (, ) ships without its — its stay resolvable from other skills' relative paths, but the skill itself can never trigger.
- A denylisted skill whose directory holds nothing but (, ) is skipped entirely.
If a user inside the Creator Hub asks to scaffold a project or publish, point them at the app's own flow rather than trying to load or reconstruct those skills. A manual
install from an external client is not denylisted: all four skills are available there.
Gotchas
- URL and token rotate on every Creator Hub launch. A config that worked yesterday gives or today; the fix is re-copying the snippet from Settings, not retrying.
- One open project at a time. The server operates on the scene open in the single editor window. Mutation tools answer
No editor window is open.
when the user is on the scene list.
- Disk-read tools may say from an external client even though the scene is visibly open: as of the initial release (creator-hub , PR #1499) the project directory is bound to / / when the embedded assistant runs a turn. Everything routed through the editor (, , , all mutations, ) works regardless. Workaround: read and
assets/scene/main.composite
from disk read-only for the roster, then mutate through the tools.
- Read tools trail the engine by one autosave (~100 ms). The mutation tools already wait it out; only matters if the user is dragging things while you read.
- caps at 200 entities. For big scenes filter by name via or read the composite from disk.
- Skill denylist inside the Creator Hub. The app links into the scene as / , but drops the of , , , and : scaffolding, publishing, and SDK6 migration are the app's own flows there. Their reference files still resolve for cross-links.
- The Explorer preview checkbox is a different server. Enable MCP Server lives in the Play Options popover (the dropdown arrow on the editor header's Play button), in the flyout that opens when you hover the Desktop Client row. It launches the Explorer with its own MCP on port 8123 (the unity-explorer-mcp skill). The Creator Hub MCP described here is the editor's server; when it is connected, use its instead of that checkbox.
- One Ctrl+Z = one of your operations, not one component write. Since creator-hub a whole synchronous change burst commits as a single undo transaction (the old 200-operation batch cap used to split a big change across several entries, so one Ctrl+Z left orphaned entities behind). A multi-entity op — placing a composite Smart Item, a scripted item with children — is now one entry, and undoing an add removes the entity from the Bevy viewport too. Still describe your changes to the user in the units they will undo them in.
- Scene audio can be muted in the editor, but only under Bevy. The viewport toolbar has a speaker toggle (Mute scene audio / Unmute scene audio) that forwards every / to the renderer at volume 0; the state survives a reload. It is absent under the Babylon renderer, which does not play scene audio at all. So "I can't hear the sound you added" is not evidence the component is wrong — check the renderer and the toggle first, and note that an authored is not what the Bevy viewport is playing while mute is on.
- reports nothing useful under the Bevy renderer. Verified in
packages/inspector/src/lib/renderer/bevy/BevyRenderer.ts
: returns {triangles: 0, bodies: 0, materials: 0, textures: 0}
and getEntitiesOutsideLayout()
returns , both hardcoded ("No renderer to introspect yet — report zeros"). The limits are still real, and is derived from the Nodes tree rather than the renderer, but all-zero content metrics and an empty mean "Bevy", not "empty scene" and not "problem" — do not read a budget or an out-of-bounds verdict out of them. Under Babylon they are real: computeEntitiesOutsideLayout
tests each entity's bounding mesh against the layout, so it does catch oversized geometry there. For a trustworthy budget either way, and use explorer_get_scene_content_stats
.
- needs a resolved file path, not a template variable. The catalog stores paths with as a template; setting that string literally via produces an invisible or broken gizmo because the engine cannot resolve the variable. resolves the path automatically (e.g. to
assets/asset-packs/spotlight/spotlight.glb
). If you must set manually, use the real on-disk path to the GLB file.