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.
Files changed (51) hide show
  1. package/README.md +87 -21
  2. package/build/src/HeapSnapshotManager.js +94 -0
  3. package/build/src/McpContext.js +26 -181
  4. package/build/src/McpPage.js +214 -0
  5. package/build/src/McpResponse.js +151 -13
  6. package/build/src/PageCollector.js +10 -24
  7. package/build/src/TextSnapshot.js +230 -0
  8. package/build/src/WaitForHelper.js +31 -0
  9. package/build/src/bin/check-latest-version.js +25 -0
  10. package/build/src/bin/chrome-devtools-mcp-cli-options.js +34 -10
  11. package/build/src/bin/chrome-devtools-mcp-main.js +2 -0
  12. package/build/src/bin/chrome-devtools.js +25 -14
  13. package/build/src/bin/cliDefinitions.js +14 -8
  14. package/build/src/daemon/client.js +11 -11
  15. package/build/src/daemon/daemon.js +6 -9
  16. package/build/src/daemon/utils.js +19 -14
  17. package/build/src/formatters/HeapSnapshotFormatter.js +38 -0
  18. package/build/src/formatters/NetworkFormatter.js +24 -7
  19. package/build/src/index.js +12 -1
  20. package/build/src/telemetry/ClearcutLogger.js +34 -12
  21. package/build/src/telemetry/flagUtils.js +46 -4
  22. package/build/src/telemetry/toolMetricsUtils.js +88 -0
  23. package/build/src/telemetry/watchdog/ClearcutSender.js +4 -3
  24. package/build/src/third_party/THIRD_PARTY_NOTICES +59 -32
  25. package/build/src/third_party/bundled-packages.json +6 -4
  26. package/build/src/third_party/devtools-formatter-worker.js +61 -64
  27. package/build/src/third_party/devtools-heap-snapshot-worker.js +9690 -0
  28. package/build/src/third_party/index.js +62661 -60590
  29. package/build/src/third_party/lighthouse-devtools-mcp-bundle.js +3501 -2658
  30. package/build/src/tools/categories.js +3 -0
  31. package/build/src/tools/console.js +42 -39
  32. package/build/src/tools/emulation.js +1 -1
  33. package/build/src/tools/extensions.js +5 -11
  34. package/build/src/tools/inPage.js +3 -13
  35. package/build/src/tools/input.js +15 -16
  36. package/build/src/tools/lighthouse.js +2 -2
  37. package/build/src/tools/memory.js +48 -3
  38. package/build/src/tools/network.js +4 -4
  39. package/build/src/tools/pages.js +212 -146
  40. package/build/src/tools/performance.js +1 -1
  41. package/build/src/tools/screencast.js +20 -8
  42. package/build/src/tools/screenshot.js +3 -3
  43. package/build/src/tools/script.js +22 -16
  44. package/build/src/tools/tools.js +2 -0
  45. package/build/src/tools/webmcp.js +63 -0
  46. package/build/src/utils/check-for-updates.js +73 -0
  47. package/build/src/utils/files.js +4 -0
  48. package/build/src/utils/id.js +15 -0
  49. package/build/src/version.js +1 -1
  50. package/package.json +13 -8
  51. package/build/src/utils/ExtensionRegistry.js +0 -35
package/README.md CHANGED
@@ -1,11 +1,12 @@
1
- # Chrome DevTools MCP
1
+ # Chrome DevTools for Agents
2
2
 
3
3
  [![npm chrome-devtools-mcp package](https://img.shields.io/npm/v/chrome-devtools-mcp.svg)](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 browser may work, but this is not guaranteed, and you may encounter unexpected behavior. Use at your own discretion.
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 install <a href="https://code.visualstudio.com/docs/copilot/chat/mcp-servers#_add-an-mcp-server">guide</a>,
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
- **Click the button to install:**
407
-
408
- [<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)
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** (4 tools)
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 remoted debugging server to be started in the Chrome instance via chrome://inspect/#remote-debugging.
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:** `stable`, `canary`, `beta`, `dev`
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
+ }
@@ -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 { ExtensionRegistry, } from './utils/ExtensionRegistry.js';
16
- import { saveTemporaryFile } from './utils/files.js';
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
- issue: event => {
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, filename) {
468
+ async saveFile(data, clientProvidedFilePath, extension) {
615
469
  try {
616
- const filePath = path.resolve(filename);
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 page = this.getSelectedPptrPage();
687
- // @ts-expect-error internal puppeteer api is needed since we don't have a way to get
688
- // a tab id at the moment
689
- const theTarget = page._tabId;
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.#extensionRegistry.list();
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
- getExtension(id) {
705
- return this.#extensionRegistry.getById(id);
549
+ async getHeapSnapshotStaticData(filePath) {
550
+ return await this.#heapSnapshotManager.getStaticData(filePath);
706
551
  }
707
552
  }