Skip to content

add: Creator Hub Scene Agent - #1499

Open
nicoecheza wants to merge 18 commits into
mainfrom
add/mcp
Open

add: Creator Hub Scene Agent#1499
nicoecheza wants to merge 18 commits into
mainfrom
add/mcp

Conversation

@nicoecheza

@nicoecheza nicoecheza commented Aug 20, 2026

Copy link
Copy Markdown
Member

Plan

Adds an AI assistant to the scene editor. It drives your own installed claude or codex CLI to build scenes for you. It reads the scene, writes SDK7 code, adds and edits entities and smart items, and can run the preview to check its work. It uses your CLI subscription so there is no API key to set up

What you get:

  • Chat panel in the editor, toggle in the header
  • Reads the scene and writes code under src/
  • Creates and edits entities, components, smart items and scripts. All live in the viewport, autosaved, and undoable
  • One button "Undo AI changes" reverts a whole turn
  • Can launch the preview and drive it (screenshot, walk, click, read logs and perf)
  • Knows the scene metrics and what you have selected
  • Conversations persist per project and survive a restart
  • Works with claude and codex

Turning it on:
Off by default. Settings > Experimental > AI scene assistant

Notes:
No keys are stored. If you have not signed into a CLI you can opt in to using the API key from your environment (ANTHROPIC_API_KEY / OPENAI_API_KEY)

@socket-security

socket-security Bot commented Aug 20, 2026

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Added@​modelcontextprotocol/​sdk@​1.30.09910010095100
Addedzod@​4.4.310010010096100

View full report

