chrome-devtools-mcp 0.13.0 → 0.15.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 +28 -2
- package/build/src/DevtoolsUtils.js +59 -42
- package/build/src/McpContext.js +106 -13
- package/build/src/McpResponse.js +213 -132
- package/build/src/browser.js +1 -0
- package/build/src/cli.js +29 -6
- package/build/src/formatters/ConsoleFormatter.js +153 -0
- package/build/src/formatters/IssueFormatter.js +190 -0
- package/build/src/formatters/NetworkFormatter.js +226 -0
- package/build/src/formatters/SnapshotFormatter.js +6 -0
- package/build/src/logger.js +9 -0
- package/build/src/main.js +16 -3
- package/build/src/telemetry/clearcut-logger.js +86 -12
- package/build/src/telemetry/flag-utils.js +1 -1
- package/build/src/telemetry/metric-utils.js +14 -0
- package/build/src/telemetry/persistence.js +53 -0
- package/build/src/telemetry/types.js +6 -0
- package/build/src/telemetry/watchdog/clearcut-sender.js +201 -0
- package/build/src/telemetry/watchdog/main.js +127 -0
- package/build/src/telemetry/watchdog-client.js +60 -0
- package/build/src/third_party/THIRD_PARTY_NOTICES +6 -5
- package/build/src/third_party/devtools-formatter-worker.js +15451 -0
- package/build/src/third_party/index.js +1356 -282
- package/build/src/tools/categories.js +2 -0
- package/build/src/tools/emulation.js +83 -1
- package/build/src/tools/extensions.js +79 -0
- package/build/src/tools/input.js +58 -9
- package/build/src/tools/network.js +17 -3
- package/build/src/tools/pages.js +91 -46
- package/build/src/tools/performance.js +6 -20
- package/build/src/tools/tools.js +2 -0
- package/build/src/utils/ExtensionRegistry.js +35 -0
- package/package.json +9 -8
- package/build/src/formatters/consoleFormatter.js +0 -156
- package/build/src/formatters/networkFormatter.js +0 -77
- package/build/src/telemetry/clearcut-sender.js +0 -11
- package/build/src/third_party/devtools.js +0 -6
package/README.md
CHANGED
|
@@ -27,6 +27,20 @@ allowing them to inspect, debug, and modify any data in the browser or DevTools.
|
|
|
27
27
|
Avoid sharing sensitive or personal information that you don't want to share with
|
|
28
28
|
MCP clients.
|
|
29
29
|
|
|
30
|
+
## **Usage statistics**
|
|
31
|
+
|
|
32
|
+
Google collects usage statistics (such as tool invocation success rates, latency, and environment information) to improve the reliability and performance of Chrome DevTools MCP.
|
|
33
|
+
|
|
34
|
+
Data collection is **enabled by default**. You can opt-out by passing the `--no-usage-statistics` flag when starting the server:
|
|
35
|
+
|
|
36
|
+
```json
|
|
37
|
+
"args": ["-y", "chrome-devtools-mcp@latest", "--no-usage-statistics"]
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Google handles this data in accordance with the [Google Privacy Policy](https://policies.google.com/privacy).
|
|
41
|
+
|
|
42
|
+
Google's collection of usage statistics for Chrome DevTools MCP is independent from the Chrome browser's usage statistics. Opting out of Chrome metrics does not automatically opt you out of this tool, and vice-versa.
|
|
43
|
+
|
|
30
44
|
## Requirements
|
|
31
45
|
|
|
32
46
|
- [Node.js](https://nodejs.org/) v20.19 or a newer [latest maintenance LTS](https://github.com/nodejs/Release#release-schedule) version.
|
|
@@ -66,7 +80,7 @@ amp mcp add chrome-devtools -- npx chrome-devtools-mcp@latest
|
|
|
66
80
|
<details>
|
|
67
81
|
<summary>Antigravity</summary>
|
|
68
82
|
|
|
69
|
-
To use the Chrome DevTools MCP server follow the instructions from <a href="https://antigravity.google/docs/mcp">Antigravity's docs
|
|
83
|
+
To use the Chrome DevTools MCP server follow the instructions from <a href="https://antigravity.google/docs/mcp">Antigravity's docs</a> to install a custom MCP server. Add the following config to the MCP servers config:
|
|
70
84
|
|
|
71
85
|
```bash
|
|
72
86
|
{
|
|
@@ -205,7 +219,10 @@ Install the Chrome DevTools MCP server using the Gemini CLI.
|
|
|
205
219
|
**Project wide:**
|
|
206
220
|
|
|
207
221
|
```bash
|
|
222
|
+
# Either MCP only:
|
|
208
223
|
gemini mcp add chrome-devtools npx chrome-devtools-mcp@latest
|
|
224
|
+
# Or as a Gemini extension (MCP+Skills):
|
|
225
|
+
gemini extensions install --auto-update https://github.com/ChromeDevTools/chrome-devtools-mcp
|
|
209
226
|
```
|
|
210
227
|
|
|
211
228
|
**Globally:**
|
|
@@ -370,7 +387,7 @@ The Chrome DevTools MCP server supports the following configuration option:
|
|
|
370
387
|
<!-- BEGIN AUTO GENERATED OPTIONS -->
|
|
371
388
|
|
|
372
389
|
- **`--autoConnect`/ `--auto-connect`**
|
|
373
|
-
If specified, automatically connects to a browser (Chrome
|
|
390
|
+
If specified, automatically connects to a browser (Chrome 144+) running in the user data directory identified by the channel param. Requires the remoted debugging server to be started in the Chrome instance via chrome://inspect/#remote-debugging.
|
|
374
391
|
- **Type:** boolean
|
|
375
392
|
- **Default:** `false`
|
|
376
393
|
|
|
@@ -447,6 +464,11 @@ The Chrome DevTools MCP server supports the following configuration option:
|
|
|
447
464
|
- **Type:** boolean
|
|
448
465
|
- **Default:** `true`
|
|
449
466
|
|
|
467
|
+
- **`--usageStatistics`/ `--usage-statistics`**
|
|
468
|
+
Set to false to opt-out of usage statistics collection. Google collects usage data to improve the tool, handled under the Google Privacy Policy (https://policies.google.com/privacy). This is independent from Chrome browser metrics.
|
|
469
|
+
- **Type:** boolean
|
|
470
|
+
- **Default:** `true`
|
|
471
|
+
|
|
450
472
|
<!-- END AUTO GENERATED OPTIONS -->
|
|
451
473
|
|
|
452
474
|
Pass them via the `args` property in the JSON configuration. For example:
|
|
@@ -631,6 +653,10 @@ If you hit VM-to-host port forwarding issues, see the “Remote debugging betwee
|
|
|
631
653
|
|
|
632
654
|
For more details on remote debugging, see the [Chrome DevTools documentation](https://developer.chrome.com/docs/devtools/remote-debugging/).
|
|
633
655
|
|
|
656
|
+
### Debugging Chrome on Android
|
|
657
|
+
|
|
658
|
+
Please consult [these instructions](./docs/debugging-android.md).
|
|
659
|
+
|
|
634
660
|
## Known limitations
|
|
635
661
|
|
|
636
662
|
### Operating system sandboxes
|
|
@@ -4,8 +4,6 @@
|
|
|
4
4
|
* SPDX-License-Identifier: Apache-2.0
|
|
5
5
|
*/
|
|
6
6
|
import { PuppeteerDevToolsConnection } from './DevToolsConnectionAdapter.js';
|
|
7
|
-
import { ISSUE_UTILS } from './issue-descriptions.js';
|
|
8
|
-
import { logger } from './logger.js';
|
|
9
7
|
import { Mutex } from './Mutex.js';
|
|
10
8
|
import { DevTools } from './third_party/index.js';
|
|
11
9
|
export function extractUrlLikeFromDevToolsTitle(title) {
|
|
@@ -64,46 +62,6 @@ export class FakeIssuesManager extends DevTools.Common.ObjectWrapper
|
|
|
64
62
|
return [];
|
|
65
63
|
}
|
|
66
64
|
}
|
|
67
|
-
export function mapIssueToMessageObject(issue) {
|
|
68
|
-
const count = issue.getAggregatedIssuesCount();
|
|
69
|
-
const markdownDescription = issue.getDescription();
|
|
70
|
-
const filename = markdownDescription?.file;
|
|
71
|
-
if (!markdownDescription) {
|
|
72
|
-
logger(`no description found for issue:` + issue.code);
|
|
73
|
-
return null;
|
|
74
|
-
}
|
|
75
|
-
const rawMarkdown = filename
|
|
76
|
-
? ISSUE_UTILS.getIssueDescription(filename)
|
|
77
|
-
: null;
|
|
78
|
-
if (!rawMarkdown) {
|
|
79
|
-
logger(`no markdown ${filename} found for issue:` + issue.code);
|
|
80
|
-
return null;
|
|
81
|
-
}
|
|
82
|
-
let processedMarkdown;
|
|
83
|
-
let title;
|
|
84
|
-
try {
|
|
85
|
-
processedMarkdown =
|
|
86
|
-
DevTools.MarkdownIssueDescription.substitutePlaceholders(rawMarkdown, markdownDescription.substitutions);
|
|
87
|
-
const markdownAst = DevTools.Marked.Marked.lexer(processedMarkdown);
|
|
88
|
-
title =
|
|
89
|
-
DevTools.MarkdownIssueDescription.findTitleFromMarkdownAst(markdownAst);
|
|
90
|
-
}
|
|
91
|
-
catch {
|
|
92
|
-
logger('error parsing markdown for issue ' + issue.code());
|
|
93
|
-
return null;
|
|
94
|
-
}
|
|
95
|
-
if (!title) {
|
|
96
|
-
logger('cannot read issue title from ' + filename);
|
|
97
|
-
return null;
|
|
98
|
-
}
|
|
99
|
-
return {
|
|
100
|
-
type: 'issue',
|
|
101
|
-
item: issue,
|
|
102
|
-
message: title,
|
|
103
|
-
count,
|
|
104
|
-
description: processedMarkdown,
|
|
105
|
-
};
|
|
106
|
-
}
|
|
107
65
|
// DevTools CDP errors can get noisy.
|
|
108
66
|
DevTools.ProtocolClient.InspectorBackend.test.suppressRequestErrors = true;
|
|
109
67
|
DevTools.I18n.DevToolsLocale.DevToolsLocale.instance({
|
|
@@ -115,6 +73,11 @@ DevTools.I18n.DevToolsLocale.DevToolsLocale.instance({
|
|
|
115
73
|
},
|
|
116
74
|
});
|
|
117
75
|
DevTools.I18n.i18n.registerLocaleDataForTest('en-US', {});
|
|
76
|
+
DevTools.Formatter.FormatterWorkerPool.FormatterWorkerPool.instance({
|
|
77
|
+
forceNew: true,
|
|
78
|
+
entrypointURL: import.meta
|
|
79
|
+
.resolve('./third_party/devtools-formatter-worker.js'),
|
|
80
|
+
});
|
|
118
81
|
export class UniverseManager {
|
|
119
82
|
#browser;
|
|
120
83
|
#createUniverseFor;
|
|
@@ -206,3 +169,57 @@ const SKIP_ALL_PAUSES = {
|
|
|
206
169
|
// Do nothing.
|
|
207
170
|
},
|
|
208
171
|
};
|
|
172
|
+
export async function createStackTraceForConsoleMessage(devTools, consoleMessage) {
|
|
173
|
+
const message = consoleMessage;
|
|
174
|
+
const rawStackTrace = message._rawStackTrace();
|
|
175
|
+
if (!rawStackTrace) {
|
|
176
|
+
return undefined;
|
|
177
|
+
}
|
|
178
|
+
const targetManager = devTools.universe.context.get(DevTools.TargetManager);
|
|
179
|
+
const messageTargetId = message._targetId();
|
|
180
|
+
const target = messageTargetId
|
|
181
|
+
? targetManager.targetById(messageTargetId) || devTools.target
|
|
182
|
+
: devTools.target;
|
|
183
|
+
const model = target.model(DevTools.DebuggerModel);
|
|
184
|
+
// DevTools doesn't wait for source maps to attach before building a stack trace, rather it'll send
|
|
185
|
+
// an update event once a source map was attached and the stack trace retranslated. This doesn't
|
|
186
|
+
// work in the MCP case, so we'll collect all script IDs upfront and wait for any pending source map
|
|
187
|
+
// loads before creating the stack trace. We might also have to wait for Debugger.ScriptParsed events if
|
|
188
|
+
// the stack trace is created particularly early.
|
|
189
|
+
const scriptIds = new Set();
|
|
190
|
+
for (const frame of rawStackTrace.callFrames) {
|
|
191
|
+
scriptIds.add(frame.scriptId);
|
|
192
|
+
}
|
|
193
|
+
for (let asyncStack = rawStackTrace.parent; asyncStack; asyncStack = asyncStack.parent) {
|
|
194
|
+
for (const frame of asyncStack.callFrames) {
|
|
195
|
+
scriptIds.add(frame.scriptId);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
const signal = AbortSignal.timeout(1_000);
|
|
199
|
+
await Promise.all([...scriptIds].map(id => waitForScript(model, id, signal)
|
|
200
|
+
.then(script => model.sourceMapManager().sourceMapForClientPromise(script))
|
|
201
|
+
.catch()));
|
|
202
|
+
const binding = devTools.universe.context.get(DevTools.DebuggerWorkspaceBinding);
|
|
203
|
+
// DevTools uses branded types for ScriptId and others. Casting the puppeteer protocol type to the DevTools protocol type is safe.
|
|
204
|
+
return binding.createStackTraceFromProtocolRuntime(rawStackTrace, target);
|
|
205
|
+
}
|
|
206
|
+
// Waits indefinitely for the script so pair it with Promise.race.
|
|
207
|
+
async function waitForScript(model, scriptId, signal) {
|
|
208
|
+
while (true) {
|
|
209
|
+
if (signal.aborted) {
|
|
210
|
+
throw signal.reason;
|
|
211
|
+
}
|
|
212
|
+
const script = model.scriptForId(scriptId);
|
|
213
|
+
if (script) {
|
|
214
|
+
return script;
|
|
215
|
+
}
|
|
216
|
+
await new Promise((resolve, reject) => {
|
|
217
|
+
signal.addEventListener('abort', () => reject(signal.reason), {
|
|
218
|
+
once: true,
|
|
219
|
+
});
|
|
220
|
+
void model
|
|
221
|
+
.once('ParsedScriptSource')
|
|
222
|
+
.then(resolve);
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
}
|
package/build/src/McpContext.js
CHANGED
|
@@ -6,12 +6,13 @@
|
|
|
6
6
|
import fs from 'node:fs/promises';
|
|
7
7
|
import os from 'node:os';
|
|
8
8
|
import path from 'node:path';
|
|
9
|
-
import { extractUrlLikeFromDevToolsTitle, urlsEqual } from './DevtoolsUtils.js';
|
|
9
|
+
import { extractUrlLikeFromDevToolsTitle, UniverseManager, urlsEqual, } from './DevtoolsUtils.js';
|
|
10
10
|
import { NetworkCollector, ConsoleCollector } from './PageCollector.js';
|
|
11
11
|
import { Locator } from './third_party/index.js';
|
|
12
12
|
import { listPages } from './tools/pages.js';
|
|
13
13
|
import { takeSnapshot } from './tools/snapshot.js';
|
|
14
14
|
import { CLOSE_PAGE_ERROR } from './tools/ToolDefinition.js';
|
|
15
|
+
import { ExtensionRegistry, } from './utils/ExtensionRegistry.js';
|
|
15
16
|
import { WaitForHelper } from './WaitForHelper.js';
|
|
16
17
|
const DEFAULT_TIMEOUT = 5_000;
|
|
17
18
|
const NAVIGATION_TIMEOUT = 10_000;
|
|
@@ -51,10 +52,15 @@ export class McpContext {
|
|
|
51
52
|
#textSnapshot = null;
|
|
52
53
|
#networkCollector;
|
|
53
54
|
#consoleCollector;
|
|
55
|
+
#devtoolsUniverseManager;
|
|
56
|
+
#extensionRegistry = new ExtensionRegistry();
|
|
54
57
|
#isRunningTrace = false;
|
|
55
58
|
#networkConditionsMap = new WeakMap();
|
|
56
59
|
#cpuThrottlingRateMap = new WeakMap();
|
|
57
60
|
#geolocationMap = new WeakMap();
|
|
61
|
+
#viewportMap = new WeakMap();
|
|
62
|
+
#userAgentMap = new WeakMap();
|
|
63
|
+
#colorSchemeMap = new WeakMap();
|
|
58
64
|
#dialog;
|
|
59
65
|
#pageIdMap = new WeakMap();
|
|
60
66
|
#nextPageId = 1;
|
|
@@ -62,6 +68,7 @@ export class McpContext {
|
|
|
62
68
|
#traceResults = [];
|
|
63
69
|
#locatorClass;
|
|
64
70
|
#options;
|
|
71
|
+
#uniqueBackendNodeIdToMcpId = new Map();
|
|
65
72
|
constructor(browser, logger, options, locatorClass) {
|
|
66
73
|
this.browser = browser;
|
|
67
74
|
this.logger = logger;
|
|
@@ -88,15 +95,18 @@ export class McpContext {
|
|
|
88
95
|
},
|
|
89
96
|
};
|
|
90
97
|
});
|
|
98
|
+
this.#devtoolsUniverseManager = new UniverseManager(this.browser);
|
|
91
99
|
}
|
|
92
100
|
async #init() {
|
|
93
101
|
const pages = await this.createPagesSnapshot();
|
|
94
102
|
await this.#networkCollector.init(pages);
|
|
95
103
|
await this.#consoleCollector.init(pages);
|
|
104
|
+
await this.#devtoolsUniverseManager.init(pages);
|
|
96
105
|
}
|
|
97
106
|
dispose() {
|
|
98
107
|
this.#networkCollector.dispose();
|
|
99
108
|
this.#consoleCollector.dispose();
|
|
109
|
+
this.#devtoolsUniverseManager.dispose();
|
|
100
110
|
}
|
|
101
111
|
static async from(browser, logger, opts,
|
|
102
112
|
/* Let tests use unbundled Locator class to avoid overly strict checks within puppeteer that fail when mixing bundled and unbundled class instances */
|
|
@@ -151,14 +161,17 @@ export class McpContext {
|
|
|
151
161
|
const page = this.getSelectedPage();
|
|
152
162
|
return this.#consoleCollector.getData(page, includePreservedMessages);
|
|
153
163
|
}
|
|
164
|
+
getDevToolsUniverse() {
|
|
165
|
+
return this.#devtoolsUniverseManager.get(this.getSelectedPage());
|
|
166
|
+
}
|
|
154
167
|
getConsoleMessageStableId(message) {
|
|
155
168
|
return this.#consoleCollector.getIdForResource(message);
|
|
156
169
|
}
|
|
157
170
|
getConsoleMessageById(id) {
|
|
158
171
|
return this.#consoleCollector.getById(this.getSelectedPage(), id);
|
|
159
172
|
}
|
|
160
|
-
async newPage() {
|
|
161
|
-
const page = await this.browser.newPage();
|
|
173
|
+
async newPage(background) {
|
|
174
|
+
const page = await this.browser.newPage({ background });
|
|
162
175
|
await this.createPagesSnapshot();
|
|
163
176
|
this.selectPage(page);
|
|
164
177
|
this.#networkCollector.addPage(page);
|
|
@@ -211,6 +224,45 @@ export class McpContext {
|
|
|
211
224
|
const page = this.getSelectedPage();
|
|
212
225
|
return this.#geolocationMap.get(page) ?? null;
|
|
213
226
|
}
|
|
227
|
+
setViewport(viewport) {
|
|
228
|
+
const page = this.getSelectedPage();
|
|
229
|
+
if (viewport === null) {
|
|
230
|
+
this.#viewportMap.delete(page);
|
|
231
|
+
}
|
|
232
|
+
else {
|
|
233
|
+
this.#viewportMap.set(page, viewport);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
getViewport() {
|
|
237
|
+
const page = this.getSelectedPage();
|
|
238
|
+
return this.#viewportMap.get(page) ?? null;
|
|
239
|
+
}
|
|
240
|
+
setUserAgent(userAgent) {
|
|
241
|
+
const page = this.getSelectedPage();
|
|
242
|
+
if (userAgent === null) {
|
|
243
|
+
this.#userAgentMap.delete(page);
|
|
244
|
+
}
|
|
245
|
+
else {
|
|
246
|
+
this.#userAgentMap.set(page, userAgent);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
getUserAgent() {
|
|
250
|
+
const page = this.getSelectedPage();
|
|
251
|
+
return this.#userAgentMap.get(page) ?? null;
|
|
252
|
+
}
|
|
253
|
+
setColorScheme(scheme) {
|
|
254
|
+
const page = this.getSelectedPage();
|
|
255
|
+
if (scheme === null) {
|
|
256
|
+
this.#colorSchemeMap.delete(page);
|
|
257
|
+
}
|
|
258
|
+
else {
|
|
259
|
+
this.#colorSchemeMap.set(page, scheme);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
getColorScheme() {
|
|
263
|
+
const page = this.getSelectedPage();
|
|
264
|
+
return this.#colorSchemeMap.get(page) ?? null;
|
|
265
|
+
}
|
|
214
266
|
setIsRunningPerformanceTrace(x) {
|
|
215
267
|
this.#isRunningTrace = x;
|
|
216
268
|
}
|
|
@@ -287,19 +339,23 @@ export class McpContext {
|
|
|
287
339
|
if (!this.#textSnapshot?.idToNode.size) {
|
|
288
340
|
throw new Error(`No snapshot found. Use ${takeSnapshot.name} to capture one.`);
|
|
289
341
|
}
|
|
290
|
-
const [snapshotId] = uid.split('_');
|
|
291
|
-
if (this.#textSnapshot.snapshotId !== snapshotId) {
|
|
292
|
-
throw new Error('This uid is coming from a stale snapshot. Call take_snapshot to get a fresh snapshot.');
|
|
293
|
-
}
|
|
294
342
|
const node = this.#textSnapshot?.idToNode.get(uid);
|
|
295
343
|
if (!node) {
|
|
296
|
-
throw new Error('No such element found in the snapshot');
|
|
344
|
+
throw new Error('No such element found in the snapshot.');
|
|
297
345
|
}
|
|
298
|
-
const
|
|
299
|
-
|
|
300
|
-
|
|
346
|
+
const message = `Element with uid ${uid} no longer exists on the page.`;
|
|
347
|
+
try {
|
|
348
|
+
const handle = await node.elementHandle();
|
|
349
|
+
if (!handle) {
|
|
350
|
+
throw new Error(message);
|
|
351
|
+
}
|
|
352
|
+
return handle;
|
|
353
|
+
}
|
|
354
|
+
catch (error) {
|
|
355
|
+
throw new Error(message, {
|
|
356
|
+
cause: error,
|
|
357
|
+
});
|
|
301
358
|
}
|
|
302
|
-
return handle;
|
|
303
359
|
}
|
|
304
360
|
/**
|
|
305
361
|
* Creates a snapshot of the pages.
|
|
@@ -405,10 +461,24 @@ export class McpContext {
|
|
|
405
461
|
// will be used for the tree serialization and mapping ids back to nodes.
|
|
406
462
|
let idCounter = 0;
|
|
407
463
|
const idToNode = new Map();
|
|
464
|
+
const seenUniqueIds = new Set();
|
|
408
465
|
const assignIds = (node) => {
|
|
466
|
+
let id = '';
|
|
467
|
+
// @ts-expect-error untyped loaderId & backendNodeId.
|
|
468
|
+
const uniqueBackendId = `${node.loaderId}_${node.backendNodeId}`;
|
|
469
|
+
if (this.#uniqueBackendNodeIdToMcpId.has(uniqueBackendId)) {
|
|
470
|
+
// Re-use MCP exposed ID if the uniqueId is the same.
|
|
471
|
+
id = this.#uniqueBackendNodeIdToMcpId.get(uniqueBackendId);
|
|
472
|
+
}
|
|
473
|
+
else {
|
|
474
|
+
// Only generate a new ID if we have not seen the node before.
|
|
475
|
+
id = `${snapshotId}_${idCounter++}`;
|
|
476
|
+
this.#uniqueBackendNodeIdToMcpId.set(uniqueBackendId, id);
|
|
477
|
+
}
|
|
478
|
+
seenUniqueIds.add(uniqueBackendId);
|
|
409
479
|
const nodeWithId = {
|
|
410
480
|
...node,
|
|
411
|
-
id
|
|
481
|
+
id,
|
|
412
482
|
children: node.children
|
|
413
483
|
? node.children.map(child => assignIds(child))
|
|
414
484
|
: [],
|
|
@@ -437,6 +507,12 @@ export class McpContext {
|
|
|
437
507
|
this.#textSnapshot.hasSelectedElement = true;
|
|
438
508
|
this.#textSnapshot.selectedElementUid = this.resolveCdpElementId(data?.cdpBackendNodeId);
|
|
439
509
|
}
|
|
510
|
+
// Clean up unique IDs that we did not see anymore.
|
|
511
|
+
for (const key of this.#uniqueBackendNodeIdToMcpId.keys()) {
|
|
512
|
+
if (!seenUniqueIds.has(key)) {
|
|
513
|
+
this.#uniqueBackendNodeIdToMcpId.delete(key);
|
|
514
|
+
}
|
|
515
|
+
}
|
|
440
516
|
}
|
|
441
517
|
getTextSnapshot() {
|
|
442
518
|
return this.#textSnapshot;
|
|
@@ -465,6 +541,8 @@ export class McpContext {
|
|
|
465
541
|
}
|
|
466
542
|
}
|
|
467
543
|
storeTraceRecording(result) {
|
|
544
|
+
// Clear the trace results because we only consume the latest trace currently.
|
|
545
|
+
this.#traceResults = [];
|
|
468
546
|
this.#traceResults.push(result);
|
|
469
547
|
}
|
|
470
548
|
recordedTraces() {
|
|
@@ -511,4 +589,19 @@ export class McpContext {
|
|
|
511
589
|
});
|
|
512
590
|
await this.#networkCollector.init(await this.browser.pages());
|
|
513
591
|
}
|
|
592
|
+
async installExtension(extensionPath) {
|
|
593
|
+
const id = await this.browser.installExtension(extensionPath);
|
|
594
|
+
await this.#extensionRegistry.registerExtension(id, extensionPath);
|
|
595
|
+
return id;
|
|
596
|
+
}
|
|
597
|
+
async uninstallExtension(id) {
|
|
598
|
+
await this.browser.uninstallExtension(id);
|
|
599
|
+
this.#extensionRegistry.remove(id);
|
|
600
|
+
}
|
|
601
|
+
listExtensions() {
|
|
602
|
+
return this.#extensionRegistry.list();
|
|
603
|
+
}
|
|
604
|
+
getExtension(id) {
|
|
605
|
+
return this.#extensionRegistry.getById(id);
|
|
606
|
+
}
|
|
514
607
|
}
|