cocos-web-inspector-mcp 0.1.7 → 0.1.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -98,19 +98,20 @@ The endpoint may instead be provided through the MCP process environment:
98
98
 
99
99
  ## Tools
100
100
 
101
- All tool input objects are strict. Unknown fields are rejected. Inspection tools are annotated as read-only, idempotent, non-destructive, and closed-world. The highlight tool is annotated as a non-destructive, non-idempotent, closed-world temporary mutation because repeated calls reset its removal timer.
101
+ All tool input objects are strict. Unknown fields are rejected. Inspection tools are annotated as read-only, idempotent, non-destructive, and closed-world. The highlight tool is annotated as a non-destructive, non-idempotent, closed-world temporary mutation because repeated calls reset its removal timer. Runtime debugger tools are non-read-only and closed-world; `cocos_click_node` is additionally destructive and open-world because game click handlers can reach real servers or make irreversible changes, and it and `cocos_step_frame` are non-idempotent.
102
102
 
103
103
  | Tool | Purpose | Inputs |
104
104
  | --- | --- | --- |
105
105
  | `cocos_list_pages` | List bounded summaries of eligible localhost pages, including sanitized URL, title, Cocos detection, version, and scene name. | None |
106
106
  | `cocos_runtime_info` | Return bounded engine, scene, canvas, view, director, and node-count information. | `pageUrl?` |
107
- | `cocos_runtime_diagnostics` | Return passive bounded hierarchy counts, depth, and duplicate names. Render metrics remain explicitly unsupported. | `pageUrl?` |
107
+ | `cocos_runtime_diagnostics` | Return passive bounded hierarchy counts, depth, duplicate names, and render metrics (FPS, frame time, draw calls, triangles, instances). | `pageUrl?` |
108
108
  | `cocos_set_node_active` | Set one node's active state. Registered only with `--allow-runtime-mutation`; returns before/after state. | `pageUrl?`; `uuid`; `active` boolean |
109
109
  | `cocos_set_transform` | Update supplied position, rotation, and/or scale fields for one node. | `pageUrl?`; `uuid`; `position?`, `rotation?`, `scale?` finite vectors |
110
110
  | `cocos_set_property` | Update one bounded public component data property. | `pageUrl?`; node `uuid`; `componentUuid`; `key`; primitive/vector/size/color `value` matching current shape |
111
111
  | `cocos_click_node` | Dispatch a real mouse click at the visible center of one UI node so Button/touch handlers run. Registered only with `--allow-runtime-mutation`. | `pageUrl?`; `uuid` |
112
112
  | `cocos_pause` | Pause the Cocos director when its public API supports it. | `pageUrl?` |
113
113
  | `cocos_resume` | Resume the Cocos director when its public API supports it. | `pageUrl?` |
114
+ | `cocos_step_frame` | Advance a paused game by 1–60 fixed-delta frames through `cc.game.step`, then stay paused. Fails unless paused first. | `pageUrl?`, `frames?` |
114
115
  | `cocos_scene_tree` | Return a bounded scene tree with node and component summaries. | `pageUrl?`; `maxDepth?` integer `0..20`, default `6`; `maxNodes?` integer `1..5000`, default `500` |
115
116
  | `cocos_find_node` | Find nodes with exact or combined bounded filters. | `pageUrl?`; at least one of `uuid`, `name`, `path`, `nameContains`, `componentType`, `active`, `pathPrefix`; `limit?` integer `1..100`, default `20` |
116
117
  | `cocos_get_components` | Return bounded component summaries for a node. | `pageUrl?`; `uuid` |
@@ -122,7 +123,7 @@ All tool input objects are strict. Unknown fields are rejected. Inspection tools
122
123
  | `cocos_wait_for_property` | Poll one top-level property until it strictly equals a primitive value or the timeout passes. | `pageUrl?`; `uuid`; `componentType?` or `componentUuid?`; `key`; `equals`; `timeoutMs?` `100..30000`, default `5000`; `intervalMs?` `50..5000`, default `200` |
