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.
Files changed (49) hide show
  1. package/README.md +83 -21
  2. package/build/src/HeapSnapshotManager.js +94 -0
  3. package/build/src/McpContext.js +26 -56
  4. package/build/src/McpPage.js +16 -0
  5. package/build/src/McpResponse.js +145 -11
  6. package/build/src/PageCollector.js +10 -24
  7. package/build/src/WaitForHelper.js +31 -0
  8. package/build/src/bin/check-latest-version.js +25 -0
  9. package/build/src/bin/chrome-devtools-mcp-cli-options.js +24 -10
  10. package/build/src/bin/chrome-devtools-mcp-main.js +2 -0
  11. package/build/src/bin/chrome-devtools.js +3 -0
  12. package/build/src/bin/cliDefinitions.js +14 -8
  13. package/build/src/daemon/client.js +1 -1
  14. package/build/src/daemon/daemon.js +0 -4
  15. package/build/src/formatters/HeapSnapshotFormatter.js +38 -0
  16. package/build/src/formatters/NetworkFormatter.js +24 -7
  17. package/build/src/index.js +12 -1
  18. package/build/src/telemetry/ClearcutLogger.js +34 -12
  19. package/build/src/telemetry/flagUtils.js +46 -4
  20. package/build/src/telemetry/toolMetricsUtils.js +88 -0
  21. package/build/src/telemetry/watchdog/ClearcutSender.js +4 -3
  22. package/build/src/third_party/THIRD_PARTY_NOTICES +32 -32
  23. package/build/src/third_party/bundled-packages.json +5 -4
  24. package/build/src/third_party/devtools-formatter-worker.js +61 -64
  25. package/build/src/third_party/devtools-heap-snapshot-worker.js +9690 -0
  26. package/build/src/third_party/index.js +61443 -59378
  27. package/build/src/third_party/lighthouse-devtools-mcp-bundle.js +3501 -2658
  28. package/build/src/tools/categories.js +3 -0
  29. package/build/src/tools/console.js +42 -39
  30. package/build/src/tools/emulation.js +1 -1
  31. package/build/src/tools/extensions.js +5 -11
  32. package/build/src/tools/inPage.js +27 -6
  33. package/build/src/tools/input.js +15 -16
  34. package/build/src/tools/lighthouse.js +2 -2
  35. package/build/src/tools/memory.js +48 -3
  36. package/build/src/tools/network.js +2 -2
  37. package/build/src/tools/pages.js +8 -5
  38. package/build/src/tools/performance.js +1 -1
  39. package/build/src/tools/screencast.js +2 -1
  40. package/build/src/tools/screenshot.js +3 -3
  41. package/build/src/tools/script.js +22 -16
  42. package/build/src/tools/tools.js +2 -0
  43. package/build/src/tools/webmcp.js +63 -0
  44. package/build/src/utils/check-for-updates.js +73 -0
  45. package/build/src/utils/files.js +4 -0
  46. package/build/src/utils/id.js +15 -0
  47. package/build/src/version.js +1 -1
  48. package/package.json +12 -8
  49. package/build/src/utils/ExtensionRegistry.js +0 -35
@@ -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 = this.#snapshotParams.filePath;
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
- inPageTools = await getToolGroup(context.getSelectedMcpPage());
288
- context.setInPageTools(inPageTools);
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
- structuredContent.extensions = data.extensions;
656
+ const extensionArray = Array.from(data.extensions.values());
657
+ structuredContent.extensions = extensionArray;
544
658
  response.push('## Extensions');
545
- if (data.extensions.length === 0) {
659
+ if (extensionArray.length === 0) {
546
660
  response.push('No extensions installed.');
547
661
  }
548
662
  else {
549
- const extensionsMessage = data.extensions
663
+ const extensionsMessage = extensionArray
550
664
  .map(extension => {
551
- return `id=${extension.id} "${extension.name}" v${extension.version} ${extension.isEnabled ? 'Enabled' : 'Disabled'}`;
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.#session.on('Audits.issueAdded', this.#onIssueAdded);
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.#session.off('Audits.issueAdded', this.#onIssueAdded);
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('issue', event.data);
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 = (data) => {
233
+ #onIssueAdded = (inspectorIssue) => {
252
234
  try {
253
- const inspectorIssue = data.issue;
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 remoted debugging server to be started in the Chrome instance via chrome://inspect/#remote-debugging.',
11
- conflicts: ['isolated', 'executablePath', 'categoryExtensions'],
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', 'categoryExtensions'],
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', 'categoryExtensions'],
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: ['stable', 'canary', 'beta', 'dev'],
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 vision tools',
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: true,
197
- conflicts: ['browserUrl', 'autoConnect', 'wsEndpoint'],
198
- describe: 'Set to true to include tools related to extensions. Note: This feature is only supported with a pipe connection. autoConnect is not supported.',
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 a input, text area or select an option from a <select> element.',
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 requests.',
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 open in the browser.',
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: 'Performance',
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 pages screenshot.',
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: {
@@ -135,7 +135,7 @@ export async function handleResponse(response, format) {
135
135
  case 'image/jpeg':
136
136
  extension = '.jpeg';
137
137
  break;
138
- case 'webp':
138
+ case 'image/webp':
139
139
  extension = '.webp';
140
140
  break;
141
141
  }
@@ -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],