@nimbus21.ai/chrome-devtools-mcp 0.12.3 → 0.17.4
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 +119 -24
- package/build/src/DevtoolsUtils.js +174 -42
- package/build/src/McpContext.js +127 -29
- package/build/src/McpResponse.js +245 -159
- package/build/src/PageCollector.js +18 -2
- package/build/src/WaitForHelper.js +2 -2
- package/build/src/browser.js +8 -13
- package/build/src/cli.js +65 -3
- package/build/src/formatters/ConsoleFormatter.js +240 -0
- package/build/src/formatters/IssueFormatter.js +190 -0
- package/build/src/formatters/NetworkFormatter.js +218 -0
- package/build/src/formatters/SnapshotFormatter.js +134 -0
- package/build/src/logger.js +9 -0
- package/build/src/main.js +66 -8
- package/build/src/telemetry/ClearcutLogger.js +102 -0
- package/build/src/telemetry/WatchdogClient.js +60 -0
- package/build/src/telemetry/flagUtils.js +45 -0
- package/build/src/telemetry/metricUtils.js +14 -0
- package/build/src/telemetry/persistence.js +53 -0
- package/build/src/telemetry/types.js +33 -0
- package/build/src/telemetry/watchdog/ClearcutSender.js +201 -0
- package/build/src/telemetry/watchdog/main.js +127 -0
- package/build/src/third_party/THIRD_PARTY_NOTICES +10 -9
- package/build/src/third_party/bundled-packages.json +8 -0
- package/build/src/third_party/devtools-formatter-worker.js +15449 -0
- package/build/src/third_party/index.js +4093 -2367
- package/build/src/third_party/issue-descriptions/connectionAllowlistInvalidAllowlistItemType.md +12 -0
- package/build/src/third_party/issue-descriptions/connectionAllowlistInvalidHeader.md +12 -0
- package/build/src/third_party/issue-descriptions/connectionAllowlistInvalidUrlPattern.md +8 -0
- package/build/src/third_party/issue-descriptions/connectionAllowlistItemNotInnerList.md +12 -0
- package/build/src/third_party/issue-descriptions/connectionAllowlistMoreThanOneList.md +7 -0
- package/build/src/third_party/issue-descriptions/connectionAllowlistReportingEndpointNotToken.md +10 -0
- 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 +93 -13
- package/build/src/tools/network.js +17 -3
- package/build/src/tools/pages.js +134 -54
- package/build/src/tools/performance.js +68 -27
- package/build/src/tools/script.js +2 -2
- package/build/src/tools/tools.js +2 -0
- package/build/src/utils/ExtensionRegistry.js +35 -0
- package/build/src/utils/string.js +36 -0
- package/package.json +17 -16
- package/build/src/formatters/consoleFormatter.js +0 -121
- package/build/src/formatters/networkFormatter.js +0 -77
- package/build/src/formatters/snapshotFormatter.js +0 -73
- package/build/src/third_party/devtools.js +0 -6
- package/build/src/third_party/issue-descriptions/SameSiteInvalidSameParty.md +0 -8
- package/build/src/third_party/issue-descriptions/SameSiteSamePartyCrossPartyContextSet.md +0 -10
- package/build/src/third_party/issue-descriptions/federatedAuthRequestClientMetadataHttpNotFound.md +0 -1
- package/build/src/third_party/issue-descriptions/federatedAuthRequestClientMetadataInvalidResponse.md +0 -1
- package/build/src/third_party/issue-descriptions/federatedAuthRequestClientMetadataNoResponse.md +0 -1
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,15 +52,23 @@ 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;
|
|
65
|
+
#pageIdMap = new WeakMap();
|
|
66
|
+
#nextPageId = 1;
|
|
59
67
|
#nextSnapshotId = 1;
|
|
60
68
|
#traceResults = [];
|
|
61
69
|
#locatorClass;
|
|
62
70
|
#options;
|
|
71
|
+
#uniqueBackendNodeIdToMcpId = new Map();
|
|
63
72
|
constructor(browser, logger, options, locatorClass) {
|
|
64
73
|
this.browser = browser;
|
|
65
74
|
this.logger = logger;
|
|
@@ -71,30 +80,26 @@ export class McpContext {
|
|
|
71
80
|
console: event => {
|
|
72
81
|
collect(event);
|
|
73
82
|
},
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
collect(event);
|
|
77
|
-
}
|
|
78
|
-
else {
|
|
79
|
-
const error = new Error(`${event}`);
|
|
80
|
-
error.stack = undefined;
|
|
81
|
-
collect(error);
|
|
82
|
-
}
|
|
83
|
+
uncaughtError: event => {
|
|
84
|
+
collect(event);
|
|
83
85
|
},
|
|
84
86
|
issue: event => {
|
|
85
87
|
collect(event);
|
|
86
88
|
},
|
|
87
89
|
};
|
|
88
90
|
});
|
|
91
|
+
this.#devtoolsUniverseManager = new UniverseManager(this.browser);
|
|
89
92
|
}
|
|
90
93
|
async #init() {
|
|
91
94
|
const pages = await this.createPagesSnapshot();
|
|
92
95
|
await this.#networkCollector.init(pages);
|
|
93
96
|
await this.#consoleCollector.init(pages);
|
|
97
|
+
await this.#devtoolsUniverseManager.init(pages);
|
|
94
98
|
}
|
|
95
99
|
dispose() {
|
|
96
100
|
this.#networkCollector.dispose();
|
|
97
101
|
this.#consoleCollector.dispose();
|
|
102
|
+
this.#devtoolsUniverseManager.dispose();
|
|
98
103
|
}
|
|
99
104
|
static async from(browser, logger, opts,
|
|
100
105
|
/* Let tests use unbundled Locator class to avoid overly strict checks within puppeteer that fail when mixing bundled and unbundled class instances */
|
|
@@ -149,25 +154,28 @@ export class McpContext {
|
|
|
149
154
|
const page = this.getSelectedPage();
|
|
150
155
|
return this.#consoleCollector.getData(page, includePreservedMessages);
|
|
151
156
|
}
|
|
157
|
+
getDevToolsUniverse() {
|
|
158
|
+
return this.#devtoolsUniverseManager.get(this.getSelectedPage());
|
|
159
|
+
}
|
|
152
160
|
getConsoleMessageStableId(message) {
|
|
153
161
|
return this.#consoleCollector.getIdForResource(message);
|
|
154
162
|
}
|
|
155
163
|
getConsoleMessageById(id) {
|
|
156
164
|
return this.#consoleCollector.getById(this.getSelectedPage(), id);
|
|
157
165
|
}
|
|
158
|
-
async newPage() {
|
|
159
|
-
const page = await this.browser.newPage();
|
|
166
|
+
async newPage(background) {
|
|
167
|
+
const page = await this.browser.newPage({ background });
|
|
160
168
|
await this.createPagesSnapshot();
|
|
161
169
|
this.selectPage(page);
|
|
162
170
|
this.#networkCollector.addPage(page);
|
|
163
171
|
this.#consoleCollector.addPage(page);
|
|
164
172
|
return page;
|
|
165
173
|
}
|
|
166
|
-
async closePage(
|
|
174
|
+
async closePage(pageId) {
|
|
167
175
|
if (this.#pages.length === 1) {
|
|
168
176
|
throw new Error(CLOSE_PAGE_ERROR);
|
|
169
177
|
}
|
|
170
|
-
const page = this.
|
|
178
|
+
const page = this.getPageById(pageId);
|
|
171
179
|
await page.close({ runBeforeUnload: false });
|
|
172
180
|
}
|
|
173
181
|
getNetworkRequestById(reqid) {
|
|
@@ -209,12 +217,54 @@ export class McpContext {
|
|
|
209
217
|
const page = this.getSelectedPage();
|
|
210
218
|
return this.#geolocationMap.get(page) ?? null;
|
|
211
219
|
}
|
|
220
|
+
setViewport(viewport) {
|
|
221
|
+
const page = this.getSelectedPage();
|
|
222
|
+
if (viewport === null) {
|
|
223
|
+
this.#viewportMap.delete(page);
|
|
224
|
+
}
|
|
225
|
+
else {
|
|
226
|
+
this.#viewportMap.set(page, viewport);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
getViewport() {
|
|
230
|
+
const page = this.getSelectedPage();
|
|
231
|
+
return this.#viewportMap.get(page) ?? null;
|
|
232
|
+
}
|
|
233
|
+
setUserAgent(userAgent) {
|
|
234
|
+
const page = this.getSelectedPage();
|
|
235
|
+
if (userAgent === null) {
|
|
236
|
+
this.#userAgentMap.delete(page);
|
|
237
|
+
}
|
|
238
|
+
else {
|
|
239
|
+
this.#userAgentMap.set(page, userAgent);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
getUserAgent() {
|
|
243
|
+
const page = this.getSelectedPage();
|
|
244
|
+
return this.#userAgentMap.get(page) ?? null;
|
|
245
|
+
}
|
|
246
|
+
setColorScheme(scheme) {
|
|
247
|
+
const page = this.getSelectedPage();
|
|
248
|
+
if (scheme === null) {
|
|
249
|
+
this.#colorSchemeMap.delete(page);
|
|
250
|
+
}
|
|
251
|
+
else {
|
|
252
|
+
this.#colorSchemeMap.set(page, scheme);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
getColorScheme() {
|
|
256
|
+
const page = this.getSelectedPage();
|
|
257
|
+
return this.#colorSchemeMap.get(page) ?? null;
|
|
258
|
+
}
|
|
212
259
|
setIsRunningPerformanceTrace(x) {
|
|
213
260
|
this.#isRunningTrace = x;
|
|
214
261
|
}
|
|
215
262
|
isRunningPerformanceTrace() {
|
|
216
263
|
return this.#isRunningTrace;
|
|
217
264
|
}
|
|
265
|
+
isCruxEnabled() {
|
|
266
|
+
return this.#options.performanceCrux;
|
|
267
|
+
}
|
|
218
268
|
getDialog() {
|
|
219
269
|
return this.#dialog;
|
|
220
270
|
}
|
|
@@ -231,14 +281,16 @@ export class McpContext {
|
|
|
231
281
|
}
|
|
232
282
|
return page;
|
|
233
283
|
}
|
|
234
|
-
|
|
235
|
-
const
|
|
236
|
-
const page = pages[idx];
|
|
284
|
+
getPageById(pageId) {
|
|
285
|
+
const page = this.#pages.find(p => this.#pageIdMap.get(p) === pageId);
|
|
237
286
|
if (!page) {
|
|
238
287
|
throw new Error('No page found');
|
|
239
288
|
}
|
|
240
289
|
return page;
|
|
241
290
|
}
|
|
291
|
+
getPageId(page) {
|
|
292
|
+
return this.#pageIdMap.get(page);
|
|
293
|
+
}
|
|
242
294
|
#dialogHandler = (dialog) => {
|
|
243
295
|
this.#dialog = dialog;
|
|
244
296
|
};
|
|
@@ -283,25 +335,34 @@ export class McpContext {
|
|
|
283
335
|
if (!this.#textSnapshot?.idToNode.size) {
|
|
284
336
|
throw new Error(`No snapshot found. Use ${takeSnapshot.name} to capture one.`);
|
|
285
337
|
}
|
|
286
|
-
const [snapshotId] = uid.split('_');
|
|
287
|
-
if (this.#textSnapshot.snapshotId !== snapshotId) {
|
|
288
|
-
throw new Error('This uid is coming from a stale snapshot. Call take_snapshot to get a fresh snapshot.');
|
|
289
|
-
}
|
|
290
338
|
const node = this.#textSnapshot?.idToNode.get(uid);
|
|
291
339
|
if (!node) {
|
|
292
|
-
throw new Error('No such element found in the snapshot');
|
|
340
|
+
throw new Error('No such element found in the snapshot.');
|
|
293
341
|
}
|
|
294
|
-
const
|
|
295
|
-
|
|
296
|
-
|
|
342
|
+
const message = `Element with uid ${uid} no longer exists on the page.`;
|
|
343
|
+
try {
|
|
344
|
+
const handle = await node.elementHandle();
|
|
345
|
+
if (!handle) {
|
|
346
|
+
throw new Error(message);
|
|
347
|
+
}
|
|
348
|
+
return handle;
|
|
349
|
+
}
|
|
350
|
+
catch (error) {
|
|
351
|
+
throw new Error(message, {
|
|
352
|
+
cause: error,
|
|
353
|
+
});
|
|
297
354
|
}
|
|
298
|
-
return handle;
|
|
299
355
|
}
|
|
300
356
|
/**
|
|
301
357
|
* Creates a snapshot of the pages.
|
|
302
358
|
*/
|
|
303
359
|
async createPagesSnapshot() {
|
|
304
360
|
const allPages = await this.browser.pages(this.#options.experimentalIncludeAllPages);
|
|
361
|
+
for (const page of allPages) {
|
|
362
|
+
if (!this.#pageIdMap.has(page)) {
|
|
363
|
+
this.#pageIdMap.set(page, this.#nextPageId++);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
305
366
|
this.#pages = allPages.filter(page => {
|
|
306
367
|
// If we allow debugging DevTools windows, return all pages.
|
|
307
368
|
// If we are in regular mode, the user should only see non-DevTools page.
|
|
@@ -396,10 +457,24 @@ export class McpContext {
|
|
|
396
457
|
// will be used for the tree serialization and mapping ids back to nodes.
|
|
397
458
|
let idCounter = 0;
|
|
398
459
|
const idToNode = new Map();
|
|
460
|
+
const seenUniqueIds = new Set();
|
|
399
461
|
const assignIds = (node) => {
|
|
462
|
+
let id = '';
|
|
463
|
+
// @ts-expect-error untyped loaderId & backendNodeId.
|
|
464
|
+
const uniqueBackendId = `${node.loaderId}_${node.backendNodeId}`;
|
|
465
|
+
if (this.#uniqueBackendNodeIdToMcpId.has(uniqueBackendId)) {
|
|
466
|
+
// Re-use MCP exposed ID if the uniqueId is the same.
|
|
467
|
+
id = this.#uniqueBackendNodeIdToMcpId.get(uniqueBackendId);
|
|
468
|
+
}
|
|
469
|
+
else {
|
|
470
|
+
// Only generate a new ID if we have not seen the node before.
|
|
471
|
+
id = `${snapshotId}_${idCounter++}`;
|
|
472
|
+
this.#uniqueBackendNodeIdToMcpId.set(uniqueBackendId, id);
|
|
473
|
+
}
|
|
474
|
+
seenUniqueIds.add(uniqueBackendId);
|
|
400
475
|
const nodeWithId = {
|
|
401
476
|
...node,
|
|
402
|
-
id
|
|
477
|
+
id,
|
|
403
478
|
children: node.children
|
|
404
479
|
? node.children.map(child => assignIds(child))
|
|
405
480
|
: [],
|
|
@@ -428,6 +503,12 @@ export class McpContext {
|
|
|
428
503
|
this.#textSnapshot.hasSelectedElement = true;
|
|
429
504
|
this.#textSnapshot.selectedElementUid = this.resolveCdpElementId(data?.cdpBackendNodeId);
|
|
430
505
|
}
|
|
506
|
+
// Clean up unique IDs that we did not see anymore.
|
|
507
|
+
for (const key of this.#uniqueBackendNodeIdToMcpId.keys()) {
|
|
508
|
+
if (!seenUniqueIds.has(key)) {
|
|
509
|
+
this.#uniqueBackendNodeIdToMcpId.delete(key);
|
|
510
|
+
}
|
|
511
|
+
}
|
|
431
512
|
}
|
|
432
513
|
getTextSnapshot() {
|
|
433
514
|
return this.#textSnapshot;
|
|
@@ -456,6 +537,8 @@ export class McpContext {
|
|
|
456
537
|
}
|
|
457
538
|
}
|
|
458
539
|
storeTraceRecording(result) {
|
|
540
|
+
// Clear the trace results because we only consume the latest trace currently.
|
|
541
|
+
this.#traceResults = [];
|
|
459
542
|
this.#traceResults.push(result);
|
|
460
543
|
}
|
|
461
544
|
recordedTraces() {
|
|
@@ -464,12 +547,12 @@ export class McpContext {
|
|
|
464
547
|
getWaitForHelper(page, cpuMultiplier, networkMultiplier) {
|
|
465
548
|
return new WaitForHelper(page, cpuMultiplier, networkMultiplier);
|
|
466
549
|
}
|
|
467
|
-
waitForEventsAfterAction(action) {
|
|
550
|
+
waitForEventsAfterAction(action, options) {
|
|
468
551
|
const page = this.getSelectedPage();
|
|
469
552
|
const cpuMultiplier = this.getCpuThrottlingRate();
|
|
470
553
|
const networkMultiplier = getNetworkMultiplierFromString(this.getNetworkConditions());
|
|
471
554
|
const waitForHelper = this.getWaitForHelper(page, cpuMultiplier, networkMultiplier);
|
|
472
|
-
return waitForHelper.waitForEventsAfterAction(action);
|
|
555
|
+
return waitForHelper.waitForEventsAfterAction(action, options);
|
|
473
556
|
}
|
|
474
557
|
getNetworkRequestStableId(request) {
|
|
475
558
|
return this.#networkCollector.getIdForResource(request);
|
|
@@ -502,4 +585,19 @@ export class McpContext {
|
|
|
502
585
|
});
|
|
503
586
|
await this.#networkCollector.init(await this.browser.pages());
|
|
504
587
|
}
|
|
588
|
+
async installExtension(extensionPath) {
|
|
589
|
+
const id = await this.browser.installExtension(extensionPath);
|
|
590
|
+
await this.#extensionRegistry.registerExtension(id, extensionPath);
|
|
591
|
+
return id;
|
|
592
|
+
}
|
|
593
|
+
async uninstallExtension(id) {
|
|
594
|
+
await this.browser.uninstallExtension(id);
|
|
595
|
+
this.#extensionRegistry.remove(id);
|
|
596
|
+
}
|
|
597
|
+
listExtensions() {
|
|
598
|
+
return this.#extensionRegistry.list();
|
|
599
|
+
}
|
|
600
|
+
getExtension(id) {
|
|
601
|
+
return this.#extensionRegistry.getById(id);
|
|
602
|
+
}
|
|
505
603
|
}
|