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 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, 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` |
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 with a `UITransform` whose box contains the point; inactive nodes are skipped. 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.
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
 
@@ -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;
@@ -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
- await session.send('Emulation.setUserAgentOverride', { userAgent: device?.userAgent ?? (await session.send('Browser.getVersion')).userAgent });
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
- // ponytail: constructor metadata is the compatibility ceiling; add explicit engine adapters if minified builds need more.
188
- const name = dataProperty(component, 'constructor')?.name || dataProperty(component, '__classname__') || '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> --port ${port}`);
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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cocos-web-inspector-mcp",
3
- "version": "2.0.0",
3
+ "version": "2.0.1",
4
4
  "description": "MCP server to inspect and debug Cocos Creator 3.x web builds in local Chromium over CDP",
5
5
  "license": "MIT",
6
6
  "repository": {