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 +1 -1
- package/dist/src/bridge.js +43 -10
- package/package.json +1 -1
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,
|
|
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
|
|
package/dist/src/bridge.js
CHANGED
|
@@ -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
|
|
17
|
-
return { captured: false, reason: '
|
|
18
|
-
|
|
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 (
|
|
23
|
-
return { captured: true, mimeType: `image/${options.type}`, data,
|
|
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
|
-
|
|
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
|
-
|
|
399
|
-
|
|
400
|
-
|
|
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'
|