123
124
  | `cocos_highlight_node` | Draw a temporary pointer-transparent overlay around a UI node. | `pageUrl?`; `uuid`; `durationMs?` integer `100..10000`, default `2000` |
124
125
 
125
- `cocos_runtime_diagnostics` does not enable profiler/statistics systems. FPS, frame time, draw calls, triangles, and generic invalid-reference checks return `UNSUPPORTED_PUBLIC_API` until stable passive public Cocos APIs are verified. `cocos_highlight_node` temporarily mutates the page DOM only. It does not mutate the Cocos node/component graph or game state. `cocos_capture_node` clips only to the visible browser viewport, never falls back to full-page capture, bounds captures by the visible viewport and encoded response size rather than a fixed pixel cap, returns PNG (or JPEG fallback) base64 in-memory, downscales down to 0.25× (reported as `scale`) when JPEG quality steps are not enough, and rejects responses still oversized after that. Mutation results provide `before` values for manual inverse calls, but restoration cannot undo lifecycle callbacks or other runtime side effects. Frame stepping is unsupported pending a verified public Cocos API compatibility matrix.
126
+ `cocos_runtime_diagnostics` does not enable profiler/statistics systems. Render metrics are read from the values `Root` and the GFX device already update every frame (the same sources as `root.fps` and `device.numDrawCalls`), whether or not the profiler is shown; draw calls include the profiler overlay when it is visible. Generic invalid-reference checks still return `UNSUPPORTED_PUBLIC_API`. `cocos_highlight_node` temporarily mutates the page DOM only. It does not mutate the Cocos node/component graph or game state. `cocos_capture_node` clips only to the visible browser viewport, never falls back to full-page capture, bounds captures by the visible viewport and encoded response size rather than a fixed pixel cap, returns PNG (or JPEG fallback) base64 in-memory, downscales down to 0.25× (reported as `scale`) when JPEG quality steps are not enough, and rejects responses still oversized after that. Mutation results provide `before` values for manual inverse calls, but restoration cannot undo lifecycle callbacks or other runtime side effects. `cocos_step_frame` uses the public `cc.game.step` (fixed `game.frameTime` delta); because `director.tick` skips logic while the director is paused, it resumes the director only for the synchronous step call and pauses it again.
126
127
 
127
128
  ### Page selection
128
129
 
@@ -198,7 +199,7 @@ npm run check
198
199
 
199
200
  `npm run check` type-checks, builds, runs the self-contained Node.js tests, exercises a vendored Cocos Creator 3.8.8 web fixture through live Chromium and CDP, packs the npm tarball, installs it into a temporary project, and smoke-tests the installed binary. Use it before pushing. `npm test` remains available for build plus self-tests only; `npm run test:integration` runs the live browser test separately.
200
201
 
201
- The test suite covers URL policy, CDP connection reuse and recovery, scene traversal, property redaction and cycle handling, output bounds, Cocos version rejection, strict tool schemas, exact tool annotations, real page selection, inspector and debugger tools, visual bounds/capture, snapshots, diagnostics, and browser reconnection. CI installs the matching Playwright Chromium revision, runs the full check on Ubuntu and Windows with Node.js 20 and 22, and rejects high-severity production dependency advisories.
202
+ The test suite covers URL policy, CDP connection reuse and recovery, scene traversal, property redaction and cycle handling, output bounds, Cocos version rejection, strict tool schemas, exact tool annotations, real page selection, inspector and debugger tools, visual bounds/capture, snapshots, diagnostics, and browser reconnection. CI installs the matching Playwright Chromium revision, runs the full check on Ubuntu and Windows with Node.js 20, 22, and 24, and rejects high-severity production dependency advisories.
202
203
 
203
204
  ## Troubleshooting
204
205
 
@@ -206,7 +207,10 @@ The test suite covers URL policy, CDP connection reuse and recovery, scene trave
206
207
  - `MULTIPLE_PAGES`: call `cocos_list_pages`, then pass the exact reported `pageUrl`. If tabs share one URL, close duplicates or use a separate Chromium per project (see Page selection).
