chrome-devtools-mcp 1.3.0 → 1.5.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 (40) hide show
  1. package/README.md +42 -5
  2. package/build/src/HeapSnapshotManager.js +60 -3
  3. package/build/src/McpContext.js +47 -5
  4. package/build/src/McpResponse.js +66 -7
  5. package/build/src/bin/check-latest-version.js +1 -0
  6. package/build/src/bin/chrome-devtools-cli-options.js +51 -3
  7. package/build/src/bin/chrome-devtools-mcp-cli-options.js +3 -3
  8. package/build/src/bin/chrome-devtools.js +25 -1
  9. package/build/src/daemon/daemon.js +1 -1
  10. package/build/src/formatters/HeapSnapshotFormatter.js +43 -0
  11. package/build/src/formatters/NetworkFormatter.js +3 -1
  12. package/build/src/index.js +2 -2
  13. package/build/src/issue-descriptions.js +6 -4
  14. package/build/src/third_party/THIRD_PARTY_NOTICES +3 -33
  15. package/build/src/third_party/bundled-packages.json +3 -4
  16. package/build/src/third_party/devtools-heap-snapshot-worker.js +94 -0
  17. package/build/src/third_party/index.js +2396 -896
  18. package/build/src/tools/memory.js +60 -4
  19. package/build/src/tools/screencast.js +1 -2
  20. package/build/src/tools/script.js +3 -10
  21. package/build/src/tools/thirdPartyDeveloper.js +6 -6
  22. package/build/src/utils/check-for-updates.js +1 -0
  23. package/build/src/utils/files.js +0 -4
  24. package/build/src/version.js +1 -1
  25. package/package.json +15 -6
  26. package/skills/a11y-debugging/SKILL.md +89 -0
  27. package/skills/a11y-debugging/references/a11y-snippets.md +92 -0
  28. package/skills/chrome-devtools/SKILL.md +72 -0
  29. package/skills/chrome-devtools-cli/SKILL.md +153 -0
  30. package/skills/chrome-devtools-cli/references/installation.md +14 -0
  31. package/skills/debug-optimize-lcp/SKILL.md +121 -0
  32. package/skills/debug-optimize-lcp/references/elements-and-size.md +27 -0
  33. package/skills/debug-optimize-lcp/references/lcp-breakdown.md +23 -0
  34. package/skills/debug-optimize-lcp/references/lcp-snippets.md +79 -0
  35. package/skills/debug-optimize-lcp/references/optimization-strategies.md +38 -0
  36. package/skills/memory-leak-debugging/SKILL.md +50 -0
  37. package/skills/memory-leak-debugging/references/common-leaks.md +33 -0
  38. package/skills/memory-leak-debugging/references/compare_snapshots.js +109 -0
  39. package/skills/memory-leak-debugging/references/memlab.md +29 -0
  40. package/skills/troubleshooting/SKILL.md +98 -0
package/README.md CHANGED
@@ -121,9 +121,9 @@ To use the Chrome DevTools MCP server follow the instructions from <a href="http
121
121
  "chrome-devtools": {
122
122
  "command": "npx",
123
123
  "args": [
124
+ "-y",
124
125
  "chrome-devtools-mcp@latest",
125
- "--browser-url=http://127.0.0.1:9222",
126
- "-y"
126
+ "--browser-url=http://127.0.0.1:9222"
127
127
  ]
128
128
  }
129
129
  }
