brave-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/brave-devtools-cli-options.js +51 -3
- package/build/src/bin/brave-devtools-mcp-cli-options.js +3 -3
- package/build/src/bin/brave-devtools.js +25 -1
- package/build/src/bin/check-latest-version.js +1 -0
- 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-formatter-worker.js +1 -0
- package/build/src/third_party/devtools-heap-snapshot-worker.js +95 -0
- package/build/src/third_party/index.js +2397 -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
|
@@ -239,9 +239,9 @@ To use the Chrome DevTools MCP server follow the instructions from <a href="http
|
|
|
239
239
|
"chrome-devtools": {
|
|
240
240
|
"command": "npx",
|
|
241
241
|
"args": [
|
|
242
|
+
"-y",
|
|
242
243
|
"chrome-devtools-mcp@latest",
|
|
243
|
-
"--browser-url=http://127.0.0.1:9222"
|
|
244
|
-
"-y"
|
|
244
|
+
"--browser-url=http://127.0.0.1:9222"
|
|
245
245
|
]
|
|
246
246
|
}
|
|
247
247
|
}
|
|
@@ -632,12 +632,14 @@ If you run into any issues, checkout our [troubleshooting guide](./docs/troubles
|
|
|
632
632
|
- [`take_snapshot`](docs/tool-reference.md#take_snapshot)
|
|
633
633
|
- [`screencast_start`](docs/tool-reference.md#screencast_start)
|
|
634
634
|
- [`screencast_stop`](docs/tool-reference.md#screencast_stop)
|
|
635
|
-
- **Memory** (
|
|
635
|
+
- **Memory** (11 tools)
|
|
636
636
|
- [`take_heapsnapshot`](docs/tool-reference.md#take_heapsnapshot)
|
|
637
637
|
- [`close_heapsnapshot`](docs/tool-reference.md#close_heapsnapshot)
|
|
638
|
+
- [`compare_heapsnapshots`](docs/tool-reference.md#compare_heapsnapshots)
|
|
638
639
|
- [`get_heapsnapshot_class_nodes`](docs/tool-reference.md#get_heapsnapshot_class_nodes)
|
|
639
640
|
- [`get_heapsnapshot_details`](docs/tool-reference.md#get_heapsnapshot_details)
|
|
640
641
|
- [`get_heapsnapshot_dominators`](docs/tool-reference.md#get_heapsnapshot_dominators)
|
|
642
|
+
- [`get_heapsnapshot_duplicate_strings`](docs/tool-reference.md#get_heapsnapshot_duplicate_strings)
|
|
641
643
|
- [`get_heapsnapshot_edges`](docs/tool-reference.md#get_heapsnapshot_edges)
|
|
642
644
|
- [`get_heapsnapshot_retainers`](docs/tool-reference.md#get_heapsnapshot_retainers)
|
|
643
645
|
- [`get_heapsnapshot_retaining_paths`](docs/tool-reference.md#get_heapsnapshot_retaining_paths)
|
|
@@ -671,14 +673,17 @@ The Brave DevTools MCP server supports the following configuration options:
|
|
|
671
673
|
- **`--browserUrl`/ `--browser-url`, `-u`**
|
|
672
674
|
Connect to a running, debuggable Brave instance (e.g. `http://127.0.0.1:9222`).
|
|
673
675
|
- **Type:** string
|
|
676
|
+
- **Default:** `false`
|
|
674
677
|
|
|
675
678
|
- **`--wsEndpoint`/ `--ws-endpoint`, `-w`**
|
|
676
679
|
WebSocket endpoint to connect to a running Brave instance (e.g., ws://127.0.0.1:9222/devtools/browser/<id>). Alternative to --browserUrl.
|
|
677
680
|
- **Type:** string
|
|
681
|
+
- **Default:** `false`
|
|
678
682
|
|
|
679
683
|
- **`--wsHeaders`/ `--ws-headers`**
|
|
680
684
|
Custom headers for WebSocket connection in JSON format (e.g., '{"Authorization":"Bearer token"}'). Only works with --wsEndpoint.
|
|
681
685
|
- **Type:** string
|
|
686
|
+
- **Default:** `false`
|
|
682
687
|
|
|
683
688
|
- **`--headless`**
|
|
684
689
|
Whether to run in headless (no UI) mode.
|
|
@@ -688,87 +693,108 @@ The Brave DevTools MCP server supports the following configuration options:
|
|
|
688
693
|
- **`--executablePath`/ `--executable-path`, `-e`**
|
|
689
694
|
Path to custom Brave executable. Can also be set via BRAVE_PATH environment variable.
|
|
690
695
|
- **Type:** string
|
|
696
|
+
- **Default:** `false`
|
|
691
697
|
|
|
692
698
|
- **`--isolated`**
|
|
693
699
|
If specified, creates a temporary user-data-dir that is automatically cleaned up after the browser is closed. Defaults to false.
|
|
694
700
|
- **Type:** boolean
|
|
701
|
+
- **Default:** `false`
|
|
695
702
|
|
|
696
703
|
- **`--userDataDir`/ `--user-data-dir`**
|
|
697
704
|
Path to the user data directory for Brave. Default is $HOME/.cache/brave-devtools-mcp/brave-profile$CHANNEL_SUFFIX_IF_NON_RELEASE
|
|
698
705
|
- **Type:** string
|
|
706
|
+
- **Default:** `false`
|
|
699
707
|
|
|
700
708
|
- **`--channel`**
|
|
701
709
|
Specify a different Brave channel that should be used. The default is the release channel.
|
|
702
710
|
- **Type:** string
|
|
703
711
|
- **Choices:** `release`, `beta`, `nightly`, `dev`
|
|
712
|
+
- **Default:** `false`
|
|
704
713
|
|
|
705
714
|
- **`--logFile`/ `--log-file`**
|
|
706
715
|
Path to a file to write debug logs to. Set the env variable `DEBUG` to `*` to enable verbose logs. Useful for submitting bug reports.
|
|
707
716
|
- **Type:** string
|
|
717
|
+
- **Default:** `false`
|
|
708
718
|
|
|
709
719
|
- **`--viewport`**
|
|
710
720
|
Initial viewport size for the Brave instances started by the server. For example, `1280x720`. In headless mode, max size is 3840x2160px.
|
|
711
721
|
- **Type:** string
|
|
722
|
+
- **Default:** `false`
|
|
712
723
|
|
|
713
724
|
- **`--proxyServer`/ `--proxy-server`**
|
|
714
725
|
Proxy server configuration for Brave passed as --proxy-server when launching the browser. See https://www.chromium.org/developers/design-documents/network-settings/ for details.
|
|
715
726
|
- **Type:** string
|
|
727
|
+
- **Default:** `false`
|
|
716
728
|
|
|
717
729
|
- **`--acceptInsecureCerts`/ `--accept-insecure-certs`**
|
|
718
730
|
If enabled, ignores errors relative to self-signed and expired certificates. Use with caution.
|
|
719
731
|
- **Type:** boolean
|
|
732
|
+
- **Default:** `false`
|
|
720
733
|
|
|
721
734
|
- **`--experimentalPageIdRouting`/ `--experimental-page-id-routing`**
|
|
722
735
|
Whether to expose pageId on page-scoped tools and route requests by page ID (useful for concurrent agent sessions).
|
|
723
736
|
- **Type:** boolean
|
|
737
|
+
- **Default:** `false`
|
|
724
738
|
|
|
725
739
|
- **`--experimentalDevtools`/ `--experimental-devtools`**
|
|
726
740
|
Whether to enable automation over DevTools targets
|
|
727
741
|
- **Type:** boolean
|
|
742
|
+
- **Default:** `false`
|
|
728
743
|
|
|
729
744
|
- **`--experimentalVision`/ `--experimental-vision`**
|
|
730
745
|
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.
|
|
731
746
|
- **Type:** boolean
|
|
747
|
+
- **Default:** `false`
|
|
732
748
|
|
|
733
749
|
- **`--memoryDebugging`/ `--memory-debugging`, `-experimentalMemory`**
|
|
734
750
|
Whether to enable memory debugging tools.
|
|
735
751
|
- **Type:** boolean
|
|
752
|
+
- **Default:** `false`
|
|
736
753
|
|
|
737
754
|
- **`--experimentalStructuredContent`/ `--experimental-structured-content`**
|
|
738
755
|
Whether to output structured formatted content.
|
|
739
756
|
- **Type:** boolean
|
|
757
|
+
- **Default:** `false`
|
|
740
758
|
|
|
741
759
|
- **`--experimentalIncludeAllPages`/ `--experimental-include-all-pages`**
|
|
742
760
|
Whether to include all kinds of pages such as webviews or background pages as pages.
|
|
743
761
|
- **Type:** boolean
|
|
762
|
+
- **Default:** `false`
|
|
744
763
|
|
|
745
764
|
- **`--experimentalScreencast`/ `--experimental-screencast`**
|
|
746
765
|
Exposes experimental screencast tools (requires ffmpeg). Install ffmpeg https://www.ffmpeg.org/download.html and ensure it is available in the MCP server PATH.
|
|
747
766
|
- **Type:** boolean
|
|
767
|
+
- **Default:** `false`
|
|
748
768
|
|
|
749
769
|
- **`--experimentalFfmpegPath`/ `--experimental-ffmpeg-path`**
|
|
750
770
|
Path to ffmpeg executable for screencast recording.
|
|
751
771
|
- **Type:** string
|
|
772
|
+
- **Default:** `false`
|
|
752
773
|
|
|
753
774
|
- **`--categoryExperimentalWebmcp`/ `--category-experimental-webmcp`**
|
|
754
775
|
Set to true to enable debugging WebMCP tools. Requires Brave with the following flags: `--enable-features=WebMCP,DevToolsWebMCPSupport`
|
|
755
776
|
- **Type:** boolean
|
|
777
|
+
- **Default:** `false`
|
|
756
778
|
|
|
757
779
|
- **`--braveArg`/ `--brave-arg`**
|
|
758
780
|
Additional arguments for Brave. Only applies when Brave is launched by brave-devtools-mcp.
|
|
759
781
|
- **Type:** array
|
|
782
|
+
- **Default:** `false`
|
|
760
783
|
|
|
761
784
|
- **`--blockedUrlPattern`/ `--blocked-url-pattern`**
|
|
762
|
-
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.
|
|
785
|
+
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.
|
|
763
786
|
- **Type:** array
|
|
787
|
+
- **Default:** `false`
|
|
764
788
|
|
|
765
789
|
- **`--allowedUrlPattern`/ `--allowed-url-pattern`**
|
|
766
|
-
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.
|
|
790
|
+
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.
|
|
767
791
|
- **Type:** array
|
|
792
|
+
- **Default:** `false`
|
|
768
793
|
|
|
769
794
|
- **`--ignoreDefaultBraveArg`/ `--ignore-default-brave-arg`**
|
|
770
795
|
Explicitly disable default arguments for Brave. Only applies when Brave is launched by brave-devtools-mcp.
|
|
771
796
|
- **Type:** array
|
|
797
|
+
- **Default:** `false`
|
|
772
798
|
|
|
773
799
|
- **`--categoryEmulation`/ `--category-emulation`**
|
|
774
800
|
Set to false to exclude tools related to emulation.
|
|
@@ -809,22 +835,27 @@ The Brave DevTools MCP server supports the following configuration options:
|
|
|
809
835
|
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").
|
|
810
836
|
- **Type:** string
|
|
811
837
|
- **Choices:** `jpeg`, `png`, `webp`
|
|
838
|
+
- **Default:** `false`
|
|
812
839
|
|
|
813
840
|
- **`--screenshotQuality`/ `--screenshot-quality`**
|
|
814
841
|
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.
|
|
815
842
|
- **Type:** number
|
|
843
|
+
- **Default:** `false`
|
|
816
844
|
|
|
817
845
|
- **`--screenshotMaxWidth`/ `--screenshot-max-width`**
|
|
818
846
|
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.
|
|
819
847
|
- **Type:** number
|
|
848
|
+
- **Default:** `false`
|
|
820
849
|
|
|
821
850
|
- **`--screenshotMaxHeight`/ `--screenshot-max-height`**
|
|
822
851
|
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.
|
|
823
852
|
- **Type:** number
|
|
853
|
+
- **Default:** `false`
|
|
824
854
|
|
|
825
855
|
- **`--slim`**
|
|
826
856
|
Exposes a "slim" set of 3 tools covering navigation, script execution and screenshots only. Useful for basic browser tasks.
|
|
827
857
|
- **Type:** boolean
|
|
858
|
+
- **Default:** `false`
|
|
828
859
|
|
|
829
860
|
- **`--redactNetworkHeaders`/ `--redact-network-headers`**
|
|
830
861
|
If true, redacts some of the network headers considered sensitive before returning to the client.
|
|
@@ -930,3 +961,9 @@ Please consult [these instructions](./docs/debugging-android.md) (originally wri
|
|
|
930
961
|
## Known limitations
|
|
931
962
|
|
|
932
963
|
See [Troubleshooting](./docs/troubleshooting.md).
|
|
964
|
+
|
|
965
|
+
## Integrating as a browser subagent
|
|
966
|
+
|
|
967
|
+
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.
|
|
968
|
+
|
|
969
|
+
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
|
},
|
|
@@ -157,7 +157,7 @@ export const cliOptions = {
|
|
|
157
157
|
},
|
|
158
158
|
experimentalToonFormat: {
|
|
159
159
|
type: 'boolean',
|
|
160
|
-
describe: 'Whether to format structured data in text response using Token-Oriented Object Notation.
|
|
160
|
+
describe: 'Whether to format structured data in text response using Token-Oriented Object Notation (requires @toon-format/toon). If running via npx, use: npx --package chrome-devtools-mcp@latest --package @toon-format/toon@latest chrome-devtools-mcp --experimentalToonFormat',
|
|
161
161
|
hidden: true,
|
|
162
162
|
},
|
|
163
163
|
experimentalIncludeAllPages: {
|
|
@@ -193,12 +193,12 @@ export const cliOptions = {
|
|
|
193
193
|
},
|
|
194
194
|
blockedUrlPattern: {
|
|
195
195
|
type: 'array',
|
|
196
|
-
describe:
|
|
196
|
+
describe: "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.",
|
|
197
197
|
conflicts: ['allowedUrlPattern'],
|
|
198
198
|
},
|
|
199
199
|
allowedUrlPattern: {
|
|
200
200
|
type: 'array',
|
|
201
|
-
describe:
|
|
201
|
+
describe: "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.",
|
|
202
202
|
conflicts: ['blockedUrlPattern'],
|
|
203
203
|
},
|
|
204
204
|
ignoreDefaultBraveArg: {
|