@decentraland-bot decentraland-bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deep Review: Creator Hub Scene Agent (PR #1499)

An impressive and well-structured addition — the four-layer architecture (Chat Panel → Agent Runner → MCP Server → Explorer Gateway) is a solid foundation. The code is thoughtful, comments are extensive, and the existing test coverage (unit + E2E passing) gives confidence. That said, the deep review uncovered several findings that should be addressed before merge. Organized by severity:


P0 — Blockers

1. [Security] Signal-killed child process treated as successful turn
ai.ts:734code === null (process killed by signal — e.g. OOM-killer, external SIGKILL, or aiStop() race) calls finish(true). This means a turn that was forcibly terminated is reported to the user as "done successfully", and any partial mutations it made are not flagged for review. Should be finish(false) or finish(false, 'assistant was interrupted').

2. [Security] Unbounded request body in MCP HTTP server
scene-mcp.ts:658-665readBody() concatenates chunks with no size limit. While the server is localhost-only with bearer auth, a compromised or buggy MCP client can OOM the Electron main process. Add a body-size cap (e.g. 4 MB) and abort with 413.


P1 — Major

3. [Correctness] Stale scene reads immediately after mutations
scene-composite.ts:64-77 reads the composite from disk synchronously, but mutations flow through the live engine (operations.dispatch() → autosave ~100ms later). If the AI calls set_component then immediately scene_state, it reads pre-mutation data. The sceneOpChain serialization in scene-mcp.ts:139-147 orders mutations, but read tools bypass the chain entirely. Consider either routing reads through the engine or adding a small flush-wait after mutations.

4. [Security] filterEnvForChild() only strips AI-specific vars
ai.ts:180-216 — Combined with --permission-mode bypassPermissions / --sandbox danger-full-access, the spawned CLI inherits the user's full environment (AWS credentials, GH_TOKEN, NPM_TOKEN, SSH agent, etc.). The system prompt is the only guardrail, which is not resistant to prompt injection. This is an inherent design trade-off of the feature, but worth documenting explicitly — at minimum, add a code comment acknowledging this surface area and consider adding a user-facing warning in the settings UI about what "full capability mode" means.

5. [Correctness] Unvalidated entity as Entity casts on AI-supplied input
inspector/server.ts:297,303,309,316,319,329,345 — Every entity as Entity and parent as Entity cast takes an arbitrary number from the AI model and brands it without validation. An invalid entity id (e.g. a hallucinated number) will silently corrupt the CRDT or throw deep in the ECS. Add engine.entityExists() guard before casting.

6. [Reliability] No Explorer gateway reconnection after crash
explorer-gateway.ts:119-139 — The initial MCP connection retries, but once gateway.client is established there is no disconnect/error handler. If Unity Explorer crashes mid-session, callExplorerTool throws on every subsequent call with no recovery. The gateway stays "running" with a dead client until a manual stop_preview.

7. [Reliability] launching serialization is project-unaware
explorer-gateway.ts:292-298 — A single module-level launching promise serializes concurrent calls. If a second call arrives for a different project while the first is in-flight, it silently returns the first project's result. The guard should check whether the in-flight launch matches the requested projectDir.

8. [Reliability] Session leak — no TTL or cap on MCP sessions
scene-mcp.ts:617 — Sessions are created on each initialize but only removed on transport close. A client that opens sessions without closing them (or crashes) leaks server + transport pairs indefinitely. Add a max-sessions cap and/or idle TTL.

9. [Security] MCP config file permissions in tmpdir
scene-mcp.ts:785-803 — The --mcp-config JSON file containing the bearer token is written inside a mkdtempSync directory (0700), but the file itself uses default permissions (typically 0644). On shared systems, other users can read the token. Use fs.writeFileSync(file, content, { mode: 0o600 }).

10. [Type Safety] content as never suppresses all type checking
scene-mcp.ts:532,602content as never bypasses the MCP SDK's content type entirely. A schema change in @modelcontextprotocol/sdk will compile silently and crash at runtime. Map content blocks to the SDK's expected type, or use a narrower assertion.


P2 — Minor (non-blocking)

11. [Type Safety] Unvalidated JSON.parse with structural cast
ai.ts:492-494 — The sessions file is parsed and cast to Record<string, ProjectSessions> with no schema validation. A corrupt file will be spread into state. Add a shape guard.

12. [Correctness] jsonSchemaToZodShape drops nested object schemas
scene-mcp.ts:549-585type: "object" is treated as z.record(z.unknown()), losing any properties/required. The advertised schema is weaker than reality (tolerable since Explorer re-validates, but imprecise).

13. [Performance] Synchronous disk I/O on main process
scene-composite.ts:64-77 uses fs.readFileSync; skills.ts uses synchronous FS throughout. These block the Electron main process during large scene reads or skill cache refreshes. Consider async alternatives.

14. [Code Quality] Duplicated explorer-tool response handling
scene-mcp.ts:524-535 and 596-607 contain near-identical response-mapping logic. Extract a shared formatExplorerResult() helper.

15. [Code Quality] Boilerplate across scene-op tool handlers
scene-mcp.ts:301-455 — Eight tools repeat the same requestSceneOp → check !res.okfail()ok() pattern. A registerSceneOpTool() helper would cut ~100 lines.

16. [Code Quality] Inconsistent error stringification
Mixed use of String(e), e instanceof Error ? e.message : e, and template wrapping across scene-mcp.ts and explorer-gateway.ts. Consolidate into a small errMsg(e) helper.

17. [Correctness] GET/DELETE without session returns plain text, not JSON-RPC
scene-mcp.ts:740 — Returns "Missing or unknown session id" as plain text. Per JSON-RPC 2.0 / MCP spec, this should be a JSON-RPC error envelope.

18. [Cleanup] Stale MCP config file not cleaned on shutdown
scene-mcp.ts:784configPath is never cleared by stopSceneMcpServer(), so stale token-containing files accumulate in os.tmpdir().

19. [Code Quality] Magic number for stderr truncation
ai.ts:7288000 should be a named constant like MAX_STDERR_BYTES for consistency with other constants in the file.

20. [Code Quality] userSeq counter outside Redux state
slice.ts:102 — Module-level let userSeq = 0 resets on HMR while the store doesn't, risking id collisions in dev.


Consumer Impact

No breaking changes. The creator-hub package is "private": true. Changes to @dcl/inspector are purely additive (new RPC handlers). New AppSettings fields have defaults. Safe to merge from a compatibility standpoint.

CI Status

✅ lint, typecheck, unit tests, E2E (macOS) — all passing.
⏳ asset-packs/version — pending (non-blocking).

Review Agents Used

  • TypeScript conventions reviewer
  • Security sentinel
  • Architecture strategist
  • MCP/Agent-native reviewer
  • Patterns & simplicity reviewer

Verdict

Request Changes — The two P0 items (signal-killed turn reported as success; unbounded request body) and the P1 items (stale reads, unvalidated entity casts, no gateway reconnection, session leak, file permissions) should be addressed before merge. The P2 items are non-blocking suggestions for follow-up.

The overall architecture is well-designed and the code quality is high — this is a strong foundation for the AI assistant feature. The fixes needed are localized and straightforward.


Reviewed by Jarvis 🤖 · Requested by Nicolas Echezarreta (<@U04FKDVJ8JW>) via Slack

@github-actions

github-actions Bot commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Test @dcl/inspector package

  • Preview: link
  • Install via NPM:
    npm install "https://sdk-team-cdn.decentraland.org/creator-hub/branch/add/mcp/@dcl/inspector/dcl-inspector-7.38.0-commit-5ae602cb0602ce3e46a64bd6c09cb620c492aadb.tgz"

@github-actions

Copy link
Copy Markdown
Contributor

Test this pull request on macos-latest

Download the correct version for your architecture:

mac-x64
mac-arm64

Click here if you don't know which version to download

For running this unsigned version of the app, you will need to run the xattr command on it:

  1. Extract the app from the downloaded .dmg file (double-click it)
  2. Place the extracted app anywhere you like in your file system
  3. Open a terminal on the directory where the app is
  4. Run xattr -c app-name, replacing "app-name" for the actual name of the app
  5. Double-click the app ✅

@github-actions

Copy link
Copy Markdown
Contributor

Test this pull request on windows-latest

Download the correct version for your architecture:

win-x64

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants