brave-mcp 0.5.0 → 1.2.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 +68 -9
- package/build/src/HeapSnapshotManager.js +55 -18
- package/build/src/McpContext.js +94 -32
- package/build/src/McpPage.js +9 -9
- package/build/src/McpResponse.js +167 -63
- package/build/src/PageCollector.js +8 -8
- package/build/src/ServiceWorkerCollector.js +171 -0
- package/build/src/TextSnapshot.js +1 -1
- package/build/src/ToolHandler.js +223 -0
- package/build/src/WaitForHelper.js +20 -6
- package/build/src/bin/brave-devtools-cli-options.js +136 -40
- package/build/src/bin/brave-devtools-mcp-cli-options.js +22 -11
- package/build/src/bin/brave-devtools-mcp-main.js +45 -6
- package/build/src/bin/brave-devtools.js +0 -2
- package/build/src/bin/check-latest-version.js +25 -1
- package/build/src/browser.js +45 -4
- package/build/src/daemon/client.js +19 -13
- package/build/src/daemon/daemon.js +74 -17
- package/build/src/daemon/utils.js +2 -2
- package/build/src/{DevToolsConnectionAdapter.js → devtools/DevToolsConnectionAdapter.js} +1 -1
- package/build/src/{DevtoolsUtils.js → devtools/DevtoolsUtils.js} +94 -19
- package/build/src/formatters/ConsoleFormatter.js +1 -1
- package/build/src/formatters/HeapSnapshotFormatter.js +44 -9
- package/build/src/formatters/IssueFormatter.js +4 -4
- package/build/src/index.js +24 -165
- package/build/src/telemetry/ClearcutLogger.js +35 -119
- package/build/src/telemetry/WatchdogClient.js +4 -4
- package/build/src/telemetry/errors.js +18 -0
- package/build/src/telemetry/flagUtils.js +4 -3
- package/build/src/telemetry/{toolMetricsUtils.js → metricsRegistry.js} +3 -3
- package/build/src/telemetry/persistence.js +21 -3
- package/build/src/telemetry/transformation.js +134 -0
- package/build/src/telemetry/types.js +0 -8
- package/build/src/telemetry/watchdog/ClearcutSender.js +10 -10
- package/build/src/telemetry/watchdog/main.js +5 -5
- package/build/src/third_party/THIRD_PARTY_NOTICES +3637 -0
- package/build/src/third_party/bundled-packages.json +12 -0
- package/build/src/third_party/devtools-formatter-worker.js +15300 -7
- package/build/src/third_party/devtools-heap-snapshot-worker.js +9817 -7
- package/build/src/third_party/index.js +159331 -27
- package/build/src/third_party/issue-descriptions/emailVerificationRequestAccountsEmptyList.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestAccountsHttpNotFound.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestAccountsInvalidContentType.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestAccountsInvalidResponse.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestAccountsNoResponse.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestDnsFetchFailed.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestDnsInvalidRecord.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestEmailVerificationWellKnownHttpNotFound.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestEmailVerificationWellKnownInvalidContentType.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestEmailVerificationWellKnownInvalidResponse.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestEmailVerificationWellKnownNoResponse.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestInvalidEmail.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestJwksHttpNotFound.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestJwksInvalidResponse.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestKeyBindingSigningFailed.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestRpOriginIsOpaque.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenHttpNotFound.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenInvalidContentType.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenInvalidResponse.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenInvalidSdJwt.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenMalformedSdJwt.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenNoResponse.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationKbInvalidAudience.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationKbInvalidIssuedAt.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationKbInvalidNonce.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationKbInvalidSdHash.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationKbInvalidTyp.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationKbMissingAud.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationKbMissingCnf.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationKbMissingIat.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationKbMissingNonce.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationKbMissingSdHash.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationKbSignatureFailed.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationSdJwtInvalidEmail.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationSdJwtInvalidEmailVerified.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationSdJwtInvalidHolderKey.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationSdJwtInvalidIssuedAt.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationSdJwtInvalidIssuer.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationSdJwtJwksMissingKeys.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationSdJwtMissingCnf.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationSdJwtMissingEmail.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationSdJwtMissingIat.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationSdJwtMissingIss.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationSdJwtSignatureFailed.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationSdJwtUnsupportedHeaderAlg.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestUserLoggedOut.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestWellKnownAccountsEndpointCrossOrigin.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestWellKnownHttpNotFound.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestWellKnownInvalidContentType.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestWellKnownInvalidResponse.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestWellKnownIssuanceEndpointCrossOrigin.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestWellKnownListEmpty.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestWellKnownMissingAccountsEndpoint.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestWellKnownMissingIssuanceEndpoint.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestWellKnownNoResponse.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestWellKnownUnsupportedSigningAlgorithm.md +1 -0
- package/build/src/third_party/issue-descriptions/genericBackUINavigationWouldSkipAd.md +4 -0
- package/build/src/third_party/lighthouse-devtools-mcp-bundle.js +4236 -4219
- package/build/src/tools/ToolDefinition.js +2 -2
- package/build/src/tools/console.js +7 -0
- package/build/src/tools/emulation.js +29 -2
- package/build/src/tools/extensions.js +5 -0
- package/build/src/tools/input.js +57 -19
- package/build/src/tools/lighthouse.js +18 -13
- package/build/src/tools/memory.js +97 -21
- package/build/src/tools/network.js +2 -2
- package/build/src/tools/pages.js +13 -5
- package/build/src/tools/performance.js +15 -11
- package/build/src/tools/screencast.js +2 -1
- package/build/src/tools/screenshot.js +1 -1
- package/build/src/tools/script.js +33 -11
- package/build/src/tools/slim/tools.js +3 -0
- package/build/src/tools/snapshot.js +3 -2
- package/build/src/tools/thirdPartyDeveloper.js +12 -2
- package/build/src/tools/webmcp.js +2 -0
- package/build/src/trace-processing/parse.js +7 -7
- package/build/src/utils/files.js +43 -0
- package/build/src/version.js +4 -1
- package/package.json +21 -17
- package/build/src/telemetry/metricUtils.js +0 -15
package/README.md
CHANGED
|
@@ -38,7 +38,7 @@ You can explicitly enable them with `--usage-statistics` if desired.
|
|
|
38
38
|
|
|
39
39
|
## Requirements
|
|
40
40
|
|
|
41
|
-
- [Node.js](https://nodejs.org/)
|
|
41
|
+
- [Node.js](https://nodejs.org/) [LTS](https://github.com/nodejs/Release#release-schedule) version.
|
|
42
42
|
- [Brave Browser](https://brave.com/) current release version or newer.
|
|
43
43
|
- [npm](https://www.npmjs.com/)
|
|
44
44
|
|
|
@@ -279,7 +279,7 @@ To install Chrome DevTools MCP with skills, add the marketplace registry in Clau
|
|
|
279
279
|
Then, install the plugin:
|
|
280
280
|
|
|
281
281
|
```sh
|
|
282
|
-
/plugin install chrome-devtools-mcp
|
|
282
|
+
/plugin install chrome-devtools-mcp@chrome-devtools-plugins
|
|
283
283
|
```
|
|
284
284
|
|
|
285
285
|
Restart Claude Code to have the MCP server and skills load (check with `/skills`).
|
|
@@ -367,7 +367,7 @@ and the expert guidance it needs to use them effectively.
|
|
|
367
367
|
|
|
368
368
|
1. Open the **Command Palette** (`Cmd+Shift+P` on macOS or `Ctrl+Shift+P` on Windows/Linux).
|
|
369
369
|
2. Search for and run the **Chat: Install Plugin From Source** command.
|
|
370
|
-
3. Paste in our repository
|
|
370
|
+
3. Paste in our repository name: `ChromeDevTools/chrome-devtools-mcp`.
|
|
371
371
|
|
|
372
372
|
That's it! Your agent is now supercharged with Chrome DevTools capabilities.
|
|
373
373
|
|
|
@@ -632,11 +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** (
|
|
636
|
-
- [`
|
|
637
|
-
- [`
|
|
638
|
-
- [`
|
|
639
|
-
- [`
|
|
635
|
+
- **Memory** (7 tools)
|
|
636
|
+
- [`take_heapsnapshot`](docs/tool-reference.md#take_heapsnapshot)
|
|
637
|
+
- [`close_heapsnapshot`](docs/tool-reference.md#close_heapsnapshot)
|
|
638
|
+
- [`get_heapsnapshot_class_nodes`](docs/tool-reference.md#get_heapsnapshot_class_nodes)
|
|
639
|
+
- [`get_heapsnapshot_details`](docs/tool-reference.md#get_heapsnapshot_details)
|
|
640
|
+
- [`get_heapsnapshot_retainers`](docs/tool-reference.md#get_heapsnapshot_retainers)
|
|
641
|
+
- [`get_heapsnapshot_retaining_paths`](docs/tool-reference.md#get_heapsnapshot_retaining_paths)
|
|
642
|
+
- [`get_heapsnapshot_summary`](docs/tool-reference.md#get_heapsnapshot_summary)
|
|
640
643
|
- **Extensions** (5 tools)
|
|
641
644
|
- [`install_extension`](docs/tool-reference.md#install_extension)
|
|
642
645
|
- [`list_extensions`](docs/tool-reference.md#list_extensions)
|
|
@@ -713,10 +716,30 @@ The Brave DevTools MCP server supports the following configuration options:
|
|
|
713
716
|
If enabled, ignores errors relative to self-signed and expired certificates. Use with caution.
|
|
714
717
|
- **Type:** boolean
|
|
715
718
|
|
|
719
|
+
- **`--experimentalPageIdRouting`/ `--experimental-page-id-routing`**
|
|
720
|
+
Whether to expose pageId on page-scoped tools and route requests by page ID (useful for concurrent agent sessions).
|
|
721
|
+
- **Type:** boolean
|
|
722
|
+
|
|
723
|
+
- **`--experimentalDevtools`/ `--experimental-devtools`**
|
|
724
|
+
Whether to enable automation over DevTools targets
|
|
725
|
+
- **Type:** boolean
|
|
726
|
+
|
|
716
727
|
- **`--experimentalVision`/ `--experimental-vision`**
|
|
717
728
|
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.
|
|
718
729
|
- **Type:** boolean
|
|
719
730
|
|
|
731
|
+
- **`--memoryDebugging`/ `--memory-debugging`, `-experimentalMemory`**
|
|
732
|
+
Whether to enable memory debugging tools.
|
|
733
|
+
- **Type:** boolean
|
|
734
|
+
|
|
735
|
+
- **`--experimentalStructuredContent`/ `--experimental-structured-content`**
|
|
736
|
+
Whether to output structured formatted content.
|
|
737
|
+
- **Type:** boolean
|
|
738
|
+
|
|
739
|
+
- **`--experimentalIncludeAllPages`/ `--experimental-include-all-pages`**
|
|
740
|
+
Whether to include all kinds of pages such as webviews or background pages as pages.
|
|
741
|
+
- **Type:** boolean
|
|
742
|
+
|
|
720
743
|
- **`--experimentalScreencast`/ `--experimental-screencast`**
|
|
721
744
|
Exposes experimental screencast tools (requires ffmpeg). Install ffmpeg https://www.ffmpeg.org/download.html and ensure it is available in the MCP server PATH.
|
|
722
745
|
- **Type:** boolean
|
|
@@ -733,6 +756,14 @@ The Brave DevTools MCP server supports the following configuration options:
|
|
|
733
756
|
Additional arguments for Brave. Only applies when Brave is launched by brave-devtools-mcp.
|
|
734
757
|
- **Type:** array
|
|
735
758
|
|
|
759
|
+
- **`--blockedUrlPattern`/ `--blocked-url-pattern`**
|
|
760
|
+
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.
|
|
761
|
+
- **Type:** array
|
|
762
|
+
|
|
763
|
+
- **`--allowedUrlPattern`/ `--allowed-url-pattern`**
|
|
764
|
+
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.
|
|
765
|
+
- **Type:** array
|
|
766
|
+
|
|
736
767
|
- **`--ignoreDefaultBraveArg`/ `--ignore-default-brave-arg`**
|
|
737
768
|
Explicitly disable default arguments for Brave. Only applies when Brave is launched by brave-devtools-mcp.
|
|
738
769
|
- **Type:** array
|
|
@@ -777,7 +808,7 @@ The Brave DevTools MCP server supports the following configuration options:
|
|
|
777
808
|
- **Type:** boolean
|
|
778
809
|
|
|
779
810
|
- **`--redactNetworkHeaders`/ `--redact-network-headers`**
|
|
780
|
-
If true, redacts some of the network headers considered
|
|
811
|
+
If true, redacts some of the network headers considered sensitive before returning to the client.
|
|
781
812
|
- **Type:** boolean
|
|
782
813
|
- **Default:** `false`
|
|
783
814
|
|
|
@@ -826,6 +857,34 @@ You can also run `npx chrome-devtools-mcp@latest --help` to see all available co
|
|
|
826
857
|
|
|
827
858
|
## Concepts
|
|
828
859
|
|
|
860
|
+
### Concurrent sessions
|
|
861
|
+
|
|
862
|
+
Most MCP clients start one Chrome DevTools MCP server per conversation. If your
|
|
863
|
+
client shares a single server instance across concurrent agents or subagents,
|
|
864
|
+
start the server with `--experimentalPageIdRouting`. This exposes `pageId` on
|
|
865
|
+
page-scoped tools so each agent can route tool calls to the tab it is working
|
|
866
|
+
with.
|
|
867
|
+
|
|
868
|
+
```json
|
|
869
|
+
{
|
|
870
|
+
"mcpServers": {
|
|
871
|
+
"chrome-devtools": {
|
|
872
|
+
"command": "npx",
|
|
873
|
+
"args": [
|
|
874
|
+
"-y",
|
|
875
|
+
"chrome-devtools-mcp@latest",
|
|
876
|
+
"--experimentalPageIdRouting"
|
|
877
|
+
]
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
```
|
|
882
|
+
|
|
883
|
+
If you run multiple independent MCP client sessions and want each session to
|
|
884
|
+
launch its own temporary Chrome profile, also pass `--isolated`. This avoids
|
|
885
|
+
sharing the default Chrome DevTools MCP user data directory between those
|
|
886
|
+
server instances.
|
|
887
|
+
|
|
829
888
|
### User data directory
|
|
830
889
|
|
|
831
890
|
`brave-devtools-mcp` starts a Brave release channel instance using the following user
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import fsSync from 'node:fs';
|
|
7
7
|
import path from 'node:path';
|
|
8
|
+
import { isNodeLike } from './formatters/HeapSnapshotFormatter.js';
|
|
8
9
|
import { DevTools } from './third_party/index.js';
|
|
9
10
|
import { createIdGenerator, stableIdSymbol, } from './utils/id.js';
|
|
10
11
|
export class HeapSnapshotManager {
|
|
@@ -19,8 +20,8 @@ export class HeapSnapshotManager {
|
|
|
19
20
|
this.#snapshots.set(absolutePath, {
|
|
20
21
|
snapshot,
|
|
21
22
|
worker,
|
|
22
|
-
|
|
23
|
-
|
|
23
|
+
idToClassKey: new Map(),
|
|
24
|
+
classKeyToId: new Map(),
|
|
24
25
|
idGenerator: createIdGenerator(),
|
|
25
26
|
});
|
|
26
27
|
return snapshot;
|
|
@@ -30,10 +31,10 @@ export class HeapSnapshotManager {
|
|
|
30
31
|
const filter = new DevTools.HeapSnapshotModel.HeapSnapshotModel.NodeFilter();
|
|
31
32
|
const aggregates = await snapshot.aggregatesWithFilter(filter);
|
|
32
33
|
for (const key of Object.keys(aggregates)) {
|
|
33
|
-
const
|
|
34
|
+
const id = await this.getOrCreateIdForClassKey(filePath, key);
|
|
34
35
|
const aggregate = aggregates[key];
|
|
35
36
|
if (aggregate) {
|
|
36
|
-
aggregate[stableIdSymbol] =
|
|
37
|
+
aggregate[stableIdSymbol] = id;
|
|
37
38
|
}
|
|
38
39
|
}
|
|
39
40
|
return aggregates;
|
|
@@ -46,26 +47,57 @@ export class HeapSnapshotManager {
|
|
|
46
47
|
const snapshot = await this.getSnapshot(filePath);
|
|
47
48
|
return snapshot.staticData;
|
|
48
49
|
}
|
|
49
|
-
async
|
|
50
|
+
async getOrCreateIdForClassKey(filePath, classKey) {
|
|
50
51
|
const cached = this.#getCachedSnapshot(filePath);
|
|
51
|
-
let
|
|
52
|
-
if (!
|
|
53
|
-
|
|
54
|
-
cached.
|
|
55
|
-
cached.
|
|
52
|
+
let id = cached.classKeyToId.get(classKey);
|
|
53
|
+
if (!id) {
|
|
54
|
+
id = cached.idGenerator();
|
|
55
|
+
cached.classKeyToId.set(classKey, id);
|
|
56
|
+
cached.idToClassKey.set(id, classKey);
|
|
56
57
|
}
|
|
57
|
-
return
|
|
58
|
+
return id;
|
|
58
59
|
}
|
|
59
|
-
async
|
|
60
|
+
async getNodesById(filePath, id) {
|
|
60
61
|
const snapshot = await this.getSnapshot(filePath);
|
|
61
62
|
const filter = new DevTools.HeapSnapshotModel.HeapSnapshotModel.NodeFilter();
|
|
62
|
-
const className = await this.
|
|
63
|
+
const className = await this.resolveClassKeyFromId(filePath, id);
|
|
63
64
|
if (!className) {
|
|
64
|
-
throw new Error(`Class with
|
|
65
|
+
throw new Error(`Class with ID ${id} not found in heap snapshot`);
|
|
65
66
|
}
|
|
66
67
|
const provider = snapshot.createNodesProviderForClass(className, filter);
|
|
67
|
-
|
|
68
|
-
|
|
68
|
+
return await provider.serializeItemsRange(0, Infinity);
|
|
69
|
+
}
|
|
70
|
+
async findNodeIndexById(filePath, nodeId) {
|
|
71
|
+
const snapshot = await this.getSnapshot(filePath);
|
|
72
|
+
const aggregates = await this.getAggregates(filePath);
|
|
73
|
+
const filter = new DevTools.HeapSnapshotModel.HeapSnapshotModel.NodeFilter();
|
|
74
|
+
for (const classKey of Object.keys(aggregates)) {
|
|
75
|
+
const provider = snapshot.createNodesProviderForClass(classKey, filter);
|
|
76
|
+
const range = await provider.serializeItemsRange(0, Infinity);
|
|
77
|
+
for (const item of range.items) {
|
|
78
|
+
if (isNodeLike(item) && item.id === nodeId) {
|
|
79
|
+
return item.nodeIndex;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return undefined;
|
|
84
|
+
}
|
|
85
|
+
async getRetainers(filePath, nodeId) {
|
|
86
|
+
const nodeIndex = await this.findNodeIndexById(filePath, nodeId);
|
|
87
|
+
if (nodeIndex === undefined) {
|
|
88
|
+
throw new Error(`Node with ID ${nodeId} not found`);
|
|
89
|
+
}
|
|
90
|
+
const snapshot = await this.getSnapshot(filePath);
|
|
91
|
+
const provider = snapshot.createRetainingEdgesProvider(nodeIndex);
|
|
92
|
+
return await provider.serializeItemsRange(0, Infinity);
|
|
93
|
+
}
|
|
94
|
+
async getRetainingPaths(filePath, nodeId, maxDepth, maxNodes, maxSiblings) {
|
|
95
|
+
const nodeIndex = await this.findNodeIndexById(filePath, nodeId);
|
|
96
|
+
if (nodeIndex === undefined) {
|
|
97
|
+
throw new Error(`Node with ID ${nodeId} not found`);
|
|
98
|
+
}
|
|
99
|
+
const snapshot = await this.getSnapshot(filePath);
|
|
100
|
+
return await snapshot.getRetainingPaths(nodeIndex, maxDepth, maxNodes, maxSiblings);
|
|
69
101
|
}
|
|
70
102
|
#getCachedSnapshot(filePath) {
|
|
71
103
|
const absolutePath = path.resolve(filePath);
|
|
@@ -75,9 +107,9 @@ export class HeapSnapshotManager {
|
|
|
75
107
|
}
|
|
76
108
|
return cached;
|
|
77
109
|
}
|
|
78
|
-
async
|
|
110
|
+
async resolveClassKeyFromId(filePath, id) {
|
|
79
111
|
const cached = this.#getCachedSnapshot(filePath);
|
|
80
|
-
return cached.
|
|
112
|
+
return cached.idToClassKey.get(id);
|
|
81
113
|
}
|
|
82
114
|
async #loadSnapshot(absolutePath) {
|
|
83
115
|
const workerProxy = new DevTools.HeapSnapshotModel.HeapSnapshotProxy.HeapSnapshotWorkerProxy(() => {
|
|
@@ -98,13 +130,18 @@ export class HeapSnapshotManager {
|
|
|
98
130
|
const snapshot = await snapshotPromise;
|
|
99
131
|
return { snapshot, worker: workerProxy };
|
|
100
132
|
}
|
|
133
|
+
hasSnapshots() {
|
|
134
|
+
return this.#snapshots.size > 0;
|
|
135
|
+
}
|
|
101
136
|
dispose(filePath) {
|
|
102
137
|
const absolutePath = path.resolve(filePath);
|
|
103
138
|
const cached = this.#snapshots.get(absolutePath);
|
|
104
139
|
if (cached) {
|
|
105
140
|
cached.worker.dispose();
|
|
106
141
|
this.#snapshots.delete(absolutePath);
|
|
142
|
+
return true;
|
|
107
143
|
}
|
|
144
|
+
return false;
|
|
108
145
|
}
|
|
109
146
|
}
|
|
110
147
|
//# sourceMappingURL=HeapSnapshotManager.js.map
|
package/build/src/McpContext.js
CHANGED
|
@@ -4,17 +4,19 @@
|
|
|
4
4
|
* SPDX-License-Identifier: Apache-2.0
|
|
5
5
|
*/
|
|
6
6
|
import fs from 'node:fs/promises';
|
|
7
|
+
import fsPromises from 'node:fs/promises';
|
|
7
8
|
import os from 'node:os';
|
|
8
9
|
import path from 'node:path';
|
|
9
10
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
10
|
-
import { UniverseManager } from './DevtoolsUtils.js';
|
|
11
|
+
import { overrideDevToolsGlobals, UniverseManager, } from './devtools/DevtoolsUtils.js';
|
|
11
12
|
import { HeapSnapshotManager } from './HeapSnapshotManager.js';
|
|
12
13
|
import { McpPage } from './McpPage.js';
|
|
13
14
|
import { NetworkCollector, ConsoleCollector, } from './PageCollector.js';
|
|
15
|
+
import { ServiceWorkerConsoleCollector } from './ServiceWorkerCollector.js';
|
|
14
16
|
import { Locator, PredefinedNetworkConditions, } from './third_party/index.js';
|
|
15
17
|
import { listPages } from './tools/pages.js';
|
|
16
18
|
import { CLOSE_PAGE_ERROR } from './tools/ToolDefinition.js';
|
|
17
|
-
import { ensureExtension, getTempFilePath } from './utils/files.js';
|
|
19
|
+
import { ensureExtension, getTempFilePath, resolveCanonicalPath, } from './utils/files.js';
|
|
18
20
|
import { getNetworkMultiplierFromString } from './WaitForHelper.js';
|
|
19
21
|
const DEFAULT_TIMEOUT = 5_000;
|
|
20
22
|
const NAVIGATION_TIMEOUT = 10_000;
|
|
@@ -32,6 +34,7 @@ export class McpContext {
|
|
|
32
34
|
#networkCollector;
|
|
33
35
|
#consoleCollector;
|
|
34
36
|
#devtoolsUniverseManager;
|
|
37
|
+
#serviceWorkerConsoleCollector;
|
|
35
38
|
#isRunningTrace = false;
|
|
36
39
|
#screenRecorderData = null;
|
|
37
40
|
#nextPageId = 1;
|
|
@@ -44,6 +47,7 @@ export class McpContext {
|
|
|
44
47
|
#heapSnapshotManager = new HeapSnapshotManager();
|
|
45
48
|
#roots = undefined;
|
|
46
49
|
constructor(browser, logger, options, locatorClass) {
|
|
50
|
+
overrideDevToolsGlobals();
|
|
47
51
|
this.browser = browser;
|
|
48
52
|
this.logger = logger;
|
|
49
53
|
this.#locatorClass = locatorClass;
|
|
@@ -62,19 +66,22 @@ export class McpContext {
|
|
|
62
66
|
},
|
|
63
67
|
};
|
|
64
68
|
});
|
|
69
|
+
this.#serviceWorkerConsoleCollector = new ServiceWorkerConsoleCollector(this.browser);
|
|
65
70
|
this.#devtoolsUniverseManager = new UniverseManager(this.browser);
|
|
66
71
|
}
|
|
67
72
|
async #init() {
|
|
68
73
|
const pages = await this.createPagesSnapshot();
|
|
69
|
-
await this.createExtensionServiceWorkersSnapshot();
|
|
74
|
+
const workers = await this.createExtensionServiceWorkersSnapshot();
|
|
70
75
|
await this.#networkCollector.init(pages);
|
|
71
76
|
await this.#consoleCollector.init(pages);
|
|
72
77
|
await this.#devtoolsUniverseManager.init(pages);
|
|
78
|
+
await this.#serviceWorkerConsoleCollector.init(workers);
|
|
73
79
|
}
|
|
74
80
|
dispose() {
|
|
75
81
|
this.#networkCollector.dispose();
|
|
76
82
|
this.#consoleCollector.dispose();
|
|
77
83
|
this.#devtoolsUniverseManager.dispose();
|
|
84
|
+
this.#serviceWorkerConsoleCollector.dispose();
|
|
78
85
|
for (const mcpPage of this.#mcpPages.values()) {
|
|
79
86
|
mcpPage.dispose();
|
|
80
87
|
}
|
|
@@ -106,7 +113,7 @@ export class McpContext {
|
|
|
106
113
|
setRoots(roots) {
|
|
107
114
|
this.#roots = roots;
|
|
108
115
|
}
|
|
109
|
-
validatePath(filePath) {
|
|
116
|
+
async validatePath(filePath) {
|
|
110
117
|
if (filePath === undefined) {
|
|
111
118
|
return;
|
|
112
119
|
}
|
|
@@ -114,19 +121,40 @@ export class McpContext {
|
|
|
114
121
|
if (roots === undefined) {
|
|
115
122
|
return;
|
|
116
123
|
}
|
|
117
|
-
|
|
124
|
+
let canonicalPath;
|
|
125
|
+
try {
|
|
126
|
+
canonicalPath = await resolveCanonicalPath(filePath);
|
|
127
|
+
}
|
|
128
|
+
catch (err) {
|
|
129
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
130
|
+
console.error(`[MCP Context] Error resolving real path for ${filePath}: ${errMsg}`);
|
|
131
|
+
throw new Error(`Access denied: Cannot resolve base path for ${filePath}.`);
|
|
132
|
+
}
|
|
133
|
+
let allowed = false;
|
|
118
134
|
for (const root of roots) {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
135
|
+
try {
|
|
136
|
+
const rootPathUri = root.uri;
|
|
137
|
+
const rootPath = path.resolve(fileURLToPath(rootPathUri));
|
|
138
|
+
const canonicalRoot = await fsPromises.realpath(rootPath);
|
|
139
|
+
if (canonicalPath === canonicalRoot ||
|
|
140
|
+
canonicalPath.startsWith(canonicalRoot + path.sep)) {
|
|
141
|
+
allowed = true;
|
|
142
|
+
break;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
catch (rootErr) {
|
|
146
|
+
const errMsg = rootErr instanceof Error ? rootErr.message : String(rootErr);
|
|
147
|
+
console.warn(`[MCP Context] Could not resolve configured root ${root.uri}: ${errMsg}`);
|
|
148
|
+
// Skip this root if it cannot be resolved.
|
|
123
149
|
}
|
|
124
150
|
}
|
|
125
|
-
|
|
151
|
+
if (!allowed) {
|
|
152
|
+
throw new Error(`Access denied: path ${filePath} (canonical: ${canonicalPath}) is not within any of the configured workspace roots.`);
|
|
153
|
+
}
|
|
126
154
|
}
|
|
127
155
|
resolveCdpRequestId(page, cdpRequestId) {
|
|
128
156
|
if (!cdpRequestId) {
|
|
129
|
-
this.logger('no network request');
|
|
157
|
+
this.logger?.('no network request');
|
|
130
158
|
return;
|
|
131
159
|
}
|
|
132
160
|
const request = this.#networkCollector.find(page.pptrPage, request => {
|
|
@@ -134,7 +162,7 @@ export class McpContext {
|
|
|
134
162
|
return request.id === cdpRequestId;
|
|
135
163
|
});
|
|
136
164
|
if (!request) {
|
|
137
|
-
this.logger('no network request for ' + cdpRequestId);
|
|
165
|
+
this.logger?.('no network request for ' + cdpRequestId);
|
|
138
166
|
return;
|
|
139
167
|
}
|
|
140
168
|
return this.#networkCollector.getIdForResource(request);
|
|
@@ -195,7 +223,13 @@ export class McpContext {
|
|
|
195
223
|
const page = targetPage ?? this.getSelectedPptrPage();
|
|
196
224
|
const mcpPage = this.#getMcpPage(page);
|
|
197
225
|
const newSettings = { ...mcpPage.emulationSettings };
|
|
198
|
-
if
|
|
226
|
+
// Skip network emulation if blocklist/allowlist is configured, as it conflicts with blocking rules in Puppeteer.
|
|
227
|
+
if (this.#options.hasNetworkBlockOrAllowlist) {
|
|
228
|
+
if (options.networkConditions !== undefined) {
|
|
229
|
+
throw new Error('Network throttling is not supported when network blocking (allowlist/blocklist) is configured.');
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
else if (!options.networkConditions) {
|
|
199
233
|
await page.emulateNetworkConditions(null);
|
|
200
234
|
delete newSettings.networkConditions;
|
|
201
235
|
}
|
|
@@ -213,12 +247,23 @@ export class McpContext {
|
|
|
213
247
|
await page.emulateNetworkConditions(networkCondition);
|
|
214
248
|
newSettings.networkConditions = options.networkConditions;
|
|
215
249
|
}
|
|
250
|
+
const secondarySession = this.getDevToolsUniverse(mcpPage)?.session;
|
|
216
251
|
if (!options.cpuThrottlingRate) {
|
|
217
252
|
await page.emulateCPUThrottling(1);
|
|
253
|
+
if (secondarySession) {
|
|
254
|
+
await secondarySession.send('Emulation.setCPUThrottlingRate', {
|
|
255
|
+
rate: 1,
|
|
256
|
+
});
|
|
257
|
+
}
|
|
218
258
|
delete newSettings.cpuThrottlingRate;
|
|
219
259
|
}
|
|
220
260
|
else {
|
|
221
261
|
await page.emulateCPUThrottling(options.cpuThrottlingRate);
|
|
262
|
+
if (secondarySession) {
|
|
263
|
+
await secondarySession.send('Emulation.setCPUThrottlingRate', {
|
|
264
|
+
rate: options.cpuThrottlingRate,
|
|
265
|
+
});
|
|
266
|
+
}
|
|
222
267
|
newSettings.cpuThrottlingRate = options.cpuThrottlingRate;
|
|
223
268
|
}
|
|
224
269
|
if (!options.geolocation) {
|
|
@@ -250,7 +295,6 @@ export class McpContext {
|
|
|
250
295
|
newSettings.colorScheme = options.colorScheme;
|
|
251
296
|
}
|
|
252
297
|
if (!options.viewport) {
|
|
253
|
-
await page.setViewport(null);
|
|
254
298
|
delete newSettings.viewport;
|
|
255
299
|
}
|
|
256
300
|
else {
|
|
@@ -260,14 +304,22 @@ export class McpContext {
|
|
|
260
304
|
hasTouch: false,
|
|
261
305
|
isLandscape: false,
|
|
262
306
|
};
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
307
|
+
newSettings.viewport = { ...defaults, ...options.viewport };
|
|
308
|
+
}
|
|
309
|
+
if (options.extraHttpHeaders !== undefined) {
|
|
310
|
+
await page.setExtraHTTPHeaders(options.extraHttpHeaders);
|
|
311
|
+
newSettings.extraHttpHeaders = options.extraHttpHeaders;
|
|
312
|
+
if (Object.keys(options.extraHttpHeaders).length === 0) {
|
|
313
|
+
delete newSettings.extraHttpHeaders;
|
|
314
|
+
}
|
|
266
315
|
}
|
|
267
316
|
mcpPage.emulationSettings = Object.keys(newSettings).length
|
|
268
317
|
? newSettings
|
|
269
318
|
: {};
|
|
270
319
|
this.#updateSelectedPageTimeouts();
|
|
320
|
+
// This should happen after updating the page timeouts.
|
|
321
|
+
// Setting the viewport can trigger a reload which we don't want to timeout.
|
|
322
|
+
await page.setViewport(newSettings.viewport ?? null);
|
|
271
323
|
}
|
|
272
324
|
setIsRunningPerformanceTrace(x) {
|
|
273
325
|
this.#isRunningTrace = x;
|
|
@@ -333,9 +385,9 @@ export class McpContext {
|
|
|
333
385
|
page.pptrPage.setDefaultTimeout(DEFAULT_TIMEOUT * cpuMultiplier);
|
|
334
386
|
// 10sec should be enough for the load event to be emitted during
|
|
335
387
|
// navigations.
|
|
336
|
-
// Increased in case we throttle the network requests
|
|
388
|
+
// Increased in case we throttle the network requests or the CPU
|
|
337
389
|
const networkMultiplier = getNetworkMultiplierFromString(page.networkConditions);
|
|
338
|
-
page.pptrPage.setDefaultNavigationTimeout(NAVIGATION_TIMEOUT * networkMultiplier);
|
|
390
|
+
page.pptrPage.setDefaultNavigationTimeout(NAVIGATION_TIMEOUT * networkMultiplier * cpuMultiplier);
|
|
339
391
|
}
|
|
340
392
|
// Linear scan over per-page snapshots. The page count is small (typically
|
|
341
393
|
// 2-10) so a reverse index isn't worthwhile given the uid-reuse lifecycle
|
|
@@ -372,6 +424,9 @@ export class McpContext {
|
|
|
372
424
|
});
|
|
373
425
|
return this.#extensionServiceWorkers;
|
|
374
426
|
}
|
|
427
|
+
getServiceWorkerConsoleData(extensionId) {
|
|
428
|
+
return this.#serviceWorkerConsoleCollector.getData(extensionId);
|
|
429
|
+
}
|
|
375
430
|
async createPagesSnapshot() {
|
|
376
431
|
const { pages: allPages, isolatedContextNames } = await this.#getAllPages();
|
|
377
432
|
for (const page of allPages) {
|
|
@@ -381,7 +436,7 @@ export class McpContext {
|
|
|
381
436
|
this.#mcpPages.set(page, mcpPage);
|
|
382
437
|
// We emulate a focused page for all pages to support multi-agent workflows.
|
|
383
438
|
void page.emulateFocusedPage(true).catch(error => {
|
|
384
|
-
this.logger('Error turning on focused page emulation', error);
|
|
439
|
+
this.logger?.('Error turning on focused page emulation', error);
|
|
385
440
|
});
|
|
386
441
|
}
|
|
387
442
|
mcpPage.isolatedContextName = isolatedContextNames.get(page);
|
|
@@ -427,7 +482,7 @@ export class McpContext {
|
|
|
427
482
|
this.#extensionPages.set(target, page);
|
|
428
483
|
}
|
|
429
484
|
catch (e) {
|
|
430
|
-
this.logger('Failed to get page for extension target', e);
|
|
485
|
+
this.logger?.('Failed to get page for extension target', e);
|
|
431
486
|
}
|
|
432
487
|
}
|
|
433
488
|
}
|
|
@@ -462,7 +517,7 @@ export class McpContext {
|
|
|
462
517
|
return { pages: allPages, isolatedContextNames };
|
|
463
518
|
}
|
|
464
519
|
async detectOpenDevToolsWindows() {
|
|
465
|
-
this.logger('Detecting open DevTools windows');
|
|
520
|
+
this.logger?.('Detecting open DevTools windows');
|
|
466
521
|
const { pages } = await this.#getAllPages();
|
|
467
522
|
await Promise.all(pages.map(async (page) => {
|
|
468
523
|
const mcpPage = this.#mcpPages.get(page);
|
|
@@ -499,7 +554,7 @@ export class McpContext {
|
|
|
499
554
|
}
|
|
500
555
|
async saveTemporaryFile(data, filename) {
|
|
501
556
|
const filepath = await getTempFilePath(filename);
|
|
502
|
-
this.validatePath(filepath);
|
|
557
|
+
await this.validatePath(filepath);
|
|
503
558
|
try {
|
|
504
559
|
await fs.writeFile(filepath, data);
|
|
505
560
|
}
|
|
@@ -509,7 +564,7 @@ export class McpContext {
|
|
|
509
564
|
return { filepath };
|
|
510
565
|
}
|
|
511
566
|
async saveFile(data, clientProvidedFilePath, extension) {
|
|
512
|
-
this.validatePath(clientProvidedFilePath);
|
|
567
|
+
await this.validatePath(clientProvidedFilePath);
|
|
513
568
|
try {
|
|
514
569
|
const filePath = ensureExtension(path.resolve(clientProvidedFilePath), extension);
|
|
515
570
|
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
@@ -517,7 +572,7 @@ export class McpContext {
|
|
|
517
572
|
return { filename: filePath };
|
|
518
573
|
}
|
|
519
574
|
catch (err) {
|
|
520
|
-
this.logger(err);
|
|
575
|
+
this.logger?.(err);
|
|
521
576
|
throw new Error('Could not save a file', { cause: err });
|
|
522
577
|
}
|
|
523
578
|
}
|
|
@@ -562,7 +617,6 @@ export class McpContext {
|
|
|
562
617
|
await this.#networkCollector.init(pages);
|
|
563
618
|
}
|
|
564
619
|
async installExtension(extensionPath) {
|
|
565
|
-
this.validatePath(extensionPath);
|
|
566
620
|
const id = await this.browser.installExtension(extensionPath);
|
|
567
621
|
return id;
|
|
568
622
|
}
|
|
@@ -586,20 +640,28 @@ export class McpContext {
|
|
|
586
640
|
return pptrExtensions.get(id);
|
|
587
641
|
}
|
|
588
642
|
async getHeapSnapshotAggregates(filePath) {
|
|
589
|
-
this.validatePath(filePath);
|
|
590
643
|
return await this.#heapSnapshotManager.getAggregates(filePath);
|
|
591
644
|
}
|
|
592
645
|
async getHeapSnapshotStats(filePath) {
|
|
593
|
-
this.validatePath(filePath);
|
|
594
646
|
return await this.#heapSnapshotManager.getStats(filePath);
|
|
595
647
|
}
|
|
596
648
|
async getHeapSnapshotStaticData(filePath) {
|
|
597
|
-
this.validatePath(filePath);
|
|
598
649
|
return await this.#heapSnapshotManager.getStaticData(filePath);
|
|
599
650
|
}
|
|
600
|
-
async
|
|
601
|
-
this.
|
|
602
|
-
|
|
651
|
+
async getHeapSnapshotNodesById(filePath, id) {
|
|
652
|
+
return await this.#heapSnapshotManager.getNodesById(filePath, id);
|
|
653
|
+
}
|
|
654
|
+
async getHeapSnapshotRetainers(filePath, nodeId) {
|
|
655
|
+
return await this.#heapSnapshotManager.getRetainers(filePath, nodeId);
|
|
656
|
+
}
|
|
657
|
+
async closeHeapSnapshot(filePath) {
|
|
658
|
+
return this.#heapSnapshotManager.dispose(filePath);
|
|
659
|
+
}
|
|
660
|
+
hasHeapSnapshots() {
|
|
661
|
+
return this.#heapSnapshotManager.hasSnapshots();
|
|
662
|
+
}
|
|
663
|
+
async getHeapSnapshotRetainingPaths(filePath, nodeId, maxDepth, maxNodes, maxSiblings) {
|
|
664
|
+
return await this.#heapSnapshotManager.getRetainingPaths(filePath, nodeId, maxDepth, maxNodes, maxSiblings);
|
|
603
665
|
}
|
|
604
666
|
}
|
|
605
667
|
//# sourceMappingURL=McpContext.js.map
|
package/build/src/McpPage.js
CHANGED
|
@@ -30,7 +30,7 @@ export class McpPage {
|
|
|
30
30
|
// Dialog
|
|
31
31
|
#dialog;
|
|
32
32
|
#dialogHandler;
|
|
33
|
-
thirdPartyDeveloperTools;
|
|
33
|
+
thirdPartyDeveloperTools = [];
|
|
34
34
|
constructor(page, id) {
|
|
35
35
|
this.pptrPage = page;
|
|
36
36
|
this.id = id;
|
|
@@ -194,18 +194,18 @@ export class McpPage {
|
|
|
194
194
|
for (const handle of oldHandles) {
|
|
195
195
|
await handle
|
|
196
196
|
.dispose()
|
|
197
|
-
.catch(e => logger('Failed to dispose old handle', e));
|
|
197
|
+
.catch(e => logger?.('Failed to dispose old handle', e));
|
|
198
198
|
}
|
|
199
199
|
}
|
|
200
200
|
const cdpElementIds = await Promise.all(elementHandles.map(async (elementHandle, index) => {
|
|
201
201
|
const backendNodeId = await elementHandle.backendNodeId();
|
|
202
202
|
if (!backendNodeId) {
|
|
203
|
-
logger(`No backendNodeId for stashed DOM element with index ${index}`);
|
|
203
|
+
logger?.(`No backendNodeId for stashed DOM element with index ${index}`);
|
|
204
204
|
return `stashed-${index}`;
|
|
205
205
|
}
|
|
206
206
|
const cdpElementId = this.resolveCdpElementId(backendNodeId);
|
|
207
207
|
if (!cdpElementId) {
|
|
208
|
-
logger(`Could not get cdpElementId for backend node ${backendNodeId}`);
|
|
208
|
+
logger?.(`Could not get cdpElementId for backend node ${backendNodeId}`);
|
|
209
209
|
return `stashed-${index}`;
|
|
210
210
|
}
|
|
211
211
|
return cdpElementId;
|
|
@@ -263,12 +263,12 @@ export class McpPage {
|
|
|
263
263
|
}
|
|
264
264
|
resolveCdpElementId(cdpBackendNodeId) {
|
|
265
265
|
if (!cdpBackendNodeId) {
|
|
266
|
-
logger('no cdpBackendNodeId');
|
|
266
|
+
logger?.('no cdpBackendNodeId');
|
|
267
267
|
return;
|
|
268
268
|
}
|
|
269
269
|
const snapshot = this.textSnapshot;
|
|
270
270
|
if (!snapshot) {
|
|
271
|
-
logger('no text snapshot');
|
|
271
|
+
logger?.('no text snapshot');
|
|
272
272
|
return;
|
|
273
273
|
}
|
|
274
274
|
// TODO: index by backendNodeId instead.
|
|
@@ -286,10 +286,10 @@ export class McpPage {
|
|
|
286
286
|
}
|
|
287
287
|
async getDevToolsData() {
|
|
288
288
|
try {
|
|
289
|
-
logger('Getting DevTools UI data');
|
|
289
|
+
logger?.('Getting DevTools UI data');
|
|
290
290
|
const devtoolsPage = this.devToolsPage;
|
|
291
291
|
if (!devtoolsPage) {
|
|
292
|
-
logger('No DevTools page detected');
|
|
292
|
+
logger?.('No DevTools page detected');
|
|
293
293
|
return {};
|
|
294
294
|
}
|
|
295
295
|
const { cdpRequestId, cdpBackendNodeId } = await devtoolsPage.evaluate(async () => {
|
|
@@ -307,7 +307,7 @@ export class McpPage {
|
|
|
307
307
|
return { cdpBackendNodeId, cdpRequestId };
|
|
308
308
|
}
|
|
309
309
|
catch (err) {
|
|
310
|
-
logger('error getting devtools data', err);
|
|
310
|
+
logger?.('error getting devtools data', err);
|
|
311
311
|
}
|
|
312
312
|
return {};
|
|
313
313
|
}
|