207
208
  - `COCOS_NOT_FOUND` or `SCENE_NOT_READY`: wait for the web build to finish loading; use `cocos_runtime_info` after the active scene exists.
208
209
  - Runtime mutation tools missing: restart the server with `--allow-runtime-mutation`; a tool call cannot enable this mode.
209
- - Bounds/capture unavailable: select a visible UI node with `UITransform`. Capture is viewport-only, bounded, and never falls back to a full-page screenshot.
210
+ - Bounds/capture unavailable: select a visible UI node with `UITransform`. `INACTIVE` means the node or an ancestor is inactive. Capture is viewport-only, bounded, and never falls back to a full-page screenshot; `RESPONSE_LIMIT` means even a 0.25× JPEG exceeded the response budget, so capture a smaller node.
211
+ - `cocos_snapshot_subtree` returns `RESPONSE_LIMIT` with a partial tree: snapshot a deeper node, or lower `maxDepth`.
212
+ - `cocos_step_frame` returns `INVALID_MUTATION`: call `cocos_pause` first.
213
+ - Render metrics `fps` reads 0: the engine publishes FPS once per elapsed second, so read again after the scene has run for a second.
210
214
 
211
215
  ## Release and compatibility
212
216
 
@@ -61,6 +61,9 @@ export type BridgeRequest = {
61
61
  action: 'pause';
62
62
  } | {
63
63
  action: 'resume';
64
+ } | {
65
+ action: 'stepFrame';
66
+ frames?: number | undefined;
64
67
  };
65
68
  type Vector3 = {
66
69
  x: number;
@@ -246,13 +246,28 @@ export function inspectCocos(request) {
246
246
  }
247
247
  });
248
248
  const duplicateNames = [...names.entries()].filter(([, uuids]) => uuids.length > 1).slice(0, 100).map(([name, uuids]) => ({ name, uuids, count: uuids.length }));
249
+ // Root and the GFX device update these every frame whether or not the profiler is shown; read backing fields, never getters.
250
+ const renderRoot = cc.director && typeof cc.director === 'object' ? dataProperty(cc.director, '_root') : undefined;
251
+ const device = renderRoot && typeof renderRoot === 'object' ? dataProperty(renderRoot, '_device') : undefined;
252
+ const metric = (owner, key) => {
253
+ const value = owner && typeof owner === 'object' ? dataProperty(owner, key) : undefined;
254
+ return typeof value === 'number' && Number.isFinite(value) ? value : undefined;
255
+ };
256
+ const render = { fps: metric(renderRoot, '_fps'), frameTimeMs: metric(renderRoot, '_frameTime'), drawCalls: metric(device, '_numDrawCalls'), triangles: metric(device, '_numTris'), instances: metric(device, '_numInstances') };
257
+ if (render.frameTimeMs !== undefined)
258
+ render.frameTimeMs *= 1_000;
259
+ const unavailableMetrics = { invalidComponentReferences: 'UNSUPPORTED_PUBLIC_API' };
260
+ for (const [key, value] of Object.entries(render))
261
+ if (value === undefined)
262
+ unavailableMetrics[key] = 'UNSUPPORTED_PUBLIC_API';
249
263
  return {
250
264
  version,
251
265
  nodeCount,
252
266
  componentCount,
253
267
  maxHierarchyDepth: maxDepth,
254
268
  duplicateNames,
255
- unavailableMetrics: { fps: 'UNSUPPORTED_PUBLIC_API', frameTime: 'UNSUPPORTED_PUBLIC_API', drawCalls: 'UNSUPPORTED_PUBLIC_API', triangles: 'UNSUPPORTED_PUBLIC_API', invalidComponentReferences: 'UNSUPPORTED_PUBLIC_API' },
269
+ render: Object.fromEntries(Object.entries(render).filter(([, value]) => value !== undefined)),
270
+ unavailableMetrics,
256
271
  truncated: traversal.truncated || duplicateNames.length >= 100,
257
272
  truncationReasons: traversal.truncated || duplicateNames.length >= 100 ? ['NODE_LIMIT'] : [],
258
273
  };
