chrome-devtools-mcp 0.21.0 → 0.23.0
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 +87 -21
- package/build/src/HeapSnapshotManager.js +94 -0
- package/build/src/McpContext.js +26 -181
- package/build/src/McpPage.js +214 -0
- package/build/src/McpResponse.js +151 -13
- package/build/src/PageCollector.js +10 -24
- package/build/src/TextSnapshot.js +230 -0
- package/build/src/WaitForHelper.js +31 -0
- package/build/src/bin/check-latest-version.js +25 -0
- package/build/src/bin/chrome-devtools-mcp-cli-options.js +34 -10
- package/build/src/bin/chrome-devtools-mcp-main.js +2 -0
- package/build/src/bin/chrome-devtools.js +25 -14
- package/build/src/bin/cliDefinitions.js +14 -8
- package/build/src/daemon/client.js +11 -11
- package/build/src/daemon/daemon.js +6 -9
- package/build/src/daemon/utils.js +19 -14
- package/build/src/formatters/HeapSnapshotFormatter.js +38 -0
- package/build/src/formatters/NetworkFormatter.js +24 -7
- package/build/src/index.js +12 -1
- package/build/src/telemetry/ClearcutLogger.js +34 -12
- package/build/src/telemetry/flagUtils.js +46 -4
- package/build/src/telemetry/toolMetricsUtils.js +88 -0
- package/build/src/telemetry/watchdog/ClearcutSender.js +4 -3
- package/build/src/third_party/THIRD_PARTY_NOTICES +59 -32
- package/build/src/third_party/bundled-packages.json +6 -4
- package/build/src/third_party/devtools-formatter-worker.js +61 -64
- package/build/src/third_party/devtools-heap-snapshot-worker.js +9690 -0
- package/build/src/third_party/index.js +62661 -60590
- package/build/src/third_party/lighthouse-devtools-mcp-bundle.js +3501 -2658
- package/build/src/tools/categories.js +3 -0
- package/build/src/tools/console.js +42 -39
- package/build/src/tools/emulation.js +1 -1
- package/build/src/tools/extensions.js +5 -11
- package/build/src/tools/inPage.js +3 -13
- package/build/src/tools/input.js +15 -16
- package/build/src/tools/lighthouse.js +2 -2
- package/build/src/tools/memory.js +48 -3
- package/build/src/tools/network.js +4 -4
- package/build/src/tools/pages.js +212 -146
- package/build/src/tools/performance.js +1 -1
- package/build/src/tools/screencast.js +20 -8
- package/build/src/tools/screenshot.js +3 -3
- package/build/src/tools/script.js +22 -16
- package/build/src/tools/tools.js +2 -0
- package/build/src/tools/webmcp.js +63 -0
- package/build/src/utils/check-for-updates.js +73 -0
- package/build/src/utils/files.js +4 -0
- package/build/src/utils/id.js +15 -0
- package/build/src/version.js +1 -1
- package/package.json +13 -8
- package/build/src/utils/ExtensionRegistry.js +0 -35
package/README.md
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
# Chrome DevTools
|
|
1
|
+
# Chrome DevTools for Agents
|
|
2
2
|
|
|
3
3
|
[](https://npmjs.org/package/chrome-devtools-mcp)
|
|
4
4
|
|
|
5
|
-
`chrome-devtools-mcp` lets your coding agent (such as Gemini, Claude, Cursor or Copilot)
|
|
5
|
+
Chrome DevTools for Agents (`chrome-devtools-mcp`) lets your coding agent (such as Gemini, Claude, Cursor or Copilot)
|
|
6
6
|
control and inspect a live Chrome browser. It acts as a Model-Context-Protocol
|
|
7
7
|
(MCP) server, giving your AI coding assistant access to the full power of
|
|
8
8
|
Chrome DevTools for reliable automation, in-depth debugging, and performance analysis.
|
|
9
|
+
A [CLI](docs/cli.md) is also provided for use without MCP.
|
|
9
10
|
|
|
10
11
|
## [Tool reference](./docs/tool-reference.md) | [Changelog](./CHANGELOG.md) | [Contributing](./CONTRIBUTING.md) | [Troubleshooting](./docs/troubleshooting.md) | [Design Principles](./docs/design-principles.md)
|
|
11
12
|
|
|
@@ -28,7 +29,7 @@ Avoid sharing sensitive or personal information that you don't want to share wit
|
|
|
28
29
|
MCP clients.
|
|
29
30
|
|
|
30
31
|
`chrome-devtools-mcp` officially supports Google Chrome and [Chrome for Testing](https://developer.chrome.com/blog/chrome-for-testing/) only.
|
|
31
|
-
Other Chromium-based
|
|
32
|
+
Other Chromium-based browsers may work, but this is not guaranteed, and you may encounter unexpected behavior. Use at your own discretion.
|
|
32
33
|
We are committed to providing fixes and support for the latest version of [Extended Stable Chrome](https://chromiumdash.appspot.com/schedule).
|
|
33
34
|
|
|
34
35
|
Performance tools may send trace URLs to the Google CrUX API to fetch real-user
|
|
@@ -51,7 +52,12 @@ Google handles this data in accordance with the [Google Privacy Policy](https://
|
|
|
51
52
|
|
|
52
53
|
Google's collection of usage statistics for Chrome DevTools MCP is independent from the Chrome browser's usage statistics. Opting out of Chrome metrics does not automatically opt you out of this tool, and vice-versa.
|
|
53
54
|
|
|
54
|
-
Collection is disabled if CHROME_DEVTOOLS_MCP_NO_USAGE_STATISTICS or CI env variables are set.
|
|
55
|
+
Collection is disabled if `CHROME_DEVTOOLS_MCP_NO_USAGE_STATISTICS` or `CI` env variables are set.
|
|
56
|
+
|
|
57
|
+
## Update checks
|
|
58
|
+
|
|
59
|
+
By default, the server periodically checks the npm registry for updates and logs a notification when a newer version is available.
|
|
60
|
+
You can disable these update checks by setting the `CHROME_DEVTOOLS_MCP_NO_UPDATE_CHECKS` environment variable.
|
|
55
61
|
|
|
56
62
|
## Requirements
|
|
57
63
|
|
|
@@ -74,7 +80,7 @@ Add the following config to your MCP client:
|
|
|
74
80
|
}
|
|
75
81
|
```
|
|
76
82
|
|
|
77
|
-
> [!NOTE]
|
|
83
|
+
> [!NOTE]
|
|
78
84
|
> Using `chrome-devtools-mcp@latest` ensures that your MCP client will always use the latest version of the Chrome DevTools MCP server.
|
|
79
85
|
|
|
80
86
|
If you are interested in doing only basic browser tasks, use the `--slim` mode:
|
|
@@ -143,7 +149,7 @@ claude mcp add chrome-devtools --scope user npx chrome-devtools-mcp@latest
|
|
|
143
149
|
|
|
144
150
|
**Install as a Plugin (MCP + Skills)**
|
|
145
151
|
|
|
146
|
-
> [!NOTE]
|
|
152
|
+
> [!NOTE]
|
|
147
153
|
> If you already had Chrome DevTools MCP installed previously for Claude Code, make sure to remove it first from your installation and configuration files.
|
|
148
154
|
|
|
149
155
|
To install Chrome DevTools MCP with skills, add the marketplace registry in Claude Code:
|
|
@@ -200,7 +206,7 @@ startup_timeout_ms = 20_000
|
|
|
200
206
|
|
|
201
207
|
<details>
|
|
202
208
|
<summary>Command Code</summary>
|
|
203
|
-
|
|
209
|
+
|
|
204
210
|
Use the Command Code CLI to add the Chrome DevTools MCP server (<a href="https://commandcode.ai/docs/mcp">MCP guide</a>):
|
|
205
211
|
|
|
206
212
|
```bash
|
|
@@ -235,6 +241,22 @@ Configure the following fields and press `CTRL+S` to save the configuration:
|
|
|
235
241
|
<details>
|
|
236
242
|
<summary>Copilot / VS Code</summary>
|
|
237
243
|
|
|
244
|
+
**Install as a Plugin (Recommended)**
|
|
245
|
+
|
|
246
|
+
The easiest way to get up and running is to install `chrome-devtools-mcp` as an agent plugin.
|
|
247
|
+
This bundles the **MCP server** and all **skills** together, so your agent gets both the tools
|
|
248
|
+
and the expert guidance it needs to use them effectively.
|
|
249
|
+
|
|
250
|
+
1. Open the **Command Palette** (`Cmd+Shift+P` on macOS or `Ctrl+Shift+P` on Windows/Linux).
|
|
251
|
+
2. Search for and run the **Chat: Install Plugin From Source** command.
|
|
252
|
+
3. Paste in our repository URL: `https://github.com/ChromeDevTools/chrome-devtools-mcp`
|
|
253
|
+
|
|
254
|
+
That's it! Your agent is now supercharged with Chrome DevTools capabilities.
|
|
255
|
+
|
|
256
|
+
---
|
|
257
|
+
|
|
258
|
+
**Install as an MCP Server (MCP only)**
|
|
259
|
+
|
|
238
260
|
**Click the button to install:**
|
|
239
261
|
|
|
240
262
|
[<img src="https://img.shields.io/badge/VS_Code-VS_Code?style=flat-square&label=Install%20Server&color=0098FF" alt="Install in VS Code">](https://vscode.dev/redirect/mcp/install?name=io.github.ChromeDevTools%2Fchrome-devtools-mcp&config=%7B%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22chrome-devtools-mcp%22%5D%2C%22env%22%3A%7B%7D%7D)
|
|
@@ -243,8 +265,7 @@ Configure the following fields and press `CTRL+S` to save the configuration:
|
|
|
243
265
|
|
|
244
266
|
**Or install manually:**
|
|
245
267
|
|
|
246
|
-
Follow the MCP
|
|
247
|
-
with the standard config from above. You can also install the Chrome DevTools MCP server using the VS Code CLI:
|
|
268
|
+
Follow the VS Code [MCP configuration guide](https://code.visualstudio.com/docs/copilot/chat/mcp-servers#_add-an-mcp-server) using the standard config from above, or use the CLI:
|
|
248
269
|
|
|
249
270
|
For macOS and Linux:
|
|
250
271
|
|
|
@@ -353,6 +374,21 @@ Once connected, the Chrome DevTools MCP tools will be available in StudioAssist.
|
|
|
353
374
|
|
|
354
375
|
</details>
|
|
355
376
|
|
|
377
|
+
<details>
|
|
378
|
+
<summary>Mistral Vibe</summary>
|
|
379
|
+
|
|
380
|
+
Add in ~/.vibe/config.toml:
|
|
381
|
+
|
|
382
|
+
```toml
|
|
383
|
+
[[mcp_servers]]
|
|
384
|
+
name = "chrome-devtools"
|
|
385
|
+
transport = "stdio"
|
|
386
|
+
command = "npx"
|
|
387
|
+
args = ["chrome-devtools-mcp@latest"]
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
</details>
|
|
391
|
+
|
|
356
392
|
<details>
|
|
357
393
|
<summary>OpenCode</summary>
|
|
358
394
|
|
|
@@ -402,10 +438,11 @@ qodercli mcp add -s user chrome-devtools -- npx chrome-devtools-mcp@latest
|
|
|
402
438
|
|
|
403
439
|
<details>
|
|
404
440
|
<summary>Visual Studio</summary>
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
441
|
+
|
|
442
|
+
**Click the button to install:**
|
|
443
|
+
|
|
444
|
+
[<img src="https://img.shields.io/badge/Visual_Studio-Install-C16FDE?logo=visualstudio&logoColor=white" alt="Install in Visual Studio">](https://vs-open.link/mcp-install?%7B%22name%22%3A%22chrome-devtools%22%2C%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22chrome-devtools-mcp%40latest%22%5D%7D)
|
|
445
|
+
|
|
409
446
|
</details>
|
|
410
447
|
|
|
411
448
|
<details>
|
|
@@ -431,7 +468,7 @@ Check the performance of https://developers.chrome.com
|
|
|
431
468
|
|
|
432
469
|
Your MCP client should open the browser and record a performance trace.
|
|
433
470
|
|
|
434
|
-
> [!NOTE]
|
|
471
|
+
> [!NOTE]
|
|
435
472
|
> The MCP server will start the browser automatically once the MCP client uses a tool that requires a running browser instance. Connecting to the Chrome DevTools MCP server on its own will not automatically start the browser.
|
|
436
473
|
|
|
437
474
|
## Tools
|
|
@@ -460,11 +497,10 @@ If you run into any issues, checkout our [troubleshooting guide](./docs/troubles
|
|
|
460
497
|
- **Emulation** (2 tools)
|
|
461
498
|
- [`emulate`](docs/tool-reference.md#emulate)
|
|
462
499
|
- [`resize_page`](docs/tool-reference.md#resize_page)
|
|
463
|
-
- **Performance** (
|
|
500
|
+
- **Performance** (3 tools)
|
|
464
501
|
- [`performance_analyze_insight`](docs/tool-reference.md#performance_analyze_insight)
|
|
465
502
|
- [`performance_start_trace`](docs/tool-reference.md#performance_start_trace)
|
|
466
503
|
- [`performance_stop_trace`](docs/tool-reference.md#performance_stop_trace)
|
|
467
|
-
- [`take_memory_snapshot`](docs/tool-reference.md#take_memory_snapshot)
|
|
468
504
|
- **Network** (2 tools)
|
|
469
505
|
- [`get_network_request`](docs/tool-reference.md#get_network_request)
|
|
470
506
|
- [`list_network_requests`](docs/tool-reference.md#list_network_requests)
|
|
@@ -475,6 +511,14 @@ If you run into any issues, checkout our [troubleshooting guide](./docs/troubles
|
|
|
475
511
|
- [`list_console_messages`](docs/tool-reference.md#list_console_messages)
|
|
476
512
|
- [`take_screenshot`](docs/tool-reference.md#take_screenshot)
|
|
477
513
|
- [`take_snapshot`](docs/tool-reference.md#take_snapshot)
|
|
514
|
+
- **Extensions** (5 tools)
|
|
515
|
+
- [`install_extension`](docs/tool-reference.md#install_extension)
|
|
516
|
+
- [`list_extensions`](docs/tool-reference.md#list_extensions)
|
|
517
|
+
- [`reload_extension`](docs/tool-reference.md#reload_extension)
|
|
518
|
+
- [`trigger_extension_action`](docs/tool-reference.md#trigger_extension_action)
|
|
519
|
+
- [`uninstall_extension`](docs/tool-reference.md#uninstall_extension)
|
|
520
|
+
- **Memory** (1 tools)
|
|
521
|
+
- [`take_memory_snapshot`](docs/tool-reference.md#take_memory_snapshot)
|
|
478
522
|
|
|
479
523
|
<!-- END AUTO GENERATED TOOLS -->
|
|
480
524
|
|
|
@@ -485,7 +529,7 @@ The Chrome DevTools MCP server supports the following configuration option:
|
|
|
485
529
|
<!-- BEGIN AUTO GENERATED OPTIONS -->
|
|
486
530
|
|
|
487
531
|
- **`--autoConnect`/ `--auto-connect`**
|
|
488
|
-
If specified, automatically connects to a browser (Chrome 144+) running locally from the user data directory identified by the channel param (default channel is stable). Requires the
|
|
532
|
+
If specified, automatically connects to a browser (Chrome 144+) running locally from the user data directory identified by the channel param (default channel is stable). Requires the remote debugging server to be started in the Chrome instance via chrome://inspect/#remote-debugging.
|
|
489
533
|
- **Type:** boolean
|
|
490
534
|
- **Default:** `false`
|
|
491
535
|
|
|
@@ -521,7 +565,7 @@ The Chrome DevTools MCP server supports the following configuration option:
|
|
|
521
565
|
- **`--channel`**
|
|
522
566
|
Specify a different Chrome channel that should be used. The default is the stable channel version.
|
|
523
567
|
- **Type:** string
|
|
524
|
-
- **Choices:** `
|
|
568
|
+
- **Choices:** `canary`, `dev`, `beta`, `stable`
|
|
525
569
|
|
|
526
570
|
- **`--logFile`/ `--log-file`**
|
|
527
571
|
Path to a file to write debug logs to. Set the env variable `DEBUG` to `*` to enable verbose logs. Useful for submitting bug reports.
|
|
@@ -539,10 +583,22 @@ The Chrome DevTools MCP server supports the following configuration option:
|
|
|
539
583
|
If enabled, ignores errors relative to self-signed and expired certificates. Use with caution.
|
|
540
584
|
- **Type:** boolean
|
|
541
585
|
|
|
586
|
+
- **`--experimentalVision`/ `--experimental-vision`**
|
|
587
|
+
Whether to enable coordinate-based tools such as click_at(x,y). Usually requires a computer-use model able to produce accurate coordinates by looking at screenshots.
|
|
588
|
+
- **Type:** boolean
|
|
589
|
+
|
|
542
590
|
- **`--experimentalScreencast`/ `--experimental-screencast`**
|
|
543
591
|
Exposes experimental screencast tools (requires ffmpeg). Install ffmpeg https://www.ffmpeg.org/download.html and ensure it is available in the MCP server PATH.
|
|
544
592
|
- **Type:** boolean
|
|
545
593
|
|
|
594
|
+
- **`--experimentalFfmpegPath`/ `--experimental-ffmpeg-path`**
|
|
595
|
+
Path to ffmpeg executable for screencast recording.
|
|
596
|
+
- **Type:** string
|
|
597
|
+
|
|
598
|
+
- **`--experimentalWebmcp`/ `--experimental-webmcp`**
|
|
599
|
+
Set to true to enable debugging WebMCP tools. Requires Chrome 149+ with the following flags: `--enable-features=WebMCPTesting,DevToolsWebMCPSupport`
|
|
600
|
+
- **Type:** boolean
|
|
601
|
+
|
|
546
602
|
- **`--chromeArg`/ `--chrome-arg`**
|
|
547
603
|
Additional arguments for Chrome. Only applies when Chrome is launched by chrome-devtools-mcp.
|
|
548
604
|
- **Type:** array
|
|
@@ -566,13 +622,18 @@ The Chrome DevTools MCP server supports the following configuration option:
|
|
|
566
622
|
- **Type:** boolean
|
|
567
623
|
- **Default:** `true`
|
|
568
624
|
|
|
625
|
+
- **`--categoryExtensions`/ `--category-extensions`**
|
|
626
|
+
Set to true to include tools related to extensions. Note: This feature is currently only supported with a pipe connection. autoConnect, browserUrl, and wsEndpoint are not supported with this feature until 149 will be released.
|
|
627
|
+
- **Type:** boolean
|
|
628
|
+
- **Default:** `false`
|
|
629
|
+
|
|
569
630
|
- **`--performanceCrux`/ `--performance-crux`**
|
|
570
631
|
Set to false to disable sending URLs from performance traces to CrUX API to get field performance data.
|
|
571
632
|
- **Type:** boolean
|
|
572
633
|
- **Default:** `true`
|
|
573
634
|
|
|
574
635
|
- **`--usageStatistics`/ `--usage-statistics`**
|
|
575
|
-
Set to false to opt-out of usage statistics collection. Google collects usage data to improve the tool, handled under the Google Privacy Policy (https://policies.google.com/privacy). This is independent from Chrome browser metrics. Disabled if CHROME_DEVTOOLS_MCP_NO_USAGE_STATISTICS or CI env variables are set.
|
|
636
|
+
Set to false to opt-out of usage statistics collection. Google collects usage data to improve the tool, handled under the Google Privacy Policy (https://policies.google.com/privacy). This is independent from Chrome browser metrics. Disabled if `CHROME_DEVTOOLS_MCP_NO_USAGE_STATISTICS` or `CI` env variables are set.
|
|
576
637
|
- **Type:** boolean
|
|
577
638
|
- **Default:** `true`
|
|
578
639
|
|
|
@@ -580,6 +641,11 @@ The Chrome DevTools MCP server supports the following configuration option:
|
|
|
580
641
|
Exposes a "slim" set of 3 tools covering navigation, script execution and screenshots only. Useful for basic browser tasks.
|
|
581
642
|
- **Type:** boolean
|
|
582
643
|
|
|
644
|
+
- **`--redactNetworkHeaders`/ `--redact-network-headers`**
|
|
645
|
+
If true, redacts some of the network headers considered senstive before returning to the client.
|
|
646
|
+
- **Type:** boolean
|
|
647
|
+
- **Default:** `false`
|
|
648
|
+
|
|
583
649
|
<!-- END AUTO GENERATED OPTIONS -->
|
|
584
650
|
|
|
585
651
|
Pass them via the `args` property in the JSON configuration. For example:
|
|
@@ -686,7 +752,7 @@ Make sure your browser is running. Open gemini-cli and run the following prompt:
|
|
|
686
752
|
Check the performance of https://developers.chrome.com
|
|
687
753
|
```
|
|
688
754
|
|
|
689
|
-
> [!NOTE]
|
|
755
|
+
> [!NOTE]
|
|
690
756
|
> The <code>autoConnect</code> option requires the user to start Chrome. If the user has multiple active profiles, the MCP server will connect to the default profile (as determined by Chrome). The MCP server has access to all open windows for the selected profile.
|
|
691
757
|
|
|
692
758
|
The Chrome DevTools MCP server will try to connect to your running Chrome
|
|
@@ -722,7 +788,7 @@ Add the `--browser-url` option to your MCP client configuration. The value of th
|
|
|
722
788
|
|
|
723
789
|
**Step 2: Start the Chrome browser**
|
|
724
790
|
|
|
725
|
-
> [!WARNING]
|
|
791
|
+
> [!WARNING]
|
|
726
792
|
> Enabling the remote debugging port opens up a debugging port on the running browser instance. Any application on your machine can connect to this port and control the browser. Make sure that you are not browsing any sensitive websites while the debugging port is open.
|
|
727
793
|
|
|
728
794
|
Start the Chrome browser with the remote debugging port enabled. Make sure to close any running Chrome instances before starting a new one with the debugging port enabled. The port number you choose must be the same as the one you specified in the `--browser-url` option in your MCP client configuration.
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Google LLC
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
import fsSync from 'node:fs';
|
|
7
|
+
import path from 'node:path';
|
|
8
|
+
import { DevTools } from './third_party/index.js';
|
|
9
|
+
import { createIdGenerator, stableIdSymbol, } from './utils/id.js';
|
|
10
|
+
export class HeapSnapshotManager {
|
|
11
|
+
#snapshots = new Map();
|
|
12
|
+
async getSnapshot(filePath) {
|
|
13
|
+
const absolutePath = path.resolve(filePath);
|
|
14
|
+
const cached = this.#snapshots.get(absolutePath);
|
|
15
|
+
if (cached) {
|
|
16
|
+
return cached.snapshot;
|
|
17
|
+
}
|
|
18
|
+
const { snapshot, worker } = await this.#loadSnapshot(absolutePath);
|
|
19
|
+
this.#snapshots.set(absolutePath, {
|
|
20
|
+
snapshot,
|
|
21
|
+
worker,
|
|
22
|
+
uidToClassKey: new Map(),
|
|
23
|
+
classKeyToUid: new Map(),
|
|
24
|
+
idGenerator: createIdGenerator(),
|
|
25
|
+
});
|
|
26
|
+
return snapshot;
|
|
27
|
+
}
|
|
28
|
+
async getAggregates(filePath) {
|
|
29
|
+
const snapshot = await this.getSnapshot(filePath);
|
|
30
|
+
const filter = new DevTools.HeapSnapshotModel.HeapSnapshotModel.NodeFilter();
|
|
31
|
+
const aggregates = await snapshot.aggregatesWithFilter(filter);
|
|
32
|
+
for (const key of Object.keys(aggregates)) {
|
|
33
|
+
const uid = await this.getOrCreateUidForClassKey(filePath, key);
|
|
34
|
+
const aggregate = aggregates[key];
|
|
35
|
+
if (aggregate) {
|
|
36
|
+
aggregate[stableIdSymbol] = uid;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return aggregates;
|
|
40
|
+
}
|
|
41
|
+
async getStats(filePath) {
|
|
42
|
+
const snapshot = await this.getSnapshot(filePath);
|
|
43
|
+
return await snapshot.getStatistics();
|
|
44
|
+
}
|
|
45
|
+
async getStaticData(filePath) {
|
|
46
|
+
const snapshot = await this.getSnapshot(filePath);
|
|
47
|
+
return snapshot.staticData;
|
|
48
|
+
}
|
|
49
|
+
async getOrCreateUidForClassKey(filePath, classKey) {
|
|
50
|
+
const cached = this.#getCachedSnapshot(filePath);
|
|
51
|
+
let uid = cached.classKeyToUid.get(classKey);
|
|
52
|
+
if (!uid) {
|
|
53
|
+
uid = cached.idGenerator();
|
|
54
|
+
cached.classKeyToUid.set(classKey, uid);
|
|
55
|
+
cached.uidToClassKey.set(uid, classKey);
|
|
56
|
+
}
|
|
57
|
+
return uid;
|
|
58
|
+
}
|
|
59
|
+
#getCachedSnapshot(filePath) {
|
|
60
|
+
const absolutePath = path.resolve(filePath);
|
|
61
|
+
const cached = this.#snapshots.get(absolutePath);
|
|
62
|
+
if (!cached) {
|
|
63
|
+
throw new Error(`Snapshot not loaded for ${filePath}`);
|
|
64
|
+
}
|
|
65
|
+
return cached;
|
|
66
|
+
}
|
|
67
|
+
async #loadSnapshot(absolutePath) {
|
|
68
|
+
const workerProxy = new DevTools.HeapSnapshotModel.HeapSnapshotProxy.HeapSnapshotWorkerProxy(() => {
|
|
69
|
+
/* noop */
|
|
70
|
+
}, import.meta.resolve('./third_party/devtools-heap-snapshot-worker.js'));
|
|
71
|
+
const { promise: snapshotPromise, resolve: resolveSnapshot } = Promise.withResolvers();
|
|
72
|
+
const loaderProxy = workerProxy.createLoader(1, snapshotProxy => {
|
|
73
|
+
resolveSnapshot(snapshotProxy);
|
|
74
|
+
});
|
|
75
|
+
const fileStream = fsSync.createReadStream(absolutePath, {
|
|
76
|
+
encoding: 'utf-8',
|
|
77
|
+
highWaterMark: 1024 * 1024,
|
|
78
|
+
});
|
|
79
|
+
for await (const chunk of fileStream) {
|
|
80
|
+
await loaderProxy.write(chunk);
|
|
81
|
+
}
|
|
82
|
+
await loaderProxy.close();
|
|
83
|
+
const snapshot = await snapshotPromise;
|
|
84
|
+
return { snapshot, worker: workerProxy };
|
|
85
|
+
}
|
|
86
|
+
dispose(filePath) {
|
|
87
|
+
const absolutePath = path.resolve(filePath);
|
|
88
|
+
const cached = this.#snapshots.get(absolutePath);
|
|
89
|
+
if (cached) {
|
|
90
|
+
cached.worker.dispose();
|
|
91
|
+
this.#snapshots.delete(absolutePath);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
package/build/src/McpContext.js
CHANGED
|
@@ -6,31 +6,17 @@
|
|
|
6
6
|
import fs from 'node:fs/promises';
|
|
7
7
|
import path from 'node:path';
|
|
8
8
|
import { UniverseManager } from './DevtoolsUtils.js';
|
|
9
|
+
import { HeapSnapshotManager } from './HeapSnapshotManager.js';
|
|
9
10
|
import { McpPage } from './McpPage.js';
|
|
10
11
|
import { NetworkCollector, ConsoleCollector, } from './PageCollector.js';
|
|
11
12
|
import { Locator } from './third_party/index.js';
|
|
12
13
|
import { PredefinedNetworkConditions } from './third_party/index.js';
|
|
13
14
|
import { listPages } from './tools/pages.js';
|
|
14
15
|
import { CLOSE_PAGE_ERROR } from './tools/ToolDefinition.js';
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
import { WaitForHelper } from './WaitForHelper.js';
|
|
16
|
+
import { ensureExtension, saveTemporaryFile } from './utils/files.js';
|
|
17
|
+
import { getNetworkMultiplierFromString } from './WaitForHelper.js';
|
|
18
18
|
const DEFAULT_TIMEOUT = 5_000;
|
|
19
19
|
const NAVIGATION_TIMEOUT = 10_000;
|
|
20
|
-
function getNetworkMultiplierFromString(condition) {
|
|
21
|
-
const puppeteerCondition = condition;
|
|
22
|
-
switch (puppeteerCondition) {
|
|
23
|
-
case 'Fast 4G':
|
|
24
|
-
return 1;
|
|
25
|
-
case 'Slow 4G':
|
|
26
|
-
return 2.5;
|
|
27
|
-
case 'Fast 3G':
|
|
28
|
-
return 5;
|
|
29
|
-
case 'Slow 3G':
|
|
30
|
-
return 10;
|
|
31
|
-
}
|
|
32
|
-
return 1;
|
|
33
|
-
}
|
|
34
20
|
export class McpContext {
|
|
35
21
|
browser;
|
|
36
22
|
logger;
|
|
@@ -45,18 +31,16 @@ export class McpContext {
|
|
|
45
31
|
#networkCollector;
|
|
46
32
|
#consoleCollector;
|
|
47
33
|
#devtoolsUniverseManager;
|
|
48
|
-
#extensionRegistry = new ExtensionRegistry();
|
|
49
34
|
#isRunningTrace = false;
|
|
50
35
|
#screenRecorderData = null;
|
|
51
|
-
#inPageTools;
|
|
52
36
|
#nextPageId = 1;
|
|
53
37
|
#extensionPages = new WeakMap();
|
|
54
38
|
#extensionServiceWorkerMap = new WeakMap();
|
|
55
39
|
#nextExtensionServiceWorkerId = 1;
|
|
56
|
-
#nextSnapshotId = 1;
|
|
57
40
|
#traceResults = [];
|
|
58
41
|
#locatorClass;
|
|
59
42
|
#options;
|
|
43
|
+
#heapSnapshotManager = new HeapSnapshotManager();
|
|
60
44
|
constructor(browser, logger, options, locatorClass) {
|
|
61
45
|
this.browser = browser;
|
|
62
46
|
this.logger = logger;
|
|
@@ -71,7 +55,7 @@ export class McpContext {
|
|
|
71
55
|
uncaughtError: event => {
|
|
72
56
|
collect(event);
|
|
73
57
|
},
|
|
74
|
-
|
|
58
|
+
devtoolsAggregatedIssue: event => {
|
|
75
59
|
collect(event);
|
|
76
60
|
},
|
|
77
61
|
};
|
|
@@ -120,29 +104,6 @@ export class McpContext {
|
|
|
120
104
|
}
|
|
121
105
|
return this.#networkCollector.getIdForResource(request);
|
|
122
106
|
}
|
|
123
|
-
resolveCdpElementId(page, cdpBackendNodeId) {
|
|
124
|
-
if (!cdpBackendNodeId) {
|
|
125
|
-
this.logger('no cdpBackendNodeId');
|
|
126
|
-
return;
|
|
127
|
-
}
|
|
128
|
-
const snapshot = page.textSnapshot;
|
|
129
|
-
if (!snapshot) {
|
|
130
|
-
this.logger('no text snapshot');
|
|
131
|
-
return;
|
|
132
|
-
}
|
|
133
|
-
// TODO: index by backendNodeId instead.
|
|
134
|
-
const queue = [snapshot.root];
|
|
135
|
-
while (queue.length) {
|
|
136
|
-
const current = queue.pop();
|
|
137
|
-
if (current.backendNodeId === cdpBackendNodeId) {
|
|
138
|
-
return current.id;
|
|
139
|
-
}
|
|
140
|
-
for (const child of current.children) {
|
|
141
|
-
queue.push(child);
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
return;
|
|
145
|
-
}
|
|
146
107
|
getNetworkRequests(page, includePreservedRequests) {
|
|
147
108
|
return this.#networkCollector.getData(page.pptrPage, includePreservedRequests);
|
|
148
109
|
}
|
|
@@ -329,12 +290,6 @@ export class McpContext {
|
|
|
329
290
|
this.#selectedPage = newPage;
|
|
330
291
|
this.#updateSelectedPageTimeouts();
|
|
331
292
|
}
|
|
332
|
-
setInPageTools(toolGroup) {
|
|
333
|
-
this.#inPageTools = toolGroup;
|
|
334
|
-
}
|
|
335
|
-
getInPageTools() {
|
|
336
|
-
return this.#inPageTools;
|
|
337
|
-
}
|
|
338
293
|
#updateSelectedPageTimeouts() {
|
|
339
294
|
const page = this.#getSelectedMcpPage();
|
|
340
295
|
// For waiters 5sec timeout should be sufficient.
|
|
@@ -507,113 +462,12 @@ export class McpContext {
|
|
|
507
462
|
getIsolatedContextName(page) {
|
|
508
463
|
return this.#mcpPages.get(page)?.isolatedContextName;
|
|
509
464
|
}
|
|
510
|
-
getDevToolsPage(page) {
|
|
511
|
-
return this.#mcpPages.get(page)?.devToolsPage;
|
|
512
|
-
}
|
|
513
|
-
async getDevToolsData(page) {
|
|
514
|
-
try {
|
|
515
|
-
this.logger('Getting DevTools UI data');
|
|
516
|
-
const devtoolsPage = this.getDevToolsPage(page.pptrPage);
|
|
517
|
-
if (!devtoolsPage) {
|
|
518
|
-
this.logger('No DevTools page detected');
|
|
519
|
-
return {};
|
|
520
|
-
}
|
|
521
|
-
const { cdpRequestId, cdpBackendNodeId } = await devtoolsPage.evaluate(async () => {
|
|
522
|
-
// @ts-expect-error no types
|
|
523
|
-
const UI = await import('/bundled/ui/legacy/legacy.js');
|
|
524
|
-
// @ts-expect-error no types
|
|
525
|
-
const SDK = await import('/bundled/core/sdk/sdk.js');
|
|
526
|
-
const request = UI.Context.Context.instance().flavor(SDK.NetworkRequest.NetworkRequest);
|
|
527
|
-
const node = UI.Context.Context.instance().flavor(SDK.DOMModel.DOMNode);
|
|
528
|
-
return {
|
|
529
|
-
cdpRequestId: request?.requestId(),
|
|
530
|
-
cdpBackendNodeId: node?.backendNodeId(),
|
|
531
|
-
};
|
|
532
|
-
});
|
|
533
|
-
return { cdpBackendNodeId, cdpRequestId };
|
|
534
|
-
}
|
|
535
|
-
catch (err) {
|
|
536
|
-
this.logger('error getting devtools data', err);
|
|
537
|
-
}
|
|
538
|
-
return {};
|
|
539
|
-
}
|
|
540
|
-
/**
|
|
541
|
-
* Creates a text snapshot of a page.
|
|
542
|
-
*/
|
|
543
|
-
async createTextSnapshot(page, verbose = false, devtoolsData = undefined) {
|
|
544
|
-
const rootNode = await page.pptrPage.accessibility.snapshot({
|
|
545
|
-
includeIframes: true,
|
|
546
|
-
interestingOnly: !verbose,
|
|
547
|
-
});
|
|
548
|
-
if (!rootNode) {
|
|
549
|
-
return;
|
|
550
|
-
}
|
|
551
|
-
const { uniqueBackendNodeIdToMcpId } = page;
|
|
552
|
-
const snapshotId = this.#nextSnapshotId++;
|
|
553
|
-
// Iterate through the whole accessibility node tree and assign node ids that
|
|
554
|
-
// will be used for the tree serialization and mapping ids back to nodes.
|
|
555
|
-
let idCounter = 0;
|
|
556
|
-
const idToNode = new Map();
|
|
557
|
-
const seenUniqueIds = new Set();
|
|
558
|
-
const assignIds = (node) => {
|
|
559
|
-
let id = '';
|
|
560
|
-
// @ts-expect-error untyped loaderId & backendNodeId.
|
|
561
|
-
const uniqueBackendId = `${node.loaderId}_${node.backendNodeId}`;
|
|
562
|
-
if (uniqueBackendNodeIdToMcpId.has(uniqueBackendId)) {
|
|
563
|
-
// Re-use MCP exposed ID if the uniqueId is the same.
|
|
564
|
-
id = uniqueBackendNodeIdToMcpId.get(uniqueBackendId);
|
|
565
|
-
}
|
|
566
|
-
else {
|
|
567
|
-
// Only generate a new ID if we have not seen the node before.
|
|
568
|
-
id = `${snapshotId}_${idCounter++}`;
|
|
569
|
-
uniqueBackendNodeIdToMcpId.set(uniqueBackendId, id);
|
|
570
|
-
}
|
|
571
|
-
seenUniqueIds.add(uniqueBackendId);
|
|
572
|
-
const nodeWithId = {
|
|
573
|
-
...node,
|
|
574
|
-
id,
|
|
575
|
-
children: node.children
|
|
576
|
-
? node.children.map(child => assignIds(child))
|
|
577
|
-
: [],
|
|
578
|
-
};
|
|
579
|
-
// The AXNode for an option doesn't contain its `value`.
|
|
580
|
-
// Therefore, set text content of the option as value.
|
|
581
|
-
if (node.role === 'option') {
|
|
582
|
-
const optionText = node.name;
|
|
583
|
-
if (optionText) {
|
|
584
|
-
nodeWithId.value = optionText.toString();
|
|
585
|
-
}
|
|
586
|
-
}
|
|
587
|
-
idToNode.set(nodeWithId.id, nodeWithId);
|
|
588
|
-
return nodeWithId;
|
|
589
|
-
};
|
|
590
|
-
const rootNodeWithId = assignIds(rootNode);
|
|
591
|
-
const snapshot = {
|
|
592
|
-
root: rootNodeWithId,
|
|
593
|
-
snapshotId: String(snapshotId),
|
|
594
|
-
idToNode,
|
|
595
|
-
hasSelectedElement: false,
|
|
596
|
-
verbose,
|
|
597
|
-
};
|
|
598
|
-
page.textSnapshot = snapshot;
|
|
599
|
-
const data = devtoolsData ?? (await this.getDevToolsData(page));
|
|
600
|
-
if (data?.cdpBackendNodeId) {
|
|
601
|
-
snapshot.hasSelectedElement = true;
|
|
602
|
-
snapshot.selectedElementUid = this.resolveCdpElementId(page, data?.cdpBackendNodeId);
|
|
603
|
-
}
|
|
604
|
-
// Clean up unique IDs that we did not see anymore.
|
|
605
|
-
for (const key of uniqueBackendNodeIdToMcpId.keys()) {
|
|
606
|
-
if (!seenUniqueIds.has(key)) {
|
|
607
|
-
uniqueBackendNodeIdToMcpId.delete(key);
|
|
608
|
-
}
|
|
609
|
-
}
|
|
610
|
-
}
|
|
611
465
|
async saveTemporaryFile(data, filename) {
|
|
612
466
|
return await saveTemporaryFile(data, filename);
|
|
613
467
|
}
|
|
614
|
-
async saveFile(data,
|
|
468
|
+
async saveFile(data, clientProvidedFilePath, extension) {
|
|
615
469
|
try {
|
|
616
|
-
const filePath = path.resolve(
|
|
470
|
+
const filePath = ensureExtension(path.resolve(clientProvidedFilePath), extension);
|
|
617
471
|
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
618
472
|
await fs.writeFile(filePath, data);
|
|
619
473
|
return { filename: filePath };
|
|
@@ -631,16 +485,6 @@ export class McpContext {
|
|
|
631
485
|
recordedTraces() {
|
|
632
486
|
return this.#traceResults;
|
|
633
487
|
}
|
|
634
|
-
getWaitForHelper(page, cpuMultiplier, networkMultiplier) {
|
|
635
|
-
return new WaitForHelper(page, cpuMultiplier, networkMultiplier);
|
|
636
|
-
}
|
|
637
|
-
waitForEventsAfterAction(action, options) {
|
|
638
|
-
const page = this.#getSelectedMcpPage();
|
|
639
|
-
const cpuMultiplier = page.cpuThrottlingRate;
|
|
640
|
-
const networkMultiplier = getNetworkMultiplierFromString(page.networkConditions);
|
|
641
|
-
const waitForHelper = this.getWaitForHelper(page.pptrPage, cpuMultiplier, networkMultiplier);
|
|
642
|
-
return waitForHelper.waitForEventsAfterAction(action, options);
|
|
643
|
-
}
|
|
644
488
|
getNetworkRequestStableId(request) {
|
|
645
489
|
return this.#networkCollector.getIdForResource(request);
|
|
646
490
|
}
|
|
@@ -675,33 +519,34 @@ export class McpContext {
|
|
|
675
519
|
}
|
|
676
520
|
async installExtension(extensionPath) {
|
|
677
521
|
const id = await this.browser.installExtension(extensionPath);
|
|
678
|
-
await this.#extensionRegistry.registerExtension(id, extensionPath);
|
|
679
522
|
return id;
|
|
680
523
|
}
|
|
681
524
|
async uninstallExtension(id) {
|
|
682
525
|
await this.browser.uninstallExtension(id);
|
|
683
|
-
this.#extensionRegistry.remove(id);
|
|
684
526
|
}
|
|
685
527
|
async triggerExtensionAction(id) {
|
|
686
|
-
const
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
const session = await this.browser.target().createCDPSession();
|
|
691
|
-
try {
|
|
692
|
-
await session.send('Extensions.triggerAction', {
|
|
693
|
-
id,
|
|
694
|
-
targetId: theTarget,
|
|
695
|
-
});
|
|
696
|
-
}
|
|
697
|
-
finally {
|
|
698
|
-
await session.detach();
|
|
528
|
+
const extensions = await this.browser.extensions();
|
|
529
|
+
const extension = extensions.get(id);
|
|
530
|
+
if (!extension) {
|
|
531
|
+
throw new Error(`Extension with ID ${id} not found.`);
|
|
699
532
|
}
|
|
533
|
+
const page = this.getSelectedPptrPage();
|
|
534
|
+
await extension.triggerAction(page);
|
|
700
535
|
}
|
|
701
536
|
listExtensions() {
|
|
702
|
-
return this
|
|
537
|
+
return this.browser.extensions();
|
|
538
|
+
}
|
|
539
|
+
async getExtension(id) {
|
|
540
|
+
const pptrExtensions = await this.browser.extensions();
|
|
541
|
+
return pptrExtensions.get(id);
|
|
542
|
+
}
|
|
543
|
+
async getHeapSnapshotAggregates(filePath) {
|
|
544
|
+
return await this.#heapSnapshotManager.getAggregates(filePath);
|
|
545
|
+
}
|
|
546
|
+
async getHeapSnapshotStats(filePath) {
|
|
547
|
+
return await this.#heapSnapshotManager.getStats(filePath);
|
|
703
548
|
}
|
|
704
|
-
|
|
705
|
-
return this.#
|
|
549
|
+
async getHeapSnapshotStaticData(filePath) {
|
|
550
|
+
return await this.#heapSnapshotManager.getStaticData(filePath);
|
|
706
551
|
}
|
|
707
552
|
}
|