cocos-web-inspector-mcp 0.1.6 → 0.1.7

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
@@ -122,7 +122,7 @@ All tool input objects are strict. Unknown fields are rejected. Inspection tools
122
122
  | `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
123
  | `cocos_highlight_node` | Draw a temporary pointer-transparent overlay around a UI node. | `pageUrl?`; `uuid`; `durationMs?` integer `100..10000`, default `2000` |
124
124
 
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, limits captures to 1,024 × 1,024 CSS pixels / 1,048,576 total pixels, returns PNG (or JPEG fallback) base64 in-memory, and rejects responses still oversized after JPEG quality steps. 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.
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
126
 
127
127
  ### Page selection
128
128
 
@@ -13,16 +13,30 @@ export async function captureNode(page, uuid) {
13
13
  if (!result.available || !result.visible)
14
14
  return { captured: false, reason: result.reason ?? 'OUTSIDE_VIEWPORT' };
15
15
  const clip = result.clippedViewport;
16
- if (!clip || !Number.isFinite(clip.x) || !Number.isFinite(clip.y) || !Number.isFinite(clip.width) || !Number.isFinite(clip.height) || clip.width <= 0 || clip.height <= 0 || clip.width > 1_024 || clip.height > 1_024 || clip.width * clip.height > 1_048_576)
17
- return { captured: false, reason: 'CAPTURE_LIMIT' };
18
- // ponytail: device-pixel PNG, then CSS-pixel JPEG quality steps; add CDP clip.scale downscaling if captures still hit the limit.
16
+ if (!clip || !Number.isFinite(clip.x) || !Number.isFinite(clip.y) || !Number.isFinite(clip.width) || !Number.isFinite(clip.height) || clip.width <= 0 || clip.height <= 0)
17
+ return { captured: false, reason: 'INVALID_GEOMETRY' };
18
+ const fits = (data) => Buffer.byteLength(JSON.stringify({ data }), 'utf8') <= MAX_BYTES - 4_096;
19
+ const size = { width: Math.round(clip.width), height: Math.round(clip.height) };
20
+ // Device-pixel PNG, then CSS-pixel JPEG quality steps.
19
21
  const attempts = [{ type: 'png', scale: 'device' }, ...[80, 60, 40].map(quality => ({ type: 'jpeg', quality, scale: 'css' }))];
20
22
  for (const options of attempts) {
21
23
  const data = (await page.screenshot({ ...options, clip })).toString('base64');
22
- if (Buffer.byteLength(JSON.stringify({ data }), 'utf8') <= MAX_BYTES - 4_096) {
23
- return { captured: true, mimeType: `image/${options.type}`, data, width: Math.round(clip.width), height: Math.round(clip.height) };
24
+ if (fits(data))
25
+ return { captured: true, mimeType: `image/${options.type}`, data, ...size };
26
+ }
27
+ // Playwright has no output scale, so downscale through CDP; its clip is document-relative.
28
+ const session = await page.context().newCDPSession(page);
29
+ try {
30
+ const [scrollX, scrollY] = await page.evaluate(() => [window.scrollX, window.scrollY]);
31
+ for (const scale of [0.75, 0.5, 0.35, 0.25]) {
32
+ const { data } = await session.send('Page.captureScreenshot', { format: 'jpeg', quality: 60, clip: { x: clip.x + scrollX, y: clip.y + scrollY, width: clip.width, height: clip.height, scale } });
33
+ if (fits(data))
34
+ return { captured: true, mimeType: 'image/jpeg', data, ...size, scale };
24
35
  }
25
36
  }
37
+ finally {
38
+ await session.detach().catch(() => { });
39
+ }
26
40
  return { captured: false, reason: 'RESPONSE_LIMIT' };
27
41
  }
28
42
  export async function clickNode(page, uuid) {
@@ -192,7 +206,8 @@ export function inspectCocos(request) {
192
206
  if (!Object.values(viewport).every(Number.isFinite) || viewport.width <= 0 || viewport.height <= 0)
193
207
  return { available: false, reason: 'INVALID_GEOMETRY' };
194
208
  const clipped = { x: Math.max(0, viewport.x), y: Math.max(0, viewport.y), width: Math.max(0, Math.min(innerWidth, viewport.x + viewport.width) - Math.max(0, viewport.x)), height: Math.max(0, Math.min(innerHeight, viewport.y + viewport.height) - Math.max(0, viewport.y)) };
195
- return { available: true, canvas: { x: worldX, y: worldY, width: worldWidth, height: worldHeight }, viewport, clippedViewport: clipped, anchor: { x: Number(transform.anchorPoint.x), y: Number(transform.anchorPoint.y) }, worldPosition: { x: Number(position.x), y: Number(position.y), z: Number(position.z ?? 0) }, visible: clipped.width > 0 && clipped.height > 0, outsideViewport: clipped.width === 0 || clipped.height === 0 };
209
+ const inactive = node.activeInHierarchy === false;
210
+ return { available: true, canvas: { x: worldX, y: worldY, width: worldWidth, height: worldHeight }, viewport, clippedViewport: clipped, anchor: { x: Number(transform.anchorPoint.x), y: Number(transform.anchorPoint.y) }, worldPosition: { x: Number(position.x), y: Number(position.y), z: Number(position.z ?? 0) }, visible: !inactive && clipped.width > 0 && clipped.height > 0, outsideViewport: clipped.width === 0 || clipped.height === 0, ...(inactive ? { reason: 'INACTIVE' } : {}) };
196
211
  };
197
212
  if (request.action === 'runtimeInfo') {
198
213
  const canvas = cc.game?.canvas ?? root.document?.querySelector('#GameCanvas');
@@ -329,17 +344,24 @@ export function inspectCocos(request) {
329
344
  const maxDepth = Math.min(Math.max(request.maxDepth ?? 6, 0), 20);
330
345
  const maxNodes = Math.min(Math.max(request.maxNodes ?? 500, 1), 5_000);
331
346
  let count = 0;
347
+ // ponytail: the MCP response carries text and structuredContent, so ~70 KB of JSON (escaped text + structured copy) fits runBridge's ceiling; stop early instead of dropping everything.
348
+ let bytes = 0;
332
349
  const reasons = new Set();
333
350
  const build = (node, depth) => {
334
351
  if (count >= maxNodes) {
335
352
  reasons.add('NODE_LIMIT');
336
353
  return undefined;
337
354
  }
338
- count++;
339
355
  const item = {
340
356
  ...summary(node),
341
357
  components: components(node).slice(0, 200).map(component => ({ type: componentName(component), uuid: String(component.uuid ?? ''), enabled: component.enabled !== false })),
342
358
  };
359
+ bytes += JSON.stringify(item).length;
360
+ if (bytes > 70_000) {
361
+ reasons.add('RESPONSE_LIMIT');
362
+ return undefined;
363
+ }
364
+ count++;
343
365
  if (components(node).length > 200)
344
366
  reasons.add('NODE_LIMIT');
345
367
  if (depth >= maxDepth) {
@@ -395,12 +417,16 @@ export function inspectCocos(request) {
395
417
  }
396
418
  if (typeof value !== 'object')
397
419
  return undefined;
398
- const uuid = dataProperty(value, 'uuid');
399
- if (reference && uuid && Array.isArray(dataProperty(value, 'children'))) {
400
- return { $type: 'Node', uuid: String(uuid), name: String(dataProperty(value, 'name') ?? '').slice(0, 500) };
420
+ // Cocos 3.x exposes uuid/children/name as accessors; read their backing fields instead.
421
+ const uuid = dataProperty(value, 'uuid') ?? dataProperty(value, '_id');
422
+ if (reference && uuid && Array.isArray(dataProperty(value, 'children') ?? dataProperty(value, '_children'))) {
423
+ return { $type: 'Node', uuid: String(uuid), name: String(dataProperty(value, 'name') ?? dataProperty(value, '_name') ?? '').slice(0, 500) };
401
424
  }
402
425
  if (reference && uuid && dataProperty(value, 'node'))
403
426
  return { $type: 'Component', uuid: String(uuid), type: componentName(value) };
427
+ if (reference && typeof cc.Asset === 'function' && value instanceof cc.Asset) {
428
+ return { $type: componentName(value), name: String(dataProperty(value, '_name') ?? '').slice(0, 500), uuid: String(dataProperty(value, '_uuid') ?? '') };
429
+ }
404
430
  if (depth > 0 && depth >= maxDepth) {
405
431
  truncate('MAX_DEPTH');
406
432
  return '[MaxDepth]';
@@ -472,6 +498,13 @@ export function inspectCocos(request) {
472
498
  readDisplay('RichText', 'string', '_string');
473
499
  readDisplay('Button', 'interactable', '_interactable');
474
500
  readDisplay('Toggle', 'isChecked', '_isChecked');
501
+ if (selected === node) {
502
+ for (const key of ['name', 'active', 'activeInHierarchy']) {
503
+ const value = dataProperty(node, key) ?? dataProperty(node, `_${key}`);
504
+ if (typeof value === 'string' || typeof value === 'boolean')
505
+ displayFields[key] = typeof value === 'string' ? value.slice(0, 500) : value;
506
+ }
507
+ }
475
508
  if (typeof cc.Sprite === 'function' && selected instanceof cc.Sprite) {
476
509
  const frame = dataProperty(selected, '_spriteFrame');
477
510
  displayFields.spriteFrame = frame && typeof frame === 'object'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cocos-web-inspector-mcp",
3
- "version": "0.1.6",
3
+ "version": "0.1.7",
4
4
  "description": "Read-only MCP inspector for Cocos Creator 3.x running in local Chromium",
5
5
  "license": "MIT",
6
6
  "repository": {