Skip to main content
Glama

cocos-mcp

A Streamable HTTP MCP server that runs inside the Cocos Creator editor process, so an AI assistant can drive the editor the same way you do: query the live scene graph, read and write assets, kick off builds, read the log, and look at a screenshot of what it just did.

Eleven tools. Zero dependencies. No build step. Tested against Cocos Creator 3.8.8.

If you landed here because the editor silently did nothing, go straight to Gotchas — that section is the real content.

Why eleven tools and not a hundred

The editor API is huge — scene alone declares 200+ messages. Wrapping each one as its own MCP tool means a giant tool list permanently occupying the model's context, and a maintenance burden every time Creator ships a release. Other Cocos MCP servers expose 100+ tools and then have to add "tool profiles" to hide most of them again.

Instead this exposes a few general tools plus one that reads the editor's own TypeScript definitions, so the model looks up a signature and then calls it. Everything the big servers do with create_label, set_node_transform, duplicate_prefab and so on is one editor_request call here.

Related MCP server: cocos-mcp-server

Install

  1. Copy this folder to <your-project>/extensions/cocos-mcp.

  2. In Creator: Extension → Extension Manager → Project, enable cocos-mcp. The console should print [cocos-mcp] http://127.0.0.1:1314/mcp.

  3. Point your MCP client at it. For Claude Code:

    { "mcpServers": { "cocos-creator": { "type": "http", "url": "http://127.0.0.1:1314/mcp" } } }

Ports

One editor instance per port. If you keep two projects open in Creator at once, the second one loses the race for 1314 and silently has no server, so give each project its own port — either via COCOS_MCP_PORT, or by dropping a .port file (gitignored) next to main.js:

echo 1315 > extensions/cocos-mcp/.port

Then point each project's MCP config at its own port. Keeping the server name the same across projects means the tool names (mcp__cocos-creator__*) stay stable wherever you are:

{ "projects": {
    "/path/to/project-a": { "mcpServers": { "cocos-creator": { "type": "http", "url": "http://127.0.0.1:1314/mcp" } } },
    "/path/to/project-b": { "mcpServers": { "cocos-creator": { "type": "http", "url": "http://127.0.0.1:1315/mcp" } } }
} }

Transport

Streamable HTTP (MCP spec 2025-03-26 and later), which replaced the two-endpoint HTTP+SSE transport from 2024-11-05. One endpoint; POST returns application/json — the spec makes the SSE stream optional, and a single JSON object is the other allowed answer. GET returns 405, which is what the spec prescribes for a server that offers no stream.

Request

Response

Spec

POST a request

200 application/json

server MUST return either this or text/event-stream

POST a notification (no id)

202, empty body

required

GET

405

allowed when the server offers no SSE stream

unsupported MCP-Protocol-Version

400

required

no MCP-Protocol-Version

served, assumed 2025-03-26

required

any request carrying Origin

403

Origin validation is required — see Security

SSE would only earn its place if the server needed to push without being asked. Build progress is polled through build_status instead, which is simpler and enough. The one case that would benefit is build with wait: true, which currently blocks for the whole build.

Tools

Tool

What it does

editor_request

Editor.Message.request(pkg, method, ...args) — the entire editor API

editor_eval

Async JS in the editor main process; Editor and require in scope

scene_eval

Async JS in the scene process; cc and the live scene graph in scope

editor_api

Greps the editor's .d.ts files for a message signature

asset_info

One asset: size, sub-assets, resolved referencers, dependencies

build

Starts a build, reusing the last task's options; overrides to change fields

build_status

Task state, progress, and the messages carrying build errors

editor_log

Tails the project / builder / asset-db log with a regex filter

screenshot

Captures an editor window as a PNG image block

preview

Preview server URL, platform, and connection count

reload

Reloads this extension so code edits take effect

Resources

cocos://project, cocos://scene/active, cocos://build/latest, cocos://log/recent.

Gotchas

Every bug found while building this had the same shape. The editor rarely throws — it returns something that looks like success, and you find out later. Half of these are Cocos's behaviour, half are mistakes this extension made itself while working around the first half.

What you see

What actually happened

undefined from execute-scene-script

the scene script exported its functions at the top level instead of under methods

SUCCESS (queued)

a build is already running; yours is waiting behind it

SUCCESS (queued), then a failed task

the platform was invalid — nothing says which option, and detailMessage is empty

free: true

a build is actively processing

bytes: 960 on a folder

the directory inode, not the 62 KB inside it (our bug)

a blank line from a log

the file ended with a newline (our bug)

a green test run

listen() lost the port and every assertion ran against a live editor (our bug)

127.0.0.1 in the bind call

any web page could still POST here and run code (our bug)

truncated, 25000 of 25000 chars

the notice never said anything was cut (our bug)

a missing .port file

the test suite deleted it in a finally block (our bug)

no match from editor_api

it was only searching a quarter of the API (our bug)

The practical consequence: read every "it worked" twice, and when adding a tool here, ask what its failure looks like before asking what its success returns. The details behind each row follow.

A scene script must export methods. Exporting the functions at the top level makes execute-scene-script return undefined — silently, with no error, even though the extension is enabled and contributions.scene.script is correct.

exports.methods = { run(code) { /* ... */ } };   // right
module.exports = { run(code) { /* ... */ } };    // silently never called

A reference index can name assets that are not in this checkout. query-asset-users returns uuids, and some of them resolve to nothing — other branches that share the same asset folder, or deleted files. On a multi-game repo where every branch checks out only its own game code but shares assets/common/, this is routine: one shared image reported 61 referencers on one branch and 94 on another, plus 15 unresolvable either way. asset_info counts those separately as ghostUsers instead of silently inflating the total.