@@ -559,6 +574,29 @@ export function inspectCocos(request) {
559
574
  const after = directorState();
560
575
  return { changed: before.paused !== after.paused, target: {}, before, after, runtimeOnly: true };
561
576
  }
577
+ if (request.action === 'stepFrame') {
578
+ const game = cc.game;
579
+ if (typeof game?.step !== 'function' || typeof game.isPaused !== 'function' || typeof cc.director?.getTotalFrames !== 'function')
580
+ invalidMutation('game.step is unavailable');
581
+ const directorPaused = directorState().paused;
582
+ if (!directorPaused && !game.isPaused())
583
+ invalidMutation('pause the game before stepping');
584
+ const before = cc.director.getTotalFrames();
585
+ // game.step ticks the director, which skips logic while director-paused; unpause only inside this synchronous call.
586
+ for (let frame = 0; frame < (request.frames ?? 1); frame++) {
587
+ if (directorPaused)
588
+ cc.director.resume();
589
+ try {
590
+ game.step();
591
+ }
592
+ finally {
593
+ if (directorPaused)
594
+ cc.director.pause();
595
+ }
596
+ }
597
+ const after = cc.director.getTotalFrames();
598
+ return { changed: after !== before, target: {}, before: { totalFrames: before }, after: { totalFrames: after }, runtimeOnly: true };
599
+ }
562
600
  if (request.action === 'setNodeActive') {
563
601
  const node = findByUuid(request.uuid);
564
602
  const before = node.active !== false;
@@ -171,9 +171,10 @@ export function createServer(browser, options = {}) {
171
171
  annotations: runtimeMutation,
172
172
  }, input => execute({ action: 'setProperty', uuid: input.uuid, componentUuid: input.componentUuid, key: input.key, value: input.value }, input.pageUrl));
173
173
  server.registerTool('cocos_click_node', {
174
- description: 'Dispatch a real mouse click at the visible center of one Cocos UI node, so Button and touch handlers run.',
174
+ description: 'Dispatch a real mouse click at the visible center of one Cocos UI node, so Button and touch handlers run. Handlers may call game servers or make irreversible changes.',
175
175
  inputSchema: z.object({ pageUrl, uuid: z.string().min(1) }).strict(),
176
- annotations: { ...runtimeMutation, idempotentHint: false },
176
+ // Game click handlers run arbitrary code: a login button reaches real servers, a buy button spends currency.
177
+ annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: true },
177
178
  }, async (input) => {
178
179
  try {
179
180
  return response(await clickNode(await browser.page(input.pageUrl), input.uuid));
@@ -189,6 +190,11 @@ export function createServer(browser, options = {}) {
189
190
  annotations: runtimeMutation,
190
191
  }, input => execute({ action }, input.pageUrl));
191
192
  }
193
+ server.registerTool('cocos_step_frame', {
194
+ description: 'Advance a paused Cocos game by fixed-delta frames through cc.game.step; requires cocos_pause first.',
195
+ inputSchema: z.object({ pageUrl, frames: z.number().int().min(1).max(60).optional() }).strict(),
196
+ annotations: { ...runtimeMutation, idempotentHint: false },
197
+ }, input => execute({ action: 'stepFrame', frames: input.frames }, input.pageUrl));
192
198
  }
193
199
  server.registerTool('cocos_highlight_node', {
194
200
  description: 'Temporarily draw a pointer-transparent DOM overlay around one Cocos UI node.',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cocos-web-inspector-mcp",
3
- "version": "0.1.7",
3
+ "version": "0.1.8",
4
4
  "description": "Read-only MCP inspector for Cocos Creator 3.x running in local Chromium",
5
5
  "license": "MIT",
6
6
  "repository": {