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.
- package/README.md +42 -5
- package/build/src/HeapSnapshotManager.js +60 -3
- package/build/src/McpContext.js +47 -5
- package/build/src/McpResponse.js +66 -7
- package/build/src/bin/check-latest-version.js +1 -0
- package/build/src/bin/chrome-devtools-cli-options.js +51 -3
- package/build/src/bin/chrome-devtools-mcp-cli-options.js +3 -3
- package/build/src/bin/chrome-devtools.js +25 -1
- package/build/src/daemon/daemon.js +1 -1
- package/build/src/formatters/HeapSnapshotFormatter.js +43 -0
- package/build/src/formatters/NetworkFormatter.js +3 -1
- package/build/src/index.js +2 -2
- package/build/src/issue-descriptions.js +6 -4
- package/build/src/third_party/THIRD_PARTY_NOTICES +3 -33
- package/build/src/third_party/bundled-packages.json +3 -4
- package/build/src/third_party/devtools-heap-snapshot-worker.js +94 -0
- package/build/src/third_party/index.js +2396 -896
- package/build/src/tools/memory.js +60 -4
- package/build/src/tools/screencast.js +1 -2
- package/build/src/tools/script.js +3 -10
- package/build/src/tools/thirdPartyDeveloper.js +6 -6
- package/build/src/utils/check-for-updates.js +1 -0
- package/build/src/utils/files.js +0 -4
- package/build/src/version.js +1 -1
- package/package.json +15 -6
- package/skills/a11y-debugging/SKILL.md +89 -0
- package/skills/a11y-debugging/references/a11y-snippets.md +92 -0
- package/skills/chrome-devtools/SKILL.md +72 -0
- package/skills/chrome-devtools-cli/SKILL.md +153 -0
- package/skills/chrome-devtools-cli/references/installation.md +14 -0
- package/skills/debug-optimize-lcp/SKILL.md +121 -0
- package/skills/debug-optimize-lcp/references/elements-and-size.md +27 -0
- package/skills/debug-optimize-lcp/references/lcp-breakdown.md +23 -0
- package/skills/debug-optimize-lcp/references/lcp-snippets.md +79 -0
- package/skills/debug-optimize-lcp/references/optimization-strategies.md +38 -0
- package/skills/memory-leak-debugging/SKILL.md +50 -0
- package/skills/memory-leak-debugging/references/common-leaks.md +33 -0
- package/skills/memory-leak-debugging/references/compare_snapshots.js +109 -0
- package/skills/memory-leak-debugging/references/memlab.md +29 -0
- 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** (
|
|
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
|
|
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(
|
|
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
|
}
|
package/build/src/McpContext.js
CHANGED
|
@@ -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 {
|
|
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.#
|
|
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.
|
|
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
|
-
|
|
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
|
package/build/src/McpResponse.js
CHANGED
|
@@ -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,
|
|
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 {
|
|
@@ -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
|
|
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: `() =>
|
|
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.\
|
|
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
|
},
|