A zero reference count is not permission to delete. It only means nothing in this checkout references it. Five images that looked dead on one branch turned out to be used by another game on a sibling branch.

scene_eval returns must be JSON-serializable, and undefined used to be ambiguous. The value crosses a process boundary, so returning a live Node/Component/Scene fails with a circular-structure error — return node.name or node.children.map(n => n.name) instead. And undefined means either "your code has no return" or "the scene script was never registered", the second being completely silent; scene_eval now pings the scene script before accepting an undefined and tells you to reload if it is dead.

@cocos/creator-types only covers a fraction of the API. The bundled types declare ~51 scene messages; the running editor has 200+. add-task, query-preview-url and many others are declared only in each installed package's own @types inside CocosCreator.app. editor_api scans both.

add-task returns two different enums. With shouldWait: false it returns TaskAddResult (1 = SUCCESS); with true it returns BuildExitCode (36 = BUILD_SUCCESS). Same integer space, different meanings. build decodes whichever applies.

Build warnings are not in build_status. Its detailMessage only carries the last hook name. Real warnings and errors go to temp/logs/project.log — use editor_log.

A second build queues silently, and free does not tell you. add-task returns SUCCESS even when a build is already running — the new task just sits there with Wait a moment, build task is busy in its message. query-tasks-info().free stays true throughout, so it cannot be used to detect this; check task state for processing/waiting instead. build now reports queuedBehind with those ids. Cancel with editor_request builder break-task [id].

Bad build options queue successfully, then fail silently. add-task accepts anything — an invalid platform gets a cheerful SUCCESS (queued), and the task only fails later with 构建参数校验失败 and an empty detailMessage, so nothing tells you which option was wrong. build now runs options through the builder's own check-and-complete-options first, which rejects them up front and lists the valid platforms. That call also completes every required field and follows taskName to outputName, so it beats merging a previous task's options by hand.

Build detail lives in the builder's own log. temp/logs/project.log gets the warnings, but editor_log with source: "builder" reaches temp/builder/log/, which has per-stage timings and memory tracking. Those files run to megabytes, so only the tail is read.

Reloading needs a deferred disable/enable. The reload tool handles it: it replies first, then tears the server down 300 ms later — disabling the package kills the HTTP server, so doing it inline loses the response. Keep-alive sockets would otherwise hold the port across a reload, so unload() destroys them explicitly. Scene scripts are require-cached, so editing scene-script.js does nothing until you reload.

Large results are truncated at 20 000 chars. Do not retry a truncated call as-is — query-assets on a folder blows the cap with a dozen assets. Loop inside editor_eval and return only the fields you need; the round trips happen inside the editor and cost nothing.

Driving the running game is a browser automation job — preview just hands over the URL. Two things that bite when clicking the canvas from Puppeteer/Playwright:

  • The engine registers mousedown/mouseup on the canvas (pal/input/web/mouse-input.ts). Synthetic PointerEvents are ignored.

  • cc.UITransform is undefined on the preview page's cc global; use node.worldPosition, or getComponent('cc.UITransform') by string. And re-measure getBoundingClientRect() every time — the preview's device toolbar collapses and shifts the canvas.

const cam = cc.director.getScene().getComponentInChildren('cc.Canvas').cameraComponent;
const s = cam.worldToScreen(cc.find('Canvas/path/to/node').worldPosition);
const r = document.querySelector('canvas').getBoundingClientRect();
const base = { bubbles: true, cancelable: true, view: window, button: 0,
    clientX: r.x + s.x / devicePixelRatio,
    clientY: r.y + (r.height - s.y / devicePixelRatio) };  // Cocos origin is bottom-left
canvas.dispatchEvent(new MouseEvent('mousedown', { ...base, buttons: 1 }));
canvas.dispatchEvent(new MouseEvent('mouseup',   { ...base, buttons: 0 }));

Tests

Runs offline against a stubbed Editor — no editor needed:

node test.js

It binds port 19314 and aborts if that is taken, so it can never accidentally run its assertions against a live editor. Override with COCOS_MCP_PORT.

Security

editor_eval and scene_eval execute arbitrary code with full editor privileges, so the server has to assume any request could be hostile.

The spec is explicit about which of these is which: validating Origin is a MUST, binding to localhost is only a SHOULD. This server originally did the SHOULD and skipped the MUST — and said so in this very section, as if the SHOULD were the guarantee.

Binding to 127.0.0.1 is not enough, and believing otherwise was a real hole here. A page the developer visits while Creator is open can POST to a loopback port: text/plain makes it a CORS simple request, so there is no preflight to block, and although the attacker cannot read the reply, the code has already run. Confirmed against an earlier build of this server — a cross-origin request executed require('os').hostname() inside the editor.

Two things guard it now: any request carrying an Origin header is refused with 403 (browsers always send one, MCP clients never do), and bodies over 4 MB are rejected before being buffered. Keep both. Anything that reaches this port can run code as the editor.

License

MIT

Maintenance

ActivityMaintained
ResponsivenessNo issues

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    F
    maintenance
    Enables AI assistants to directly control the Cocos Creator 3.8.x editor via MCP protocol, providing over 130 tools for scene, node, component, asset, and project operations.
    30
    39
    MIT
  • -
    license
    Not graded
    quality
    Not graded
    maintenance
    Enables AI assistants to interact with the Cocos Creator 3.8+ editor through standardized protocols for scene, node, component, prefab, asset, project, debugging, and server operations.
  • A
    license
    Not graded
    quality
    C
    maintenance
    Enables AI assistants to directly control the Cocos Creator game editor via MCP protocol, supporting scene management, node manipulation, component attachment, and asset management.
    30
    2
    MIT

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/az198071123/cocos-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server