chrome-devtools-mcp 0.21.0 → 0.22.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 +83 -21
- package/build/src/HeapSnapshotManager.js +94 -0
- package/build/src/McpContext.js +26 -56
- package/build/src/McpPage.js +16 -0
- package/build/src/McpResponse.js +145 -11
- package/build/src/PageCollector.js +10 -24
- package/build/src/WaitForHelper.js +31 -0
- package/build/src/bin/check-latest-version.js +25 -0
- package/build/src/bin/chrome-devtools-mcp-cli-options.js +24 -10
- package/build/src/bin/chrome-devtools-mcp-main.js +2 -0
- package/build/src/bin/chrome-devtools.js +3 -0
- package/build/src/bin/cliDefinitions.js +14 -8
- package/build/src/daemon/client.js +1 -1
- package/build/src/daemon/daemon.js +0 -4
- package/build/src/formatters/HeapSnapshotFormatter.js +38 -0
- package/build/src/formatters/NetworkFormatter.js +24 -7
- package/build/src/index.js +12 -1
- package/build/src/telemetry/ClearcutLogger.js +34 -12
- package/build/src/telemetry/flagUtils.js +46 -4
- package/build/src/telemetry/toolMetricsUtils.js +88 -0
- package/build/src/telemetry/watchdog/ClearcutSender.js +4 -3
- package/build/src/third_party/THIRD_PARTY_NOTICES +32 -32
- package/build/src/third_party/bundled-packages.json +5 -4
- package/build/src/third_party/devtools-formatter-worker.js +61 -64
- package/build/src/third_party/devtools-heap-snapshot-worker.js +9690 -0
- package/build/src/third_party/index.js +61443 -59378
- package/build/src/third_party/lighthouse-devtools-mcp-bundle.js +3501 -2658
- package/build/src/tools/categories.js +3 -0
- package/build/src/tools/console.js +42 -39
- package/build/src/tools/emulation.js +1 -1
- package/build/src/tools/extensions.js +5 -11
- package/build/src/tools/inPage.js +27 -6
- package/build/src/tools/input.js +15 -16
- package/build/src/tools/lighthouse.js +2 -2
- package/build/src/tools/memory.js +48 -3
- package/build/src/tools/network.js +2 -2
- package/build/src/tools/pages.js +8 -5
- package/build/src/tools/performance.js +1 -1
- package/build/src/tools/screencast.js +2 -1
- package/build/src/tools/screenshot.js +3 -3
- package/build/src/tools/script.js +22 -16
- package/build/src/tools/tools.js +2 -0
- package/build/src/tools/webmcp.js +63 -0
- package/build/src/utils/check-for-updates.js +73 -0
- package/build/src/utils/files.js +4 -0
- package/build/src/utils/id.js +15 -0
- package/build/src/version.js +1 -1
- package/package.json +12 -8
- package/build/src/utils/ExtensionRegistry.js +0 -35
package/build/src/McpResponse.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
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';
|
|
7
8
|
import { IssueFormatter } from './formatters/IssueFormatter.js';
|
|
8
9
|
import { NetworkFormatter } from './formatters/NetworkFormatter.js';
|
|
9
10
|
import { SnapshotFormatter } from './formatters/SnapshotFormatter.js';
|
|
@@ -12,6 +13,52 @@ import { DevTools } from './third_party/index.js';
|
|
|
12
13
|
import { handleDialog } from './tools/pages.js';
|
|
13
14
|
import { getInsightOutput, getTraceSummary } from './trace-processing/parse.js';
|
|
14
15
|
import { paginate } from './utils/pagination.js';
|
|
16
|
+
export function replaceHtmlElementsWithUids(schema) {
|
|
17
|
+
if (typeof schema === 'boolean') {
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
let isHtmlElement = false;
|
|
21
|
+
for (const [key, value] of Object.entries(schema)) {
|
|
22
|
+
if (key === 'x-mcp-type' && value === 'HTMLElement') {
|
|
23
|
+
isHtmlElement = true;
|
|
24
|
+
break;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
if (isHtmlElement) {
|
|
28
|
+
schema.properties = { uid: { type: 'string' } };
|
|
29
|
+
schema.required = ['uid'];
|
|
30
|
+
}
|
|
31
|
+
if (schema.properties) {
|
|
32
|
+
for (const key of Object.keys(schema.properties)) {
|
|
33
|
+
replaceHtmlElementsWithUids(schema.properties[key]);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
if (schema.items) {
|
|
37
|
+
if (Array.isArray(schema.items)) {
|
|
38
|
+
for (const item of schema.items) {
|
|
39
|
+
replaceHtmlElementsWithUids(item);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
replaceHtmlElementsWithUids(schema.items);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
if (schema.anyOf) {
|
|
47
|
+
for (const s of schema.anyOf) {
|
|
48
|
+
replaceHtmlElementsWithUids(s);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
if (schema.allOf) {
|
|
52
|
+
for (const s of schema.allOf) {
|
|
53
|
+
replaceHtmlElementsWithUids(s);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
if (schema.oneOf) {
|
|
57
|
+
for (const s of schema.oneOf) {
|
|
58
|
+
replaceHtmlElementsWithUids(s);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
15
62
|
async function getToolGroup(page) {
|
|
16
63
|
// Check if there is a `devtoolstooldiscovery` event listener
|
|
17
64
|
const windowHandle = await page.pptrPage.evaluateHandle(() => window);
|
|
@@ -54,6 +101,9 @@ async function getToolGroup(page) {
|
|
|
54
101
|
}, 0);
|
|
55
102
|
});
|
|
56
103
|
});
|
|
104
|
+
for (const tool of toolGroup?.tools ?? []) {
|
|
105
|
+
replaceHtmlElementsWithUids(tool.inputSchema);
|
|
106
|
+
}
|
|
57
107
|
return toolGroup;
|
|
58
108
|
}
|
|
59
109
|
export class McpResponse {
|
|
@@ -69,20 +119,26 @@ export class McpResponse {
|
|
|
69
119
|
#attachedLighthouseResult;
|
|
70
120
|
#textResponseLines = [];
|
|
71
121
|
#images = [];
|
|
122
|
+
#heapSnapshotOptions;
|
|
72
123
|
#networkRequestsOptions;
|
|
73
124
|
#consoleDataOptions;
|
|
74
125
|
#listExtensions;
|
|
75
126
|
#listInPageTools;
|
|
127
|
+
#listWebMcpTools;
|
|
76
128
|
#devToolsData;
|
|
77
129
|
#tabId;
|
|
78
130
|
#args;
|
|
79
131
|
#page;
|
|
132
|
+
#redactNetworkHeaders = true;
|
|
80
133
|
constructor(args) {
|
|
81
134
|
this.#args = args;
|
|
82
135
|
}
|
|
83
136
|
setPage(page) {
|
|
84
137
|
this.#page = page;
|
|
85
138
|
}
|
|
139
|
+
setRedactNetworkHeaders(value) {
|
|
140
|
+
this.#redactNetworkHeaders = value;
|
|
141
|
+
}
|
|
86
142
|
attachDevToolsData(data) {
|
|
87
143
|
this.#devToolsData = data;
|
|
88
144
|
}
|
|
@@ -109,6 +165,9 @@ export class McpResponse {
|
|
|
109
165
|
this.#listInPageTools = true;
|
|
110
166
|
}
|
|
111
167
|
}
|
|
168
|
+
setListWebMcpTools() {
|
|
169
|
+
this.#listWebMcpTools = true;
|
|
170
|
+
}
|
|
112
171
|
setIncludeNetworkRequests(value, options) {
|
|
113
172
|
if (!value) {
|
|
114
173
|
this.#networkRequestsOptions = undefined;
|
|
@@ -197,6 +256,22 @@ export class McpResponse {
|
|
|
197
256
|
appendResponseLine(value) {
|
|
198
257
|
this.#textResponseLines.push(value);
|
|
199
258
|
}
|
|
259
|
+
setHeapSnapshotAggregates(aggregates, options) {
|
|
260
|
+
this.#heapSnapshotOptions = {
|
|
261
|
+
...this.#heapSnapshotOptions,
|
|
262
|
+
include: true,
|
|
263
|
+
aggregates,
|
|
264
|
+
pagination: options,
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
setHeapSnapshotStats(stats, staticData) {
|
|
268
|
+
this.#heapSnapshotOptions = {
|
|
269
|
+
...this.#heapSnapshotOptions,
|
|
270
|
+
include: true,
|
|
271
|
+
stats,
|
|
272
|
+
staticData,
|
|
273
|
+
};
|
|
274
|
+
}
|
|
200
275
|
attachImage(value) {
|
|
201
276
|
this.#images.push(value);
|
|
202
277
|
}
|
|
@@ -209,6 +284,9 @@ export class McpResponse {
|
|
|
209
284
|
get snapshotParams() {
|
|
210
285
|
return this.#snapshotParams;
|
|
211
286
|
}
|
|
287
|
+
get listWebMcpTools() {
|
|
288
|
+
return this.#listWebMcpTools;
|
|
289
|
+
}
|
|
212
290
|
async handle(toolName, context) {
|
|
213
291
|
if (this.#includePages) {
|
|
214
292
|
await context.createPagesSnapshot();
|
|
@@ -226,8 +304,8 @@ export class McpResponse {
|
|
|
226
304
|
if (textSnapshot) {
|
|
227
305
|
const formatter = new SnapshotFormatter(textSnapshot);
|
|
228
306
|
if (this.#snapshotParams.filePath) {
|
|
229
|
-
await context.saveFile(new TextEncoder().encode(formatter.toString()), this.#snapshotParams.filePath);
|
|
230
|
-
snapshot =
|
|
307
|
+
const result = await context.saveFile(new TextEncoder().encode(formatter.toString()), this.#snapshotParams.filePath, '.txt');
|
|
308
|
+
snapshot = result.filename;
|
|
231
309
|
}
|
|
232
310
|
else {
|
|
233
311
|
snapshot = formatter;
|
|
@@ -246,7 +324,8 @@ export class McpResponse {
|
|
|
246
324
|
fetchData: true,
|
|
247
325
|
requestFilePath: this.#attachedNetworkRequestOptions?.requestFilePath,
|
|
248
326
|
responseFilePath: this.#attachedNetworkRequestOptions?.responseFilePath,
|
|
249
|
-
saveFile: (data, filename) => context.saveFile(data, filename),
|
|
327
|
+
saveFile: (data, filename, extension) => context.saveFile(data, filename, extension),
|
|
328
|
+
redactNetworkHeaders: this.#redactNetworkHeaders,
|
|
250
329
|
});
|
|
251
330
|
detailedNetworkRequest = formatter;
|
|
252
331
|
}
|
|
@@ -280,12 +359,18 @@ export class McpResponse {
|
|
|
280
359
|
}
|
|
281
360
|
let extensions;
|
|
282
361
|
if (this.#listExtensions) {
|
|
283
|
-
extensions = context.listExtensions();
|
|
362
|
+
extensions = await context.listExtensions();
|
|
284
363
|
}
|
|
285
364
|
let inPageTools;
|
|
286
365
|
if (this.#listInPageTools) {
|
|
287
|
-
|
|
288
|
-
|
|
366
|
+
const page = this.#page ?? context.getSelectedMcpPage();
|
|
367
|
+
inPageTools = await getToolGroup(page);
|
|
368
|
+
page.inPageTools = inPageTools;
|
|
369
|
+
}
|
|
370
|
+
let webmcpTools;
|
|
371
|
+
if (this.#listWebMcpTools && this.#args.experimentalWebmcp) {
|
|
372
|
+
const page = this.#page ?? context.getSelectedMcpPage();
|
|
373
|
+
webmcpTools = page.getWebMcpTools();
|
|
289
374
|
}
|
|
290
375
|
let consoleMessages;
|
|
291
376
|
if (this.#consoleDataOptions?.include) {
|
|
@@ -349,7 +434,8 @@ export class McpResponse {
|
|
|
349
434
|
selectedInDevToolsUI: context.getNetworkRequestStableId(request) ===
|
|
350
435
|
this.#networkRequestsOptions?.networkRequestIdInDevToolsUI,
|
|
351
436
|
fetchData: false,
|
|
352
|
-
saveFile: (data, filename) => context.saveFile(data, filename),
|
|
437
|
+
saveFile: (data, filename, extension) => context.saveFile(data, filename, extension),
|
|
438
|
+
redactNetworkHeaders: this.#redactNetworkHeaders,
|
|
353
439
|
})));
|
|
354
440
|
}
|
|
355
441
|
}
|
|
@@ -364,6 +450,7 @@ export class McpResponse {
|
|
|
364
450
|
extensions,
|
|
365
451
|
lighthouseResult: this.#attachedLighthouseResult,
|
|
366
452
|
inPageTools,
|
|
453
|
+
webmcpTools,
|
|
367
454
|
});
|
|
368
455
|
}
|
|
369
456
|
format(toolName, context, data) {
|
|
@@ -529,6 +616,32 @@ Call ${handleDialog.name} to handle it before continuing.`);
|
|
|
529
616
|
structuredContent.snapshot = data.snapshot.toJSON();
|
|
530
617
|
}
|
|
531
618
|
}
|
|
619
|
+
if (this.#heapSnapshotOptions?.include) {
|
|
620
|
+
response.push('## Heap Snapshot Data');
|
|
621
|
+
const stats = this.#heapSnapshotOptions.stats;
|
|
622
|
+
const staticData = this.#heapSnapshotOptions.staticData;
|
|
623
|
+
if (stats) {
|
|
624
|
+
response.push(`Statistics: ${JSON.stringify(stats, null, 2)}`);
|
|
625
|
+
structuredContent.heapSnapshot = structuredContent.heapSnapshot || {};
|
|
626
|
+
structuredContent.heapSnapshot.stats = stats;
|
|
627
|
+
}
|
|
628
|
+
if (staticData) {
|
|
629
|
+
response.push(`Static Data: ${JSON.stringify(staticData, null, 2)}`);
|
|
630
|
+
structuredContent.heapSnapshot = structuredContent.heapSnapshot || {};
|
|
631
|
+
structuredContent.heapSnapshot.staticData = staticData;
|
|
632
|
+
}
|
|
633
|
+
const aggregates = this.#heapSnapshotOptions.aggregates;
|
|
634
|
+
if (aggregates) {
|
|
635
|
+
const sortedEntries = HeapSnapshotFormatter.sort(aggregates);
|
|
636
|
+
const paginationData = this.#dataWithPagination(sortedEntries, this.#heapSnapshotOptions.pagination);
|
|
637
|
+
structuredContent.pagination = paginationData.pagination;
|
|
638
|
+
response.push(...paginationData.info);
|
|
639
|
+
const paginatedRecord = Object.fromEntries(paginationData.items);
|
|
640
|
+
const formatter = new HeapSnapshotFormatter(paginatedRecord);
|
|
641
|
+
response.push(formatter.toString());
|
|
642
|
+
structuredContent.heapSnapshotData = formatter.toJSON();
|
|
643
|
+
}
|
|
644
|
+
}
|
|
532
645
|
if (data.detailedNetworkRequest) {
|
|
533
646
|
response.push(data.detailedNetworkRequest.toStringDetailed());
|
|
534
647
|
structuredContent.networkRequest =
|
|
@@ -540,15 +653,16 @@ Call ${handleDialog.name} to handle it before continuing.`);
|
|
|
540
653
|
data.detailedConsoleMessage.toJSONDetailed();
|
|
541
654
|
}
|
|
542
655
|
if (data.extensions) {
|
|
543
|
-
|
|
656
|
+
const extensionArray = Array.from(data.extensions.values());
|
|
657
|
+
structuredContent.extensions = extensionArray;
|
|
544
658
|
response.push('## Extensions');
|
|
545
|
-
if (
|
|
659
|
+
if (extensionArray.length === 0) {
|
|
546
660
|
response.push('No extensions installed.');
|
|
547
661
|
}
|
|
548
662
|
else {
|
|
549
|
-
const extensionsMessage =
|
|
663
|
+
const extensionsMessage = extensionArray
|
|
550
664
|
.map(extension => {
|
|
551
|
-
return `id=${extension.id} "${extension.name}" v${extension.version} ${extension.
|
|
665
|
+
return `id=${extension.id} "${extension.name}" v${extension.version} ${extension.enabled ? 'Enabled' : 'Disabled'}`;
|
|
552
666
|
})
|
|
553
667
|
.join('\n');
|
|
554
668
|
response.push(extensionsMessage);
|
|
@@ -572,6 +686,26 @@ Call ${handleDialog.name} to handle it before continuing.`);
|
|
|
572
686
|
response.push(toolDefinitionsMessage);
|
|
573
687
|
}
|
|
574
688
|
}
|
|
689
|
+
if (this.#listWebMcpTools && data.webmcpTools) {
|
|
690
|
+
structuredContent.webmcpTools = data.webmcpTools.map(({ name, description, inputSchema, annotations }) => ({
|
|
691
|
+
name,
|
|
692
|
+
description,
|
|
693
|
+
inputSchema,
|
|
694
|
+
annotations,
|
|
695
|
+
}));
|
|
696
|
+
response.push('## WebMCP tools');
|
|
697
|
+
if (data.webmcpTools.length === 0) {
|
|
698
|
+
response.push('No WebMCP tools available.');
|
|
699
|
+
}
|
|
700
|
+
else {
|
|
701
|
+
const webmcpToolsMessage = data.webmcpTools
|
|
702
|
+
.map(tool => {
|
|
703
|
+
return `name="${tool.name}", description="${tool.description}", inputSchema=${JSON.stringify(tool.inputSchema)}, annotations=${JSON.stringify(tool.annotations)}`;
|
|
704
|
+
})
|
|
705
|
+
.join('\n');
|
|
706
|
+
response.push(webmcpToolsMessage);
|
|
707
|
+
}
|
|
708
|
+
}
|
|
575
709
|
if (this.#networkRequestsOptions?.include && data.networkRequests) {
|
|
576
710
|
const requests = data.networkRequests;
|
|
577
711
|
response.push('## Network requests');
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
import { FakeIssuesManager } from './DevtoolsUtils.js';
|
|
7
7
|
import { logger } from './logger.js';
|
|
8
8
|
import { DevTools } from './third_party/index.js';
|
|
9
|
+
import { createIdGenerator, stableIdSymbol, } from './utils/id.js';
|
|
9
10
|
export class UncaughtError {
|
|
10
11
|
details;
|
|
11
12
|
targetId;
|
|
@@ -14,16 +15,6 @@ export class UncaughtError {
|
|
|
14
15
|
this.targetId = targetId;
|
|
15
16
|
}
|
|
16
17
|
}
|
|
17
|
-
function createIdGenerator() {
|
|
18
|
-
let i = 1;
|
|
19
|
-
return () => {
|
|
20
|
-
if (i === Number.MAX_SAFE_INTEGER) {
|
|
21
|
-
i = 0;
|
|
22
|
-
}
|
|
23
|
-
return i++;
|
|
24
|
-
};
|
|
25
|
-
}
|
|
26
|
-
export const stableIdSymbol = Symbol('stableIdSymbol');
|
|
27
18
|
export class PageCollector {
|
|
28
19
|
#browser;
|
|
29
20
|
#listenersInitializer;
|
|
@@ -206,34 +197,25 @@ class PageEventSubscriber {
|
|
|
206
197
|
async subscribe() {
|
|
207
198
|
this.#resetIssueAggregator();
|
|
208
199
|
this.#page.on('framenavigated', this.#onFrameNavigated);
|
|
209
|
-
this.#
|
|
200
|
+
this.#page.on('issue', this.#onIssueAdded);
|
|
210
201
|
this.#session.on('Runtime.exceptionThrown', this.#onExceptionThrown);
|
|
211
|
-
try {
|
|
212
|
-
await this.#session.send('Audits.enable');
|
|
213
|
-
}
|
|
214
|
-
catch (error) {
|
|
215
|
-
logger('Error subscribing to issues', error);
|
|
216
|
-
}
|
|
217
202
|
}
|
|
218
203
|
unsubscribe() {
|
|
219
204
|
this.#seenKeys.clear();
|
|
220
205
|
this.#seenIssues.clear();
|
|
221
206
|
this.#page.off('framenavigated', this.#onFrameNavigated);
|
|
222
|
-
this.#
|
|
207
|
+
this.#page.off('issue', this.#onIssueAdded);
|
|
223
208
|
this.#session.off('Runtime.exceptionThrown', this.#onExceptionThrown);
|
|
224
209
|
if (this.#issueAggregator) {
|
|
225
210
|
this.#issueAggregator.removeEventListener("AggregatedIssueUpdated" /* DevTools.IssueAggregatorEvents.AGGREGATED_ISSUE_UPDATED */, this.#onAggregatedIssue);
|
|
226
211
|
}
|
|
227
|
-
void this.#session.send('Audits.disable').catch(() => {
|
|
228
|
-
// might fail.
|
|
229
|
-
});
|
|
230
212
|
}
|
|
231
213
|
#onAggregatedIssue = (event) => {
|
|
232
214
|
if (this.#seenIssues.has(event.data)) {
|
|
233
215
|
return;
|
|
234
216
|
}
|
|
235
217
|
this.#seenIssues.add(event.data);
|
|
236
|
-
this.#page.emit('
|
|
218
|
+
this.#page.emit('devtoolsAggregatedIssue', event.data);
|
|
237
219
|
};
|
|
238
220
|
#onExceptionThrown = (event) => {
|
|
239
221
|
this.#page.emit('uncaughtError', new UncaughtError(event.exceptionDetails, this.#targetId));
|
|
@@ -248,9 +230,13 @@ class PageEventSubscriber {
|
|
|
248
230
|
this.#seenIssues.clear();
|
|
249
231
|
this.#resetIssueAggregator();
|
|
250
232
|
};
|
|
251
|
-
#onIssueAdded = (
|
|
233
|
+
#onIssueAdded = (inspectorIssue) => {
|
|
252
234
|
try {
|
|
253
|
-
|
|
235
|
+
// DevTools currently defines this protocol issue code but has no
|
|
236
|
+
// IssuesManager handler for it, so calling into the mapper only warns.
|
|
237
|
+
if (String(inspectorIssue.code) === 'PerformanceIssue') {
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
254
240
|
const issue = DevTools.createIssuesFromProtocolIssue(null,
|
|
255
241
|
// @ts-expect-error Protocol types diverge.
|
|
256
242
|
inspectorIssue)[0];
|
|
@@ -104,6 +104,23 @@ export class WaitForHelper {
|
|
|
104
104
|
});
|
|
105
105
|
}
|
|
106
106
|
async waitForEventsAfterAction(action, options) {
|
|
107
|
+
if (options?.handleDialog) {
|
|
108
|
+
const dialogHandler = (dialog) => {
|
|
109
|
+
if (options.handleDialog === 'dismiss') {
|
|
110
|
+
void dialog.dismiss();
|
|
111
|
+
}
|
|
112
|
+
else if (options.handleDialog === 'accept') {
|
|
113
|
+
void dialog.accept();
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
void dialog.accept(options.handleDialog);
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
this.#page.on('dialog', dialogHandler);
|
|
120
|
+
this.#abortController.signal.addEventListener('abort', () => {
|
|
121
|
+
this.#page.off('dialog', dialogHandler);
|
|
122
|
+
});
|
|
123
|
+
}
|
|
107
124
|
const navigationFinished = this.waitForNavigationStarted()
|
|
108
125
|
.then(navigationStated => {
|
|
109
126
|
if (navigationStated) {
|
|
@@ -137,3 +154,17 @@ export class WaitForHelper {
|
|
|
137
154
|
}
|
|
138
155
|
}
|
|
139
156
|
}
|
|
157
|
+
export function getNetworkMultiplierFromString(condition) {
|
|
158
|
+
const puppeteerCondition = condition;
|
|
159
|
+
switch (puppeteerCondition) {
|
|
160
|
+
case 'Fast 4G':
|
|
161
|
+
return 1;
|
|
162
|
+
case 'Slow 4G':
|
|
163
|
+
return 2.5;
|
|
164
|
+
case 'Fast 3G':
|
|
165
|
+
return 5;
|
|
166
|
+
case 'Slow 3G':
|
|
167
|
+
return 10;
|
|
168
|
+
}
|
|
169
|
+
return 1;
|
|
170
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Google LLC
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
import fs from 'node:fs/promises';
|
|
7
|
+
import path from 'node:path';
|
|
8
|
+
import process from 'node:process';
|
|
9
|
+
const cachePath = process.argv[2];
|
|
10
|
+
if (cachePath) {
|
|
11
|
+
try {
|
|
12
|
+
const response = await fetch('https://registry.npmjs.org/chrome-devtools-mcp/latest');
|
|
13
|
+
const data = response.ok ? await response.json() : null;
|
|
14
|
+
if (data &&
|
|
15
|
+
typeof data === 'object' &&
|
|
16
|
+
'version' in data &&
|
|
17
|
+
typeof data.version === 'string') {
|
|
18
|
+
await fs.mkdir(path.dirname(cachePath), { recursive: true });
|
|
19
|
+
await fs.writeFile(cachePath, JSON.stringify({ version: data.version }));
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
// Ignore errors.
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -7,8 +7,8 @@ import { yargs, hideBin } from '../third_party/index.js';
|
|
|
7
7
|
export const cliOptions = {
|
|
8
8
|
autoConnect: {
|
|
9
9
|
type: 'boolean',
|
|
10
|
-
description: 'If specified, automatically connects to a browser (Chrome 144+) running locally from the user data directory identified by the channel param (default channel is stable). Requires the
|
|
11
|
-
conflicts: ['isolated', 'executablePath'
|
|
10
|
+
description: 'If specified, automatically connects to a browser (Chrome 144+) running locally from the user data directory identified by the channel param (default channel is stable). Requires the remote debugging server to be started in the Chrome instance via chrome://inspect/#remote-debugging.',
|
|
11
|
+
conflicts: ['isolated', 'executablePath'],
|
|
12
12
|
default: false,
|
|
13
13
|
coerce: (value) => {
|
|
14
14
|
if (!value) {
|
|
@@ -21,7 +21,7 @@ export const cliOptions = {
|
|
|
21
21
|
type: 'string',
|
|
22
22
|
description: 'Connect to a running, debuggable Chrome instance (e.g. `http://127.0.0.1:9222`). For more details see: https://github.com/ChromeDevTools/chrome-devtools-mcp#connecting-to-a-running-chrome-instance.',
|
|
23
23
|
alias: 'u',
|
|
24
|
-
conflicts: ['wsEndpoint'
|
|
24
|
+
conflicts: ['wsEndpoint'],
|
|
25
25
|
coerce: (url) => {
|
|
26
26
|
if (!url) {
|
|
27
27
|
return;
|
|
@@ -39,7 +39,7 @@ export const cliOptions = {
|
|
|
39
39
|
type: 'string',
|
|
40
40
|
description: 'WebSocket endpoint to connect to a running Chrome instance (e.g., ws://127.0.0.1:9222/devtools/browser/<id>). Alternative to --browserUrl.',
|
|
41
41
|
alias: 'w',
|
|
42
|
-
conflicts: ['browserUrl'
|
|
42
|
+
conflicts: ['browserUrl'],
|
|
43
43
|
coerce: (url) => {
|
|
44
44
|
if (!url) {
|
|
45
45
|
return;
|
|
@@ -102,7 +102,7 @@ export const cliOptions = {
|
|
|
102
102
|
channel: {
|
|
103
103
|
type: 'string',
|
|
104
104
|
description: 'Specify a different Chrome channel that should be used. The default is the stable channel version.',
|
|
105
|
-
choices: ['
|
|
105
|
+
choices: ['canary', 'dev', 'beta', 'stable'],
|
|
106
106
|
conflicts: ['browserUrl', 'wsEndpoint', 'executablePath'],
|
|
107
107
|
},
|
|
108
108
|
logFile: {
|
|
@@ -146,7 +146,12 @@ export const cliOptions = {
|
|
|
146
146
|
},
|
|
147
147
|
experimentalVision: {
|
|
148
148
|
type: 'boolean',
|
|
149
|
-
describe: 'Whether to enable
|
|
149
|
+
describe: '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.',
|
|
150
|
+
hidden: false,
|
|
151
|
+
},
|
|
152
|
+
experimentalMemory: {
|
|
153
|
+
type: 'boolean',
|
|
154
|
+
describe: 'Whether to enable experimental memory tools.',
|
|
150
155
|
hidden: true,
|
|
151
156
|
},
|
|
152
157
|
experimentalStructuredContent: {
|
|
@@ -168,6 +173,10 @@ export const cliOptions = {
|
|
|
168
173
|
type: 'boolean',
|
|
169
174
|
describe: 'Exposes experimental screencast tools (requires ffmpeg). Install ffmpeg https://www.ffmpeg.org/download.html and ensure it is available in the MCP server PATH.',
|
|
170
175
|
},
|
|
176
|
+
experimentalWebmcp: {
|
|
177
|
+
type: 'boolean',
|
|
178
|
+
describe: 'Set to true to enable debugging WebMCP tools. Requires Chrome 149+ with the following flags: `--enable-features=WebMCPTesting,DevToolsWebMCPSupport`',
|
|
179
|
+
},
|
|
171
180
|
chromeArg: {
|
|
172
181
|
type: 'array',
|
|
173
182
|
describe: 'Additional arguments for Chrome. Only applies when Chrome is launched by chrome-devtools-mcp.',
|
|
@@ -193,9 +202,9 @@ export const cliOptions = {
|
|
|
193
202
|
},
|
|
194
203
|
categoryExtensions: {
|
|
195
204
|
type: 'boolean',
|
|
196
|
-
hidden:
|
|
197
|
-
|
|
198
|
-
describe: 'Set to true to include tools related to extensions. Note: This feature is only supported with a pipe connection. autoConnect
|
|
205
|
+
hidden: false,
|
|
206
|
+
default: false,
|
|
207
|
+
describe: 'Set to true to include tools related to extensions. Note: This feature is currently only supported with a pipe connection. autoConnect, browserUrl, and wsEndpoint are not supported with this feature until 149 will be released.',
|
|
199
208
|
},
|
|
200
209
|
categoryInPageTools: {
|
|
201
210
|
type: 'boolean',
|
|
@@ -210,7 +219,7 @@ export const cliOptions = {
|
|
|
210
219
|
usageStatistics: {
|
|
211
220
|
type: 'boolean',
|
|
212
221
|
default: true,
|
|
213
|
-
describe: '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. Disabled if CHROME_DEVTOOLS_MCP_NO_USAGE_STATISTICS or CI env variables are set.',
|
|
222
|
+
describe: '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. Disabled if `CHROME_DEVTOOLS_MCP_NO_USAGE_STATISTICS` or `CI` env variables are set.',
|
|
214
223
|
},
|
|
215
224
|
clearcutEndpoint: {
|
|
216
225
|
type: 'string',
|
|
@@ -236,6 +245,11 @@ export const cliOptions = {
|
|
|
236
245
|
describe: 'Set by Chrome DevTools CLI if the MCP server is started via the CLI client (this arg exists for usage stats)',
|
|
237
246
|
hidden: true,
|
|
238
247
|
},
|
|
248
|
+
redactNetworkHeaders: {
|
|
249
|
+
type: 'boolean',
|
|
250
|
+
describe: 'If true, redacts some of the network headers considered senstive before returning to the client.',
|
|
251
|
+
default: false,
|
|
252
|
+
},
|
|
239
253
|
};
|
|
240
254
|
export function parseArguments(version, argv = process.argv) {
|
|
241
255
|
const yargsInstance = yargs(hideBin(argv))
|
|
@@ -9,8 +9,10 @@ import { createMcpServer, logDisclaimers } from '../index.js';
|
|
|
9
9
|
import { logger, saveLogsToFile } from '../logger.js';
|
|
10
10
|
import { computeFlagUsage } from '../telemetry/flagUtils.js';
|
|
11
11
|
import { StdioServerTransport } from '../third_party/index.js';
|
|
12
|
+
import { checkForUpdates } from '../utils/check-for-updates.js';
|
|
12
13
|
import { VERSION } from '../version.js';
|
|
13
14
|
import { cliOptions, parseArguments } from './chrome-devtools-mcp-cli-options.js';
|
|
15
|
+
await checkForUpdates('Run `npm install chrome-devtools-mcp@latest` to update.');
|
|
14
16
|
export const args = parseArguments(VERSION);
|
|
15
17
|
const logFile = args.logFile ? saveLogsToFile(args.logFile) : undefined;
|
|
16
18
|
if (process.env['CI'] ||
|
|
@@ -10,9 +10,11 @@ import { startDaemon, stopDaemon, sendCommand, handleResponse, } from '../daemon
|
|
|
10
10
|
import { isDaemonRunning, serializeArgs } from '../daemon/utils.js';
|
|
11
11
|
import { logDisclaimers } from '../index.js';
|
|
12
12
|
import { hideBin, yargs } from '../third_party/index.js';
|
|
13
|
+
import { checkForUpdates } from '../utils/check-for-updates.js';
|
|
13
14
|
import { VERSION } from '../version.js';
|
|
14
15
|
import { commands } from './chrome-devtools-cli-options.js';
|
|
15
16
|
import { cliOptions, parseArguments } from './chrome-devtools-mcp-cli-options.js';
|
|
17
|
+
await checkForUpdates('Run `npm install -g chrome-devtools-mcp@latest` and `chrome-devtools start` to update and restart the daemon.');
|
|
16
18
|
async function start(args) {
|
|
17
19
|
const combinedArgs = [...args, ...defaultArgs];
|
|
18
20
|
await startDaemon(combinedArgs);
|
|
@@ -30,6 +32,7 @@ delete startCliOptions.viewport;
|
|
|
30
32
|
// tools, they need to be enabled during CLI generation.
|
|
31
33
|
delete startCliOptions.experimentalPageIdRouting;
|
|
32
34
|
delete startCliOptions.experimentalVision;
|
|
35
|
+
delete startCliOptions.experimentalWebmcp;
|
|
33
36
|
delete startCliOptions.experimentalInteropTools;
|
|
34
37
|
delete startCliOptions.experimentalScreencast;
|
|
35
38
|
delete startCliOptions.categoryEmulation;
|
|
@@ -84,7 +84,7 @@ export const commands = {
|
|
|
84
84
|
geolocation: {
|
|
85
85
|
name: 'geolocation',
|
|
86
86
|
type: 'string',
|
|
87
|
-
description: 'Geolocation (`<latitude>x<longitude>`) to emulate. Latitude between -90 and 90. Longitude between -180 and 180. Omit clear the geolocation override.',
|
|
87
|
+
description: 'Geolocation (`<latitude>x<longitude>`) to emulate. Latitude between -90 and 90. Longitude between -180 and 180. Omit to clear the geolocation override.',
|
|
88
88
|
required: false,
|
|
89
89
|
},
|
|
90
90
|
userAgent: {
|
|
@@ -124,10 +124,16 @@ export const commands = {
|
|
|
124
124
|
description: 'An optional list of arguments to pass to the function.',
|
|
125
125
|
required: false,
|
|
126
126
|
},
|
|
127
|
+
dialogAction: {
|
|
128
|
+
name: 'dialogAction',
|
|
129
|
+
type: 'string',
|
|
130
|
+
description: 'Handle dialogs while execution. "accept", "dismiss", or string for response of window.prompt. Defaults to accept.',
|
|
131
|
+
required: false,
|
|
132
|
+
},
|
|
127
133
|
},
|
|
128
134
|
},
|
|
129
135
|
fill: {
|
|
130
|
-
description: 'Type text into
|
|
136
|
+
description: 'Type text into an input, text area or select an option from a <select> element.',
|
|
131
137
|
category: 'Input automation',
|
|
132
138
|
args: {
|
|
133
139
|
uid: {
|
|
@@ -175,13 +181,13 @@ export const commands = {
|
|
|
175
181
|
requestFilePath: {
|
|
176
182
|
name: 'requestFilePath',
|
|
177
183
|
type: 'string',
|
|
178
|
-
description: 'The absolute or relative path to save the request body to. If omitted, the body is returned inline.',
|
|
184
|
+
description: 'The absolute or relative path to a .network-request file to save the request body to. If omitted, the body is returned inline.',
|
|
179
185
|
required: false,
|
|
180
186
|
},
|
|
181
187
|
responseFilePath: {
|
|
182
188
|
name: 'responseFilePath',
|
|
183
189
|
type: 'string',
|
|
184
|
-
description: 'The absolute or relative path to save the response body to. If omitted, the body is returned inline.',
|
|
190
|
+
description: 'The absolute or relative path to a .network-response file to save the response body to. If omitted, the body is returned inline.',
|
|
185
191
|
required: false,
|
|
186
192
|
},
|
|
187
193
|
},
|
|
@@ -258,7 +264,7 @@ export const commands = {
|
|
|
258
264
|
pageSize: {
|
|
259
265
|
name: 'pageSize',
|
|
260
266
|
type: 'integer',
|
|
261
|
-
description: 'Maximum number of messages to return. When omitted, returns all
|
|
267
|
+
description: 'Maximum number of messages to return. When omitted, returns all messages.',
|
|
262
268
|
required: false,
|
|
263
269
|
},
|
|
264
270
|
pageIdx: {
|
|
@@ -314,7 +320,7 @@ export const commands = {
|
|
|
314
320
|
},
|
|
315
321
|
},
|
|
316
322
|
list_pages: {
|
|
317
|
-
description: 'Get a list of pages
|
|
323
|
+
description: 'Get a list of pages open in the browser.',
|
|
318
324
|
category: 'Navigation automation',
|
|
319
325
|
args: {},
|
|
320
326
|
},
|
|
@@ -504,7 +510,7 @@ export const commands = {
|
|
|
504
510
|
},
|
|
505
511
|
take_memory_snapshot: {
|
|
506
512
|
description: 'Capture a heap snapshot of the currently selected page. Use to analyze the memory distribution of JavaScript objects and debug memory leaks.',
|
|
507
|
-
category: '
|
|
513
|
+
category: 'Memory',
|
|
508
514
|
args: {
|
|
509
515
|
filePath: {
|
|
510
516
|
name: 'filePath',
|
|
@@ -535,7 +541,7 @@ export const commands = {
|
|
|
535
541
|
uid: {
|
|
536
542
|
name: 'uid',
|
|
537
543
|
type: 'string',
|
|
538
|
-
description: 'The uid of an element on the page from the page content snapshot. If omitted takes a
|
|
544
|
+
description: 'The uid of an element on the page from the page content snapshot. If omitted, takes a page screenshot.',
|
|
539
545
|
required: false,
|
|
540
546
|
},
|
|
541
547
|
fullPage: {
|
|
@@ -32,10 +32,6 @@ let server = null;
|
|
|
32
32
|
async function setupMCPClient() {
|
|
33
33
|
console.log('Setting up MCP client connection...');
|
|
34
34
|
// Create stdio transport for chrome-devtools-mcp
|
|
35
|
-
// Workaround for https://github.com/modelcontextprotocol/typescript-sdk/blob/v1.x/src/client/stdio.ts#L128
|
|
36
|
-
// which causes the console window to show on Windows.
|
|
37
|
-
// @ts-expect-error no types for type.
|
|
38
|
-
process.type = 'mcp-client';
|
|
39
35
|
mcpTransport = new StdioClientTransport({
|
|
40
36
|
command: process.execPath,
|
|
41
37
|
args: [INDEX_SCRIPT_PATH, ...mcpServerArgs],
|