@@ -514,12 +514,14 @@ If you run into any issues, checkout our [troubleshooting guide](./docs/troubles
514
514
  - [`take_snapshot`](docs/tool-reference.md#take_snapshot)
515
515
  - [`screencast_start`](docs/tool-reference.md#screencast_start)
516
516
  - [`screencast_stop`](docs/tool-reference.md#screencast_stop)
517
- - **Memory** (9 tools)
517
+ - **Memory** (11 tools)
518
518
  - [`take_heapsnapshot`](docs/tool-reference.md#take_heapsnapshot)
519
519
  - [`close_heapsnapshot`](docs/tool-reference.md#close_heapsnapshot)
520
+ - [`compare_heapsnapshots`](docs/tool-reference.md#compare_heapsnapshots)
520
521
  - [`get_heapsnapshot_class_nodes`](docs/tool-reference.md#get_heapsnapshot_class_nodes)
521
522
  - [`get_heapsnapshot_details`](docs/tool-reference.md#get_heapsnapshot_details)
522
523
  - [`get_heapsnapshot_dominators`](docs/tool-reference.md#get_heapsnapshot_dominators)
524
+ - [`get_heapsnapshot_duplicate_strings`](docs/tool-reference.md#get_heapsnapshot_duplicate_strings)
523
525
  - [`get_heapsnapshot_edges`](docs/tool-reference.md#get_heapsnapshot_edges)
524
526
  - [`get_heapsnapshot_retainers`](docs/tool-reference.md#get_heapsnapshot_retainers)
525
527
  - [`get_heapsnapshot_retaining_paths`](docs/tool-reference.md#get_heapsnapshot_retaining_paths)
@@ -553,14 +555,17 @@ The Chrome DevTools MCP server supports the following configuration option:
553
555
  - **`--browserUrl`/ `--browser-url`, `-u`**
554
556
  Connect to a running, debuggable Chrome instance (e.g. `http://127.0.0.1:9222`). For more details see: https://github.com/ChromeDevTools/chrome-devtools-mcp#connecting-to-a-running-chrome-instance.
555
557
  - **Type:** string
558
+ - **Default:** `false`
556
559
 
557
560
  - **`--wsEndpoint`/ `--ws-endpoint`, `-w`**
558
561
  WebSocket endpoint to connect to a running Chrome instance (e.g., ws://127.0.0.1:9222/devtools/browser/<id>). Alternative to --browserUrl.
559
562
  - **Type:** string
563
+ - **Default:** `false`
560
564
 
561
565
  - **`--wsHeaders`/ `--ws-headers`**
562
566
  Custom headers for WebSocket connection in JSON format (e.g., '{"Authorization":"Bearer token"}'). Only works with --wsEndpoint.
563
567
  - **Type:** string
568
+ - **Default:** `false`
564
569
 
565
570
  - **`--headless`**
566
571
  Whether to run in headless (no UI) mode.
@@ -570,87 +575,108 @@ The Chrome DevTools MCP server supports the following configuration option:
570
575
  - **`--executablePath`/ `--executable-path`, `-e`**
571
576
  Path to custom Chrome executable.
572
577
  - **Type:** string
578
+ - **Default:** `false`
573
579
 
574
580
  - **`--isolated`**
575
581
  If specified, creates a temporary user-data-dir that is automatically cleaned up after the browser is closed. Defaults to false.
576
582
  - **Type:** boolean
583
+ - **Default:** `false`
577
584
 
578
585
  - **`--userDataDir`/ `--user-data-dir`**
579
586
  Path to the user data directory for Chrome. Default is $HOME/.cache/chrome-devtools-mcp/chrome-profile$CHANNEL_SUFFIX_IF_NON_STABLE
580
587
  - **Type:** string
588
+ - **Default:** `false`
581
589
 
582
590
  - **`--channel`**
583
591
  Specify a different Chrome channel that should be used. The default is the stable channel version.
584
592
  - **Type:** string
585
593
  - **Choices:** `canary`, `dev`, `beta`, `stable`
594
+ - **Default:** `false`
586
595
 
587
596
  - **`--logFile`/ `--log-file`**
588
597
  Path to a file to write debug logs to. Set the env variable `DEBUG` to `*` to enable verbose logs. Useful for submitting bug reports.
589
598
  - **Type:** string
599
+ - **Default:** `false`
590
600
 
591
601
  - **`--viewport`**
592
602
  Initial viewport size for the Chrome instances started by the server. For example, `1280x720`. In headless mode, max size is 3840x2160px.
593
603
  - **Type:** string
604
+ - **Default:** `false`
594
605
 
595
606
  - **`--proxyServer`/ `--proxy-server`**
596
607
  Proxy server configuration for Chrome passed as --proxy-server when launching the browser. See https://www.chromium.org/developers/design-documents/network-settings/ for details.
597
608
  - **Type:** string
609
+ - **Default:** `false`
598
610
 
599
611
  - **`--acceptInsecureCerts`/ `--accept-insecure-certs`**
600
612
  If enabled, ignores errors relative to self-signed and expired certificates. Use with caution.
601
613
  - **Type:** boolean
614
+ - **Default:** `false`
602
615
 
603
616
  - **`--experimentalPageIdRouting`/ `--experimental-page-id-routing`**
604
617
  Whether to expose pageId on page-scoped tools and route requests by page ID (useful for concurrent agent sessions).
605
618
  - **Type:** boolean
619
+ - **Default:** `false`
606
620
 
607
621
  - **`--experimentalDevtools`/ `--experimental-devtools`**
608
622
  Whether to enable automation over DevTools targets
609
623
  - **Type:** boolean
624
+ - **Default:** `false`
610
625
 
611
626
  - **`--experimentalVision`/ `--experimental-vision`**
612
627
  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.
613
628
  - **Type:** boolean
629
+ - **Default:** `false`
614
630
 
615
631
  - **`--memoryDebugging`/ `--memory-debugging`, `-experimentalMemory`**
616
632
  Whether to enable memory debugging tools.
617
633
  - **Type:** boolean
634
+ - **Default:** `false`
618
635
 
619
636
  - **`--experimentalStructuredContent`/ `--experimental-structured-content`**
620
637
  Whether to output structured formatted content.
621
638
  - **Type:** boolean
639
+ - **Default:** `false`
622
640
 
623
641
  - **`--experimentalIncludeAllPages`/ `--experimental-include-all-pages`**
624
642
  Whether to include all kinds of pages such as webviews or background pages as pages.
625
643
  - **Type:** boolean
644
+ - **Default:** `false`
626
645
 
627
646
  - **`--experimentalScreencast`/ `--experimental-screencast`**
628
647
  Exposes experimental screencast tools (requires ffmpeg). Install ffmpeg https://www.ffmpeg.org/download.html and ensure it is available in the MCP server PATH.
629
648
  - **Type:** boolean
649
+ - **Default:** `false`
630
650
 
631
651
  - **`--experimentalFfmpegPath`/ `--experimental-ffmpeg-path`**
632
652
  Path to ffmpeg executable for screencast recording.
633
653
  - **Type:** string
654
+ - **Default:** `false`
634
655
 
635
656
  - **`--categoryExperimentalWebmcp`/ `--category-experimental-webmcp`**
636
657
  Set to true to enable debugging WebMCP tools. Requires Chrome 149+ with the following flags: `--enable-features=WebMCP,DevToolsWebMCPSupport`
637
658
  - **Type:** boolean
659
+ - **Default:** `false`
638
660
 
639
661
  - **`--chromeArg`/ `--chrome-arg`**
640
662
  Additional arguments for Chrome. Only applies when Chrome is launched by chrome-devtools-mcp.
641
663
  - **Type:** array
664
+ - **Default:** `false`
642
665
 
643
666
  - **`--blockedUrlPattern`/ `--blocked-url-pattern`**
644
- Restricts network access by blocking specified URL patterns (uses https://urlpattern.spec.whatwg.org/). Silently detaches from targets with blocked URLs upon connection, and blocks runtime requests (including navigations and subresources). Accepts an array of patterns.
667
+ Restricts browser's network access by blocking specified URL patterns (uses https://urlpattern.spec.whatwg.org/). Silently detaches from targets with blocked URLs upon connection, and blocks runtime requests (including navigations and subresources). Accepts an array of patterns.
645
668
  - **Type:** array
669
+ - **Default:** `false`
646
670
 
647
671
  - **`--allowedUrlPattern`/ `--allowed-url-pattern`**
648
- Restricts network access by allowing only specified URL patterns (uses https://urlpattern.spec.whatwg.org/). Requires Chrome 149+. Silently detaches from targets with unallowed URLs upon connection, and blocks runtime requests (including navigations and subresources). Accepts an array of patterns.
672
+ Restricts browser's network access by allowing only specified URL patterns (uses https://urlpattern.spec.whatwg.org/). Requires Chrome 149+. Silently detaches from targets with unallowed URLs upon connection, and blocks runtime requests (including navigations and subresources). Accepts an array of patterns.
649
673
  - **Type:** array
674
+ - **Default:** `false`
650
675
 
651
676
  - **`--ignoreDefaultChromeArg`/ `--ignore-default-chrome-arg`**
652
677
  Explicitly disable default arguments for Chrome. Only applies when Chrome is launched by chrome-devtools-mcp.
653
678
  - **Type:** array
679
+ - **Default:** `false`
654
680
 
655
681
  - **`--categoryEmulation`/ `--category-emulation`**
656
682
  Set to false to exclude tools related to emulation.
@@ -691,22 +717,27 @@ The Chrome DevTools MCP server supports the following configuration option:
691
717
  Override the default output format used by take_screenshot when the caller does not specify one. JPEG and WebP are ~3-5x smaller than PNG, which helps reduce context size in AI conversations. Unset preserves the existing default ("png").
692
718
  - **Type:** string
693
719
  - **Choices:** `jpeg`, `png`, `webp`
720
+ - **Default:** `false`
694
721
 
695
722
  - **`--screenshotQuality`/ `--screenshot-quality`**
696
723
  Override the default compression quality (0-100) used by take_screenshot for JPEG and WebP when the caller does not specify one. Lower values mean smaller files. Ignored for PNG. Unset preserves the Puppeteer default.
697
724
  - **Type:** number
725
+ - **Default:** `false`
698
726
 
699
727
  - **`--screenshotMaxWidth`/ `--screenshot-max-width`**
700
728
  Maximum width in pixels for screenshots. If the captured image is wider, it is downscaled (preserving aspect ratio) before being returned. Reduces context size in AI conversations. Unset means no resize.
701
729
  - **Type:** number
730
+ - **Default:** `false`
702
731
 
703
732
  - **`--screenshotMaxHeight`/ `--screenshot-max-height`**
704
733
  Maximum height in pixels for screenshots. If the captured image is taller, it is downscaled (preserving aspect ratio) before being returned. Can be combined with --screenshot-max-width; the smaller scale factor wins. Unset means no resize.
705
734
  - **Type:** number
735
+ - **Default:** `false`
706
736
 
707
737
  - **`--slim`**
708
738
  Exposes a "slim" set of 3 tools covering navigation, script execution and screenshots only. Useful for basic browser tasks.
709
739
  - **Type:** boolean
740
+ - **Default:** `false`
710
741
 
711
742
  - **`--redactNetworkHeaders`/ `--redact-network-headers`**
712
743
  If true, redacts some of the network headers considered sensitive before returning to the client.
@@ -929,3 +960,9 @@ Please consult [these instructions](./docs/debugging-android.md).
929
960
  ## Known limitations
930
961
 
931
962
  See [Troubleshooting](./docs/troubleshooting.md).
963
+
964
+ ## Integrating as a browser subagent
965
+
966
+ If you are developing agentic tooling and want to provide an integrated browser subagent as part of your product, we recommend building on top of Chrome DevTools for agents.
967
+
968
+ For a reference implementation, see the [Gemini CLI browser agent documentation](https://geminicli.com/docs/core/subagents/#browser-agent).
@@ -8,6 +8,7 @@ import path from 'node:path';
8
8
  import { DevTools } from './third_party/index.js';
9
9
  import { createIdGenerator, stableIdSymbol, } from './utils/id.js';
10
10
  export class HeapSnapshotManager {
11
+ #snapshotIdGenerator = createIdGenerator();
11
12
  #snapshots = new Map();
12
13
  async getSnapshot(filePath) {
13
14
  const absolutePath = path.resolve(filePath);
@@ -15,7 +16,8 @@ export class HeapSnapshotManager {
15
16
  if (cached) {
16
17
  return cached.snapshot;
17
18
  }
18
- const { snapshot, worker } = await this.#loadSnapshot(absolutePath);
19
+ const uid = this.#snapshotIdGenerator();
20
+ const { snapshot, worker } = await this.#loadSnapshot(absolutePath, uid);
19
21
  this.#snapshots.set(absolutePath, {
20
22
  snapshot,
21
23
  worker,
@@ -100,6 +102,38 @@ export class HeapSnapshotManager {
100
102
  const provider = snapshot.createEdgesProvider(nodeIndex);
101
103
  return await provider.serializeItemsRange(0, Infinity);
102
104
  }
105
+ async getClassDiffs(baseFilePath, currentFilePath) {
106
+ const rawDiffs = await this.#getSortedRawClassDiffs(baseFilePath, currentFilePath);
107
+ return rawDiffs.map(rawDiff => ({
108
+ className: rawDiff.name,
109
+ addedCount: rawDiff.addedCount,
110
+ removedCount: rawDiff.removedCount,
111
+ countDelta: rawDiff.countDelta,
112
+ addedSize: rawDiff.addedSize,
113
+ removedSize: rawDiff.removedSize,
114
+ sizeDelta: rawDiff.sizeDelta,
115
+ }));
116
+ }
117
+ async getDetailedClassDiff(baseFilePath, currentFilePath, classIndex) {
118
+ const classDiffs = await this.#getSortedRawClassDiffs(baseFilePath, currentFilePath);
119
+ const rawDiff = classDiffs[classIndex];
120
+ if (!rawDiff) {
121
+ throw new Error(`Invalid classIndex: ${classIndex}. Total classes with changes: ${classDiffs.length}`);
122
+ }
123
+ return {
124
+ className: rawDiff.name,
125
+ addedCount: rawDiff.addedCount,
126
+ removedCount: rawDiff.removedCount,
127
+ countDelta: rawDiff.countDelta,
128
+ addedSize: rawDiff.addedSize,
129
+ removedSize: rawDiff.removedSize,
130
+ sizeDelta: rawDiff.sizeDelta,
131
+ addedIds: rawDiff.addedIds ?? [],
132
+ addedSelfSizes: rawDiff.addedSelfSizes ?? [],
133
+ deletedIds: rawDiff.deletedIds ?? [],
134
+ deletedSelfSizes: rawDiff.deletedSelfSizes ?? [],
135
+ };
136
+ }
103
137
  #getCachedSnapshot(filePath) {
104
138
  const absolutePath = path.resolve(filePath);
105
139
  const cached = this.#snapshots.get(absolutePath);
@@ -108,16 +142,35 @@ export class HeapSnapshotManager {
108
142
  }
109
143
  return cached;
110
144
  }
145
+ async #getSortedRawClassDiffs(baseFilePath, currentFilePath) {
146
+ const baseSnapshot = await this.getSnapshot(baseFilePath);
147
+ const currentSnapshot = await this.getSnapshot(currentFilePath);
148
+ const interfaceDefinitions = await currentSnapshot.interfaceDefinitions();
149
+ const aggregatesForDiff = await baseSnapshot.aggregatesForDiff(interfaceDefinitions);
150
+ const baseSnapshotId = baseSnapshot.uid;
151
+ if (baseSnapshotId === undefined) {
152
+ throw new Error('Base snapshot UID is undefined');
153
+ }
154
+ // DevTools calculateSnapshotDiff uses the first parameter (baseSnapshotId)
155
+ // as a cache key. We pass the unique UID of the base snapshot.
156
+ const rawDiffs = await currentSnapshot.calculateSnapshotDiff(baseSnapshotId, aggregatesForDiff);
157
+ // Return a filtered and sorted array here to ensure that
158
+ // compare_heapsnapshot_summary and compare_heapsnapshot_details agree
159
+ // on indices.
160
+ return Object.values(rawDiffs)
161
+ .filter(diff => diff.addedCount > 0 || diff.removedCount > 0)
162
+ .sort((a, b) => b.sizeDelta - a.sizeDelta);
163
+ }
111
164
  async resolveClassKeyFromId(filePath, id) {
112
165
  const cached = this.#getCachedSnapshot(filePath);
113
166
  return cached.idToClassKey.get(id);
114
167
  }
115
- async #loadSnapshot(absolutePath) {
168
+ async #loadSnapshot(absolutePath, uid) {
116
169
  const workerProxy = new DevTools.HeapSnapshotModel.HeapSnapshotProxy.HeapSnapshotWorkerProxy(() => {
117
170
  /* noop */
118
171
  }, import.meta.resolve('./third_party/devtools-heap-snapshot-worker.js'));
119
172
  const { promise: snapshotPromise, resolve: resolveSnapshot } = Promise.withResolvers();
120
- const loaderProxy = workerProxy.createLoader(1, snapshotProxy => {
173
+ const loaderProxy = workerProxy.createLoader(uid, snapshotProxy => {
121
174
  resolveSnapshot(snapshotProxy);
122
175
  });
123
176
  const fileStream = fsSync.createReadStream(absolutePath, {
@@ -131,6 +184,10 @@ export class HeapSnapshotManager {
131
184
  const snapshot = await snapshotPromise;
132
185
  return { snapshot, worker: workerProxy };
133
186
  }
187
+ async getDuplicateStrings(filePath) {
188
+ const snapshot = await this.getSnapshot(filePath);
189
+ return await snapshot.getDuplicateStrings();
190
+ }
134
191
  hasSnapshots() {
135
192
  return this.#snapshots.size > 0;
136
193
  }
@@ -16,7 +16,7 @@ import { ServiceWorkerConsoleCollector } from './ServiceWorkerCollector.js';
16
16
  import { Locator, PredefinedNetworkConditions, } from './third_party/index.js';
17
17
  import { listPages } from './tools/pages.js';
18
18
  import { CLOSE_PAGE_ERROR } from './tools/ToolDefinition.js';
19
- import { ensureExtension, getTempFilePath, resolveCanonicalPath, } from './utils/files.js';
19
+ import { getTempFilePath, resolveCanonicalPath } from './utils/files.js';
20
20
  import { getNetworkMultiplierFromString } from './WaitForHelper.js';
21
21
  const DEFAULT_TIMEOUT = 5_000;
22
22
  const NAVIGATION_TIMEOUT = 10_000;
@@ -156,6 +156,13 @@ export class McpContext {
156
156
  throw new Error(`Access denied: path ${filePath} (canonical: ${canonicalPath}) is not within any of the configured workspace roots.`);
157
157
  }
158
158
  }
159
+ async ensureExtension(filePath, extension) {
160
+ const resolvedPath = path.resolve(filePath);
161
+ const currentExtension = path.extname(resolvedPath);
162
+ const outputPath = `${resolvedPath.slice(0, resolvedPath.length - currentExtension.length)}${extension}`;
163
+ await this.validatePath(outputPath);
164
+ return outputPath;
165
+ }
159
166
  resolveCdpRequestId(page, cdpRequestId) {
160
167
  if (!cdpRequestId) {
161
168
  this.logger?.('no network request');
@@ -223,12 +230,15 @@ export class McpContext {
223
230
  const currentSetting = page.emulationSettings;
224
231
  await this.emulate(currentSetting, page.pptrPage);
225
232
  }
233
+ get #hasNetworkBlockOrAllowlist() {
234
+ return !!(this.#options.allowList || this.#options.blocklist);
235
+ }
226
236
  async emulate(options, targetPage) {
227
237
  const page = targetPage ?? this.getSelectedPptrPage();
228
238
  const mcpPage = this.#getMcpPage(page);
229
239
  const newSettings = { ...mcpPage.emulationSettings };
230
240
  // Skip network emulation if blocklist/allowlist is configured, as it conflicts with blocking rules in Puppeteer.
231
- if (this.#options.hasNetworkBlockOrAllowlist) {
241
+ if (this.#hasNetworkBlockOrAllowlist) {
232
242
  if (options.networkConditions !== undefined) {
233
243
  throw new Error('Network throttling is not supported when network blocking (allowlist/blocklist) is configured.');
234
244
  }
@@ -568,9 +578,8 @@ export class McpContext {
568
578
  return { filepath };
569
579
  }
570
580
  async saveFile(data, clientProvidedFilePath, extension) {
571
- await this.validatePath(clientProvidedFilePath);
581
+ const filePath = await this.ensureExtension(clientProvidedFilePath, extension);
572
582
  try {
573
- const filePath = ensureExtension(path.resolve(clientProvidedFilePath), extension);
574
583
  await fs.mkdir(path.dirname(filePath), { recursive: true });
575
584
  await fs.writeFile(filePath, data);
576
585
  return { filename: filePath };
@@ -646,6 +655,9 @@ export class McpContext {
646
655
  async getHeapSnapshotAggregates(filePath) {
647
656
  return await this.#heapSnapshotManager.getAggregates(filePath);
648
657
  }
658
+ async getHeapSnapshotDuplicateStrings(filePath) {
659
+ return await this.#heapSnapshotManager.getDuplicateStrings(filePath);
660
+ }
649
661
  async getHeapSnapshotStats(filePath) {
650
662
  return await this.#heapSnapshotManager.getStats(filePath);
651
663
  }
@@ -670,12 +682,36 @@ export class McpContext {
670
682
  async getHeapSnapshotDominators(filePath, nodeId) {
671
683
  return await this.#heapSnapshotManager.getDominatorsOf(filePath, nodeId);
672
684
  }
685
+ #validateUrlNotBlocked(url) {
686
+ if (!this.#options.blocklist) {
687
+ return;
688
+ }
689
+ for (const block of this.#options.blocklist) {
690
+ const pattern = new URLPattern(block);
691
+ if (pattern.test(url)) {
692
+ throw new Error(`Blocked by blocklist: ${url}`);
693
+ }
694
+ }
695
+ }
696
+ #validateUrlAllowed(url) {
697
+ if (!this.#options.allowList) {
698
+ return;
699
+ }
700
+ for (const allow of this.#options.allowList) {
701
+ const pattern = new URLPattern(allow);
702
+ if (pattern.test(url)) {
703
+ return;
704
+ }
705
+ }
706
+ throw new Error(`Not allowed by allowlist: ${url}`);
707
+ }
673
708
  async loadResource(path) {
674
709
  const url = new URL(path);
710
+ this.#validateUrlNotBlocked(url);
675
711
  switch (url.protocol) {
676
712
  case 'https:':
677
713
  case 'http:': {
678
- // TODO: Verify allow/block list
714
+ this.#validateUrlAllowed(url);
679
715
  const response = await fetch(url);
680
716
  if (!response.ok) {
681
717
  throw new Error(`Failed to load resource: ${url}`);
@@ -693,5 +729,11 @@ export class McpContext {
693
729
  async getHeapSnapshotEdges(filePath, nodeId) {
694
730
  return await this.#heapSnapshotManager.getEdges(filePath, nodeId);
695
731
  }
732
+ async getHeapSnapshotClassDiffs(baseFilePath, currentFilePath) {
733
+ return await this.#heapSnapshotManager.getClassDiffs(baseFilePath, currentFilePath);
734
+ }
735
+ async getHeapSnapshotDetailedClassDiff(baseFilePath, currentFilePath, classIndex) {
736
+ return await this.#heapSnapshotManager.getDetailedClassDiff(baseFilePath, currentFilePath, classIndex);
737
+ }
696
738
  }
697
739
  //# sourceMappingURL=McpContext.js.map
@@ -4,14 +4,13 @@
4
4
  * SPDX-License-Identifier: Apache-2.0
5
5
  */
6
6
  import { ConsoleFormatter } from './formatters/ConsoleFormatter.js';
7
- import { HeapSnapshotFormatter } from './formatters/HeapSnapshotFormatter.js';
8
- import { isEdgeLike, isNodeLike } from './formatters/HeapSnapshotFormatter.js';
7
+ import { HeapSnapshotFormatter, isEdgeLike, isNodeLike, } from './formatters/HeapSnapshotFormatter.js';
9
8
  import { IssueFormatter } from './formatters/IssueFormatter.js';
10
9
  import { NetworkFormatter } from './formatters/NetworkFormatter.js';
11
10
  import { SnapshotFormatter } from './formatters/SnapshotFormatter.js';
12
11
  import { UncaughtError } from './PageCollector.js';
13
12
  import { TextSnapshot } from './TextSnapshot.js';
14
- import { DevTools, toonEncode } from './third_party/index.js';
13
+ import { DevTools, getToonEncode } from './third_party/index.js';
15
14
  import { handleDialog } from './tools/pages.js';
16
15
  import { getInsightOutput, getTraceSummary } from './trace-processing/parse.js';
17
16
  import { paginate } from './utils/pagination.js';
@@ -334,6 +333,14 @@ export class McpResponse {
334
333
  pagination: options,
335
334
  };
336
335
  }
336
+ setHeapSnapshotDuplicateStrings(duplicateStrings, options) {
337
+ this.#heapSnapshotOptions = {
338
+ ...this.#heapSnapshotOptions,
339
+ include: true,
340
+ duplicateStrings,
341
+ pagination: options,
342
+ };
343
+ }
337
344
  setHeapSnapshotRetainingPaths(retainingPaths) {
338
345
  this.#heapSnapshotOptions = {
339
346
  ...this.#heapSnapshotOptions,
@@ -348,6 +355,20 @@ export class McpResponse {
348
355
  dominators,
349
356
  };
350
357
  }
358
+ setHeapSnapshotClassDiffs(classDiffs) {
359
+ this.#heapSnapshotOptions = {
360
+ ...this.#heapSnapshotOptions,
361
+ include: true,
362
+ classDiffs,
363
+ };
364
+ }
365
+ setHeapSnapshotDetailedClassDiff(detailedClassDiff) {
366
+ this.#heapSnapshotOptions = {
367
+ ...this.#heapSnapshotOptions,
368
+ include: true,
369
+ detailedClassDiff,
370
+ };
371
+ }
351
372
  attachImage(value) {
352
373
  this.#images.push(value);
353
374
  }
@@ -548,6 +569,18 @@ export class McpResponse {
548
569
  }
549
570
  async format(toolName, context, data, useToon) {
550
571
  const structuredContent = {};
572
+ let toonEncode;
573
+ if (useToon) {
574
+ try {
575
+ toonEncode = await getToonEncode();
576
+ }
577
+ catch {
578
+ throw new Error('The `@toon-format/toon` package is required to use the experimental TOON format. ' +
579
+ 'Make sure the peer dependency is installed:\n' +
580
+ '- For npx: npx --package chrome-devtools-mcp@latest --package @toon-format/toon@latest chrome-devtools-mcp --experimentalToonFormat\n' +
581
+ '- For npm: npm install @toon-format/toon (add -g if installed globally)');
582
+ }
583
+ }
551
584
  const response = [];
552
585
  if (this.#textResponseLines.length) {
553
586
  structuredContent.message = this.#textResponseLines.join('\n');
@@ -726,7 +759,7 @@ Call ${handleDialog.name} to handle it before continuing.`);
726
759
  else {
727
760
  structuredContent.snapshot = data.snapshot.toJSON();
728
761
  response.push('## Latest page snapshot');
729
- response.push(useToon
762
+ response.push(useToon && toonEncode
730
763
  ? toonEncode(structuredContent.snapshot)
731
764
  : data.snapshot.toString());
732
765
  }
@@ -754,7 +787,7 @@ Call ${handleDialog.name} to handle it before continuing.`);
754
787
  const paginatedRecord = Object.fromEntries(paginationData.items);
755
788
  const formatter = new HeapSnapshotFormatter(paginatedRecord);
756
789
  structuredContent.heapSnapshotData = formatter.toJSON();
757
- response.push(useToon
790
+ response.push(useToon && toonEncode
758
791
  ? toonEncode(structuredContent.heapSnapshotData)
759
792
  : formatter.toString());
760
793
  }
@@ -808,6 +841,32 @@ Call ${handleDialog.name} to handle it before continuing.`);
808
841
  }
809
842
  structuredContent.heapSnapshotDominators = dominators;
810
843
  }
844
+ const classDiffs = this.#heapSnapshotOptions.classDiffs;
845
+ if (classDiffs) {
846
+ response.push('### Heap Snapshot Diff');
847
+ response.push(useToon && toonEncode
848
+ ? toonEncode(classDiffs)
849
+ : HeapSnapshotFormatter.formatDiffSummary(classDiffs));
850
+ structuredContent.heapSnapshotClassDiffs = classDiffs;
851
+ }
852
+ const detailedClassDiff = this.#heapSnapshotOptions.detailedClassDiff;
853
+ if (detailedClassDiff) {
854
+ response.push('### Heap Snapshot Detailed Diff');
855
+ response.push(useToon && toonEncode
856
+ ? toonEncode(detailedClassDiff)
857
+ : HeapSnapshotFormatter.formatDiffDetails(detailedClassDiff));
858
+ structuredContent.heapSnapshotDetailedClassDiff = detailedClassDiff;
859
+ }
860
+ const duplicateStrings = this.#heapSnapshotOptions.duplicateStrings;
861
+ if (duplicateStrings) {
862
+ response.push('### Duplicate Strings');
863
+ const paginationData = this.#dataWithPagination(duplicateStrings, this.#heapSnapshotOptions.pagination);
864
+ structuredContent.pagination = paginationData.pagination;
865
+ response.push(...paginationData.info);
866
+ const formatted = HeapSnapshotFormatter.formatDuplicateStrings(paginationData.items);
867
+ response.push(formatted);
868
+ structuredContent.heapSnapshotDuplicateStrings = paginationData.items;
869
+ }
811
870
  }
812
871
  if (data.detailedNetworkRequest) {
813
872
  response.push(data.detailedNetworkRequest.toStringDetailed());
@@ -879,7 +938,7 @@ Call ${handleDialog.name} to handle it before continuing.`);
879
938
  response.push(...paginationData.info);
880
939
  if (data.networkRequests) {
881
940
  structuredContent.networkRequests = paginationData.items.map(i => i.toJSON());
882
- response.push(...(useToon
941
+ response.push(...(useToon && toonEncode
883
942
  ? [toonEncode(structuredContent.networkRequests)]
884
943
  : paginationData.items.map(i => i.toString())));
885
944
  }
@@ -897,7 +956,7 @@ Call ${handleDialog.name} to handle it before continuing.`);
897
956
  structuredContent.pagination = paginationData.pagination;
898
957
  structuredContent.consoleMessages = paginationData.items.map(item => item.toJSON());
899
958
  response.push(...paginationData.info);
900
- if (useToon) {
959
+ if (useToon && toonEncode) {
901
960
  response.push(toonEncode(structuredContent.consoleMessages));
902
961
  }
903
962
  else {
@@ -18,6 +18,7 @@ function getRegistry() {
18
18
  encoding: 'utf8',
19
19
  stdio: ['ignore', 'pipe', 'ignore'],
20
20
  timeout: 5000,
21
+ windowsHide: true,
21
22
  })
22
23
  .trim()
23
24
  .replace(/\/$/, '');
@@ -82,6 +82,30 @@ export const commands = {
82
82
  },
83
83
  },
84
84
  },
85
+ compare_heapsnapshots: {
86
+ description: 'Loads two memory heapsnapshots and returns the comparison. If classIndex is provided, returns detailed diff for that class, otherwise returns summary diff. (requires flag: --memoryDebugging=true)',
87
+ category: 'Memory',
88
+ args: {
89
+ baseFilePath: {
90
+ name: 'baseFilePath',
91
+ type: 'string',
92
+ description: 'A path to the base .heapsnapshot file (earlier snapshot).',
93
+ required: true,
94
+ },
95
+ currentFilePath: {
96
+ name: 'currentFilePath',
97
+ type: 'string',
98
+ description: 'A path to the current .heapsnapshot file (later snapshot).',
99
+ required: true,
100
+ },
101
+ classIndex: {
102
+ name: 'classIndex',
103
+ type: 'number',
104
+ description: 'Optional 0-based index of the class in the summary list to filter results, showing individual objects.',
105
+ required: false,
106
+ },
107
+ },
108
+ },
85
109
  drag: {
86
110
  description: 'Drag an element onto another element',
87
111
  category: 'Input automation',
@@ -157,13 +181,13 @@ export const commands = {
157
181
  },
158
182
  },
159
183
  evaluate_script: {
160
- description: 'Evaluate a JavaScript function inside the currently selected page. Returns the response as JSON,\nso returned values have to be JSON-serializable.',
184
+ description: 'Evaluate a JavaScript function inside the currently selected page. Returns the response as JSON, so returned values have to be JSON-serializable.',
161
185
  category: 'Debugging',
162
186
  args: {
163
187
  function: {
164
188
  name: 'function',
165
189
  type: 'string',
166
- description: 'A JavaScript function declaration to be executed by the tool in the currently selected page.\nExample without arguments: `() => {\n return document.title\n}` or `async () => {\n return await fetch("example.com")\n}`.\nExample with arguments: `(el) => {\n return el.innerText;\n}`\n',
190
+ description: 'A JavaScript function declaration to be executed by the tool in the currently selected page.\nExample without arguments: `() => document.title` or `async () => await fetch("example.com")`.\nExample with arguments: `(el) => el.innerText`\n',
167
191
  required: true,
168
192
  },
169
193
  args: {
@@ -330,6 +354,30 @@ export const commands = {
330
354
  },
331
355
  },
332
356
  },
357
+ get_heapsnapshot_duplicate_strings: {
358
+ description: 'Loads a memory heapsnapshot and returns duplicate strings grouped by their value. (requires flag: --memoryDebugging=true)',
359
+ category: 'Memory',
360
+ args: {
361
+ filePath: {
362
+ name: 'filePath',
363
+ type: 'string',
364
+ description: 'A path to a .heapsnapshot file to read.',
365
+ required: true,
366
+ },
367
+ pageIdx: {
368
+ name: 'pageIdx',
369
+ type: 'number',
370
+ description: 'The page index for pagination.',
371
+ required: false,
372
+ },
373
+ pageSize: {
374
+ name: 'pageSize',
375
+ type: 'number',
376
+ description: 'The page size for pagination.',
377
+ required: false,
378
+ },
379
+ },
380
+ },
333
381
  get_heapsnapshot_edges: {
334
382
  description: 'Loads a memory heapsnapshot and returns outgoing edges (references) for a specific node ID. (requires flag: --memoryDebugging=true)',
335
383
  category: 'Memory',
@@ -540,7 +588,7 @@ export const commands = {
540
588
  },
541
589
  },
542
590
  list_3p_developer_tools: {
543
- description: "Lists all third-party developer tools the page exposes for providing runtime information.\n Third-party developer tools can be called via the 'execute_3p_developer_tool()' MCP tool.\n Alternatively, third-party developer tools can be executed by calling 'evaluate_script' and adding the\n following command to the script:\n 'window.__dtmcp.executeTool(toolName, params)'\n This might be helpful when the third-party developer tools return non-serializable values or when composing\n third-party developer tools with additional functionality. (requires flag: --categoryExperimentalThirdParty=true)",
591
+ description: "Lists all third-party developer tools the page exposes for providing runtime information.\nThird-party developer tools can be called via the 'execute_3p_developer_tool()' MCP tool.\nAlternatively, third-party developer tools can be executed by calling 'evaluate_script' and adding the\nfollowing command to the script:\n`window.__dtmcp.executeTool(toolName, params)`\nThis might be helpful when the third-party developer tools return non-serializable values or when composing\nthird-party developer tools with additional functionality. (requires flag: --categoryExperimentalThirdParty=true)",
544
592
  category: 'Third-party',
545
593
  args: {},
546
594
  },