Skip to main content
Glama
nguyennt02

cocos-mcp-v3-ui

by nguyennt02
README.md
# cocos-mcp-v3-ui

MCP server (UI-design tools) for **Cocos Creator 3.8.x**, built as an editor package. It
lets an MCP client (Claude Code) drive the editor to build UI — ideal for translating a
Figma design into a Cocos scene: read the design with the Figma MCP, then create
nodes/components/sprites/prefabs here.

Ported (not mechanically) from a 2.x `cocos-mcp-v2-ui`. 3.x differs fundamentally:
the scene bridge is the Promise-based `Editor.Message.request('scene','execute-scene-script')`,
node size/anchor/opacity/color live on components (`cc.UITransform` / `cc.UIOpacity` /
the render component — **not** on the node as in 2.x), and the package is plain CommonJS.

## Architecture

```
Claude Code ──stdio(MCP)──▶ bridge/  ──HTTP /rpc──▶ editor package (main proc)
                          (system Node,            core/server + registry + tools
                           @modelcontextprotocol)        │
                                                          ▼  Editor.Message.request
                                                             ('scene','execute-scene-script')
                                                   scene.js (scene proc, cc.* live)
```

- **Editor package** (`main.js`, `core/`, `tools/`, `scene.js`, `scene/`) runs inside
  Cocos Creator. `main.js` starts an HTTP server (`core/server.js`) exposing
  `GET /health`, `POST /rpc {tool,args}`, `GET /tools`, and a stateless `POST /mcp`.
  The server is **loopback-only and rejects browser-originated requests** (no `Origin`
  header, loopback `Host` only) so a web page cannot drive the editor (see `isLocalRequest`).
- **Bridge** (`bridge/`) is a separate Node process Claude spawns. It speaks the MCP
  protocol over stdio (via the official SDK) and proxies `tools/list`+`tools/call` to the
  editor's HTTP `/tools`+`/rpc`. Keeping MCP-spec compliance out of the editor process.
- **Single-responsibility / DRY**: tool modules are thin (schema + delegate). Everything
  crossing into the scene goes through one `core/scene-bridge.js#callScene`; one
  `core/response.js` shape; scene-side has one `resolveNode`, one `serialize`, one
  `applyTransform`. Each mutating tool records an editor `snapshot` (undo + dirty flag).

## Install

1. Symlink (or copy) this folder into the project's `extensions/` (or `packages/`):
   ```
   ln -s /Users/nguyennt/Documents/MCP/cocos-mcp-v3-ui \
         <your-cocos-3.x-project>/extensions/cocos-mcp-v3-ui
   ```
2. Install the bridge deps once: `cd bridge && npm install`.
3. Open the project in Cocos Creator 3.8.x. The package auto-starts the server on
   **port 8585** (`autoStart: true`). Panel: menu **Extension → Cocos MCP v3 UI → Open Panel**.

## Connect to Claude Code

```
claude mcp add cocos-ui --transport stdio -- \
  node /Users/nguyennt/Documents/MCP/cocos-mcp-v3-ui/bridge/bin/cocos-ui-bridge.js --port 8585
```
Verify: `/mcp` in Claude lists `cocos-ui` connected and the 36 tools. The editor must be
running with a scene open for scene tools to work (otherwise they return `No active scene`).

## Tools (36)

| Category | Tools |
|---|---|
| ping (1) | `ping` |
| scene (5) | `get_current_scene`, `get_scene_hierarchy`, `open_scene`, `save_scene`, `capture_node` |
| node (10) | `create_node`, `get_node_info`, `find_nodes`, `set_node_transform`, `set_node_property`, `set_node_properties_batch`, `create_node_tree`, `set_sibling_index`, `delete_node`, `duplicate_node` |
| component (13) | `add_component`, `set_component_property`, `get_components`, `remove_component`, `set_component_reference`, `add_click_event`, `add_components_batch`, `set_component_references_batch`, `add_click_events_batch`, `set_label_style`, `apply_widget`, `draw_rounded_rect`, `apply_gradient` |
| asset (2) | `import_asset`, `query_asset_uuid` |
| prefab (2) | `create_prefab`, `instantiate_prefab` |
| referenceImage (3) | `add_reference_image`, `set_reference_image_data`, `remove_reference_image` |

MCP tool name = `<category>_<method>` (e.g. `node_create_node`,
`referenceImage_add_reference_image`). A category must not contain an underscore — dispatch
splits on the FIRST underscore.

### Highlights (added per build-experience feedback)
- **`color` is forgiving**: `set_component_property`/`set_node_property`/tree specs accept `{r,g,b,a}`,
  `[r,g,b,a]`, or a hex string (`"#161718"`).
- **`create_node_tree`**: build an entire node + component + property subtree in ONE call (returns uuids).
- **`draw_rounded_rect`**: native rounded backgrounds/borders via `cc.Graphics` — no texture hacks.
- **`apply_gradient`**: bakes a rounded gradient PNG and binds it as a spriteFrame — real gradient
  buttons/badges (`cc.Graphics` fill is solid-only).
