cocos-web-inspector-mcp 2.0.0 → 2.0.1
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 +2 -2
- package/dist/src/bridge.d.ts +6 -0
- package/dist/src/bridge.js +27 -11
- package/dist/src/cli.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -130,7 +130,7 @@ All tool input objects are strict. Unknown fields are rejected. Inspection tools
|
|
|
130
130
|
| `cocos_resume` | Resume the Cocos director when its public API supports it. | `pageUrl?` |
|
|
131
131
|
| `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?` |
|
|
132
132
|
| `cocos_show_stats` | Show or hide the engine's FPS, draw-call, and triangle overlay through the public `profiler` API. | `pageUrl?`; `visible` boolean |
|
|
133
|
-
| `cocos_emulate_device` | Emulate a mobile device like the Chrome device toolbar: viewport, DPR, touch (mouse input arrives as touch), user agent
|
|
133
|
+
| `cocos_emulate_device` | Emulate a mobile device like the Chrome device toolbar: viewport, DPR, touch (mouse input arrives as touch), user agent and `navigator.platform`, and orientation; optionally slow the CPU or network. Settings merge across calls. | `pageUrl?`; `preset?` (`iphone-se`, `iphone-14`, `iphone-14-pro-max`, `pixel-7`, `galaxy-s20`, `ipad-mini`) or `width`+`height` `200..4000` with `deviceScaleFactor?` `1..4` and `mobile?`; `orientation?`; `cpuSlowdown?` `1..20`; `network?` (`online`, `offline`, `slow-3g`, `fast-3g`, `fast-4g`); `reload?`; or `reset: true` |
|
|
134
134
|
| `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` |
|
|
135
135
|
| `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` |
|
|
136
136
|
| `cocos_get_components` | Return bounded component summaries for a node. | `pageUrl?`; `uuid` |
|
|
@@ -143,7 +143,7 @@ All tool input objects are strict. Unknown fields are rejected. Inspection tools
|
|
|
143
143
|
| `cocos_get_selection` | Return the node the user last Alt+clicked on the game canvas: node summary and path, viewport box, and the hit stack under the point (topmost first). The first call installs the picker. | `pageUrl?`; `disable?` removes the picker, overlay, and selection |
|
|
144
144
|
| `cocos_highlight_node` | Draw a temporary pointer-transparent overlay around a UI node. | `pageUrl?`; `uuid`; `durationMs?` integer `100..10000`, default `2000` |
|
|
145
145
|
|
|
146
|
-
`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_get_selection` lets the user point at a node instead of describing it: it installs a capture-phase listener that consumes only Alt+clicks on the game canvas, so the game never sees them, and draws a labelled pointer-transparent overlay. It picks the topmost active node
|
|
146
|
+
`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_get_selection` lets the user point at a node instead of describing it: it installs a capture-phase listener that consumes only Alt+clicks on the game canvas, so the game never sees them, and draws a labelled pointer-transparent overlay. It picks the topmost active node whose box contains the point and that draws or takes input (a 2D renderer such as Sprite or Label, or a Button, Toggle, EditBox, or Slider). Inactive nodes, layout-only containers, and nodes at zero opacity (including through a parent) are skipped, so invisible full-screen blockers do not swallow picks. The selection is reported `stale` once that node leaves the scene, and the picker disappears on reload. `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. `cocos_show_stats` is an explicit configuration change; while the overlay is visible, draw-call metrics include it. `cocos_emulate_device` holds a CDP session per page: emulation ends on `reset`, when the server disconnects, or when the server process exits, and `reset` also clears viewport overrides set by other CDP clients on that page. Cocos reads the user agent and touch support at startup, so pass `reload: true` for the game to see a new device class. Network throttling only delays traffic; it never reads requests or responses.
|
|
147
147
|
|
|
148
148
|
### Page selection
|
|
149
149
|
|
package/dist/src/bridge.d.ts
CHANGED
|
@@ -110,6 +110,7 @@ export declare const devicePresets: {
|
|
|
110
110
|
readonly deviceScaleFactor: 2;
|
|
111
111
|
readonly mobile: true;
|
|
112
112
|
readonly userAgent: "Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1";
|
|
113
|
+
readonly platform: 'iPhone';
|
|
113
114
|
};
|
|
114
115
|
readonly 'iphone-14': {
|
|
115
116
|
readonly width: 390;
|
|
@@ -117,6 +118,7 @@ export declare const devicePresets: {
|
|
|
117
118
|
readonly deviceScaleFactor: 3;
|
|
118
119
|
readonly mobile: true;
|
|
119
120
|
readonly userAgent: "Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1";
|
|
121
|
+
readonly platform: 'iPhone';
|
|
120
122
|
};
|
|
121
123
|
readonly 'iphone-14-pro-max': {
|
|
122
124
|
readonly width: 430;
|
|
@@ -124,6 +126,7 @@ export declare const devicePresets: {
|
|
|
124
126
|
readonly deviceScaleFactor: 3;
|
|
125
127
|
readonly mobile: true;
|
|
126
128
|
readonly userAgent: "Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1";
|
|
129
|
+
readonly platform: 'iPhone';
|
|
127
130
|
};
|
|
128
131
|
readonly 'pixel-7': {
|
|
129
132
|
readonly width: 412;
|
|
@@ -131,6 +134,7 @@ export declare const devicePresets: {
|
|
|
131
134
|
readonly deviceScaleFactor: 2.625;
|
|
132
135
|
readonly mobile: true;
|
|
133
136
|
readonly userAgent: string;
|
|
137
|
+
readonly platform: 'Linux armv81';
|
|
134
138
|
};
|
|
135
139
|
readonly 'galaxy-s20': {
|
|
136
140
|
readonly width: 360;
|
|
@@ -138,6 +142,7 @@ export declare const devicePresets: {
|
|
|
138
142
|
readonly deviceScaleFactor: 3;
|
|
139
143
|
readonly mobile: true;
|
|
140
144
|
readonly userAgent: string;
|
|
145
|
+
readonly platform: 'Linux armv81';
|
|
141
146
|
};
|
|
142
147
|
readonly 'ipad-mini': {
|
|
143
148
|
readonly width: 768;
|
|
@@ -145,6 +150,7 @@ export declare const devicePresets: {
|
|
|
145
150
|
readonly deviceScaleFactor: 2;
|
|
146
151
|
readonly mobile: true;
|
|
147
152
|
readonly userAgent: "Mozilla/5.0 (iPad; CPU OS 17_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1";
|
|
153
|
+
readonly platform: 'iPad';
|
|
148
154
|
};
|
|
149
155
|
};
|
|
150
156
|
export type DevicePreset = keyof typeof devicePresets;
|
package/dist/src/bridge.js
CHANGED
|
@@ -52,12 +52,12 @@ const IOS_UA = 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebK
|
|
|
52
52
|
const IPAD_UA = 'Mozilla/5.0 (iPad; CPU OS 17_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1';
|
|
53
53
|
const androidUa = (model) => `Mozilla/5.0 (Linux; Android 14; ${model}) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Mobile Safari/537.36`;
|
|
54
54
|
export const devicePresets = {
|
|
55
|
-
'iphone-se': { width: 375, height: 667, deviceScaleFactor: 2, mobile: true, userAgent: IOS_UA },
|
|
56
|
-
'iphone-14': { width: 390, height: 844, deviceScaleFactor: 3, mobile: true, userAgent: IOS_UA },
|
|
57
|
-
'iphone-14-pro-max': { width: 430, height: 932, deviceScaleFactor: 3, mobile: true, userAgent: IOS_UA },
|
|
58
|
-
'pixel-7': { width: 412, height: 915, deviceScaleFactor: 2.625, mobile: true, userAgent: androidUa('Pixel 7') },
|
|
59
|
-
'galaxy-s20': { width: 360, height: 800, deviceScaleFactor: 3, mobile: true, userAgent: androidUa('SM-G981B') },
|
|
60
|
-
'ipad-mini': { width: 768, height: 1024, deviceScaleFactor: 2, mobile: true, userAgent: IPAD_UA },
|
|
55
|
+
'iphone-se': { width: 375, height: 667, deviceScaleFactor: 2, mobile: true, userAgent: IOS_UA, platform: 'iPhone' },
|
|
56
|
+
'iphone-14': { width: 390, height: 844, deviceScaleFactor: 3, mobile: true, userAgent: IOS_UA, platform: 'iPhone' },
|
|
57
|
+
'iphone-14-pro-max': { width: 430, height: 932, deviceScaleFactor: 3, mobile: true, userAgent: IOS_UA, platform: 'iPhone' },
|
|
58
|
+
'pixel-7': { width: 412, height: 915, deviceScaleFactor: 2.625, mobile: true, userAgent: androidUa('Pixel 7'), platform: 'Linux armv81' },
|
|
59
|
+
'galaxy-s20': { width: 360, height: 800, deviceScaleFactor: 3, mobile: true, userAgent: androidUa('SM-G981B'), platform: 'Linux armv81' },
|
|
60
|
+
'ipad-mini': { width: 768, height: 1024, deviceScaleFactor: 2, mobile: true, userAgent: IPAD_UA, platform: 'iPad' },
|
|
61
61
|
};
|
|
62
62
|
export const devicePresetNames = Object.keys(devicePresets);
|
|
63
63
|
// Chrome DevTools throttling presets, in bytes per second and milliseconds.
|
|
@@ -104,7 +104,7 @@ export async function emulateDevice(page, request) {
|
|
|
104
104
|
const report = (state) => {
|
|
105
105
|
if (!state.device)
|
|
106
106
|
return state;
|
|
107
|
-
const { userAgent: _userAgent, ...device } = state.device;
|
|
107
|
+
const { userAgent: _userAgent, platform: _platform, ...device } = state.device;
|
|
108
108
|
const landscape = device.orientation === 'landscape';
|
|
109
109
|
return { ...state, device: { ...device, width: landscape ? device.height : device.width, height: landscape ? device.width : device.height } };
|
|
110
110
|
};
|
|
@@ -128,7 +128,10 @@ async function applyEmulation(session, state) {
|
|
|
128
128
|
await session.send('Emulation.setTouchEmulationEnabled', touch ? { enabled: true, maxTouchPoints: 5 } : { enabled: false });
|
|
129
129
|
// Like the Chrome device toolbar: mouse input arrives as touch, which mobile Cocos builds listen for.
|
|
130
130
|
await session.send('Emulation.setEmitTouchEventsForMouse', { enabled: touch, configuration: 'mobile' });
|
|
131
|
-
|
|
131
|
+
// Engines such as Cocos read navigator.platform too: a touch-enabled MacIntel counts as iPad, whatever the user agent says.
|
|
132
|
+
await session.send('Emulation.setUserAgentOverride', device?.userAgent
|
|
133
|
+
? { userAgent: device.userAgent, platform: device.platform ?? '' }
|
|
134
|
+
: { userAgent: (await session.send('Browser.getVersion')).userAgent });
|
|
132
135
|
await session.send('Emulation.setCPUThrottlingRate', { rate: state.cpuSlowdown ?? 1 });
|
|
133
136
|
await session.send('Network.emulateNetworkConditions', networkProfiles[state.network ?? 'online']);
|
|
134
137
|
}
|
|
@@ -184,8 +187,8 @@ export function inspectCocos(request) {
|
|
|
184
187
|
return undefined;
|
|
185
188
|
};
|
|
186
189
|
const componentName = (component) => {
|
|
187
|
-
//
|
|
188
|
-
const name = dataProperty(component, '
|
|
190
|
+
// Release builds minify constructor names ("e"); the registered __classname__ survives, so it wins.
|
|
191
|
+
const name = dataProperty(component, '__classname__') || dataProperty(component, 'constructor')?.name || 'Component';
|
|
189
192
|
return String(name).replace(/^cc\./, '').slice(0, 120);
|
|
190
193
|
};
|
|
191
194
|
const components = (node) => {
|
|
@@ -441,8 +444,21 @@ export function inspectCocos(request) {
|
|
|
441
444
|
if (request.action === 'pickAt') {
|
|
442
445
|
// Cocos draws 2D nodes in pre-order, so the last hit in traversal order is the topmost one.
|
|
443
446
|
const hits = [];
|
|
447
|
+
// Full-screen blockers often sit on top at opacity 0; read the backing field, never the computing getter.
|
|
448
|
+
const transparent = (node) => {
|
|
449
|
+
for (let current = node, depth = 0; current && depth < 100; current = current.parent, depth++) {
|
|
450
|
+
const local = dataProperty(dataProperty(current, '_uiProps') ?? {}, '_localOpacity');
|
|
451
|
+
if (typeof local === 'number' && local <= 0)
|
|
452
|
+
return true;
|
|
453
|
+
}
|
|
454
|
+
return false;
|
|
455
|
+
};
|
|
456
|
+
// Full-screen layout containers draw nothing; only nodes that render or take input can be what the user pointed at.
|
|
457
|
+
const Renderable = cc.internal?.Renderable2D;
|
|
458
|
+
const pickable = (node) => components(node).some(component => typeof Renderable === 'function' && component instanceof Renderable
|
|
459
|
+
|| /^(Sprite|Label|RichText|Graphics|Button|Toggle|EditBox|Slider)$/.test(componentName(component)));
|
|
444
460
|
walk((node, path) => {
|
|
445
|
-
if (node === scene || node.activeInHierarchy === false)
|
|
461
|
+
if (node === scene || node.activeInHierarchy === false || !pickable(node) || transparent(node))
|
|
446
462
|
return;
|
|
447
463
|
const bounds = nodeBounds(node);
|
|
448
464
|
const box = bounds.available ? bounds.viewport : undefined;
|
package/dist/src/cli.js
CHANGED
|
@@ -150,7 +150,7 @@ async function doctor(args) {
|
|
|
150
150
|
const httpEndpoint = url.origin.replace(/^ws/, 'http');
|
|
151
151
|
const probed = await probe(httpEndpoint);
|
|
152
152
|
if (probed.status === 'down')
|
|
153
|
-
fail('CDP_UNAVAILABLE', `nothing answers on port ${port} (${probed.message})`, `run: npx cocos-web-inspector-mcp launch <game-url
|
|
153
|
+
fail('CDP_UNAVAILABLE', `nothing answers on port ${port} (${probed.message})`, `run: npx cocos-web-inspector-mcp launch <game-url>${Number(port) >= 1_024 ? ` --port ${port}` : ''}`);
|
|
154
154
|
else if (probed.status === 'http')
|
|
155
155
|
fail('CDP_UNAVAILABLE', `port ${port} answers HTTP ${probed.code}${owner ? ` (${owner})` : ''}`, probed.code === 404 ? 'Chrome built-in remote debugging (chrome://inspect/#remote-debugging) holds this port; turn it off or launch on another --port' : 'stop the process holding this port or use another port');
|
|
156
156
|
else
|