- **`set_component_reference`** + **`add_click_event`**: wire `ScrollView.content`, `Widget.target`,
  button handlers, etc.
- **Enum names**: enum props accept names (`type:"VERTICAL"`, `sizeMode:"CUSTOM"`) as well as numbers.
- **`import_asset`** auto-creates the destination folder, auto-rasterizes SVG-content files, and fails
  loudly instead of silently importing nothing. **spriteFrame** assignment preserves node size.

## Figma → Cocos workflow

1. Read the design with the Figma MCP (positions, sizes, colors, text).
2. `scene_get_scene_hierarchy` → find the `Canvas` uuid.
3. (optional) export a screenshot from Figma → `asset_import_asset` →
   `referenceImage_add_reference_image { spriteFrameUuid }` to trace it.
4. `node_create_node` per element → `component_add_component` (`cc.Sprite`/`cc.Label`/
   `cc.Button`/`cc.Widget`/`cc.Layout`) → `component_set_component_property`
   (string, fontSize, color, spriteFrame) → `node_set_node_transform` to place/size.
5. For images: `asset_import_asset` returns **`spriteFrameUuid`** — pass that to
   `set_component_property property:"spriteFrame"` (it persists; the texture uuid does not).
6. Reusable bits: `prefab_create_prefab` → `prefab_instantiate_prefab`.
7. `scene_save_scene`.

## Notes & limitations (3.x specifics)

- **`capture_node` (edit-mode)** — 3.x has no immediate-mode `camera.render(node)`, so we
  render through a temporary off-screen `cc.Camera` + `cc.RenderTexture` framed on the node's
  world bounds, force a frame, then `readPixels()` (falls back to borrowing the scene camera).
  The scene side returns base64 RGBA (bottom-up); the main side flips + PNG-encodes to disk and
  returns the path. Works in edit mode at the node's own aspect; `maxSize` caps the longest side
  (default 720). You can also verify by DATA with `get_scene_hierarchy` / `get_components`.
- **One render component per node (3.8.x)** — `cc.Sprite`, `cc.Graphics`, `cc.Label`,
  `cc.RichText` all derive from `cc.UIRenderer` and remain **mutually exclusive** on a node
  (verified: adding `cc.Sprite` next to `cc.Graphics` throws a conflict error). So a node can't
  have both `draw_rounded_rect` (Graphics) and `apply_gradient`/`spriteFrame` (Sprite) —
  `apply_gradient` auto-routes to a child `gradient_bg` node when the target already has a
  render component, and `add_component` surfaces the engine's conflict message.
- **Reference image is simulated** — Cocos has a native reference-image gizmo, but to keep the
  workflow we add a low-opacity `Sprite` node `__ReferenceImage__` behind the UI. Remove it
  before shipping (`remove_reference_image`); it is a real scene node.
- **Editor Hierarchy/Inspector** refresh on the next reselect/reload after raw-runtime
  mutations. Each mutating tool records a `snapshot` (so undo works and the scene is marked
  dirty); the live scene/canvas updates immediately.
- **No main-process hot-reload**: editing `main.js`/`core/`/`tools/`/`scene/` during
  development requires a full editor restart (Developer → Reload Extensions/Editor). At runtime
  this is irrelevant — the server starts on project open.
- **spriteFrame**: importing a PNG yields a texture *and* a `sprite-frame` sub-asset. Bind the
  **sub-asset** uuid (returned as `spriteFrameUuid`). In 3.x these sub-assets have stable uuid
  suffixes (`<uuid>@f9941` spriteFrame, `<uuid>@6c48a` texture); `assetdb.importFiles` verifies
  them via `asset-db:query-url` before returning.
- Verified editor messages (3.8.x): scene `execute-scene-script` / `save-scene` / `open-scene` /
  `snapshot`; `asset-db:query-uuid` / `query-url` / `import-asset` / `create-asset` /
  `refresh-asset`. Prefab create uses the scene `create-prefab` message where available, with a
  manual drag-to-Assets fallback.

## Security

The HTTP server binds `127.0.0.1` and **only** serves loopback requests that carry no `Origin`
header (i.e. the Node bridge, not a browser). This prevents a malicious web page from POSTing to
`http://127.0.0.1:8585/rpc` to drive the editor or write files, and the loopback `Host` check
blocks DNS-rebinding. Do not expose the port beyond localhost.

## Tests

- **Unit** (pure logic — colors, enums, PNG encoder, gradient, SVG sniff; no editor needed):
  ```
  node test/unit.test.js
  ```
- **Integration** (error/edge paths for the scene & component tools; needs the editor running with a
  scene open — creates a temp container node and deletes it afterwards):
  ```
  node test/integration.test.js --port 8585
  ```
- See `test/manual-checklist.md` for the per-tool manual verification steps.