chrome-devtools-mcp 1.1.1 → 1.3.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 (109) hide show
  1. package/README.md +33 -4
  2. package/build/src/HeapSnapshotManager.js +30 -16
  3. package/build/src/McpContext.js +63 -9
  4. package/build/src/McpPage.js +9 -9
  5. package/build/src/McpResponse.js +176 -58
  6. package/build/src/PageCollector.js +8 -8
  7. package/build/src/ServiceWorkerCollector.js +171 -0
  8. package/build/src/TextSnapshot.js +1 -1
  9. package/build/src/ToolHandler.js +10 -4
  10. package/build/src/WaitForHelper.js +2 -2
  11. package/build/src/bin/chrome-devtools-cli-options.js +106 -4
  12. package/build/src/bin/chrome-devtools-mcp-cli-options.js +64 -5
  13. package/build/src/bin/chrome-devtools-mcp-main.js +5 -5
  14. package/build/src/browser.js +8 -4
  15. package/build/src/daemon/client.js +7 -7
  16. package/build/src/daemon/daemon.js +12 -12
  17. package/build/src/daemon/utils.js +2 -2
  18. package/build/src/{DevToolsConnectionAdapter.js → devtools/DevToolsConnectionAdapter.js} +1 -1
  19. package/build/src/{DevtoolsUtils.js → devtools/DevtoolsUtils.js} +79 -18
  20. package/build/src/devtools/McpHostBindingAdapter.js +165 -0
  21. package/build/src/formatters/ConsoleFormatter.js +1 -1
  22. package/build/src/formatters/HeapSnapshotFormatter.js +22 -0
  23. package/build/src/formatters/IssueFormatter.js +4 -4
  24. package/build/src/index.js +13 -1
  25. package/build/src/telemetry/ClearcutLogger.js +1 -1
  26. package/build/src/telemetry/WatchdogClient.js +4 -4
  27. package/build/src/telemetry/persistence.js +2 -2
  28. package/build/src/telemetry/watchdog/ClearcutSender.js +10 -10
  29. package/build/src/telemetry/watchdog/main.js +5 -5
  30. package/build/src/third_party/THIRD_PARTY_NOTICES +31 -1
  31. package/build/src/third_party/bundled-packages.json +2 -1
  32. package/build/src/third_party/devtools-formatter-worker.js +1 -1
  33. package/build/src/third_party/devtools-heap-snapshot-worker.js +161 -3
  34. package/build/src/third_party/index.js +7020 -5774
  35. package/build/src/third_party/issue-descriptions/emailVerificationRequestAccountsEmptyList.md +1 -0
  36. package/build/src/third_party/issue-descriptions/emailVerificationRequestAccountsHttpNotFound.md +1 -0
  37. package/build/src/third_party/issue-descriptions/emailVerificationRequestAccountsInvalidContentType.md +1 -0
  38. package/build/src/third_party/issue-descriptions/emailVerificationRequestAccountsInvalidResponse.md +1 -0
  39. package/build/src/third_party/issue-descriptions/emailVerificationRequestAccountsNoResponse.md +1 -0
  40. package/build/src/third_party/issue-descriptions/emailVerificationRequestDnsFetchFailed.md +1 -0
  41. package/build/src/third_party/issue-descriptions/emailVerificationRequestDnsInvalidRecord.md +1 -0
  42. package/build/src/third_party/issue-descriptions/emailVerificationRequestEmailVerificationWellKnownHttpNotFound.md +1 -0
  43. package/build/src/third_party/issue-descriptions/emailVerificationRequestEmailVerificationWellKnownInvalidContentType.md +1 -0
  44. package/build/src/third_party/issue-descriptions/emailVerificationRequestEmailVerificationWellKnownInvalidResponse.md +1 -0
  45. package/build/src/third_party/issue-descriptions/emailVerificationRequestEmailVerificationWellKnownNoResponse.md +1 -0
  46. package/build/src/third_party/issue-descriptions/emailVerificationRequestInvalidEmail.md +1 -0
  47. package/build/src/third_party/issue-descriptions/emailVerificationRequestJwksHttpNotFound.md +1 -0
  48. package/build/src/third_party/issue-descriptions/emailVerificationRequestJwksInvalidResponse.md +1 -0
  49. package/build/src/third_party/issue-descriptions/emailVerificationRequestKeyBindingSigningFailed.md +1 -0
  50. package/build/src/third_party/issue-descriptions/emailVerificationRequestRpOriginIsOpaque.md +1 -0
  51. package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenHttpNotFound.md +1 -0
  52. package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenInvalidContentType.md +1 -0
  53. package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenInvalidResponse.md +1 -0
  54. package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenInvalidSdJwt.md +1 -0
  55. package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenMalformedSdJwt.md +1 -0
  56. package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenNoResponse.md +1 -0
  57. package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationKbInvalidAudience.md +1 -0
  58. package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationKbInvalidIssuedAt.md +1 -0
  59. package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationKbInvalidNonce.md +1 -0
  60. package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationKbInvalidSdHash.md +1 -0
  61. package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationKbInvalidTyp.md +1 -0
  62. package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationKbMissingAud.md +1 -0
  63. package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationKbMissingCnf.md +1 -0
  64. package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationKbMissingIat.md +1 -0
  65. package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationKbMissingNonce.md +1 -0
  66. package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationKbMissingSdHash.md +1 -0
  67. package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationKbSignatureFailed.md +1 -0
  68. package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationSdJwtInvalidEmail.md +1 -0
  69. package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationSdJwtInvalidEmailVerified.md +1 -0
  70. package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationSdJwtInvalidHolderKey.md +1 -0
  71. package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationSdJwtInvalidIssuedAt.md +1 -0
  72. package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationSdJwtInvalidIssuer.md +1 -0
  73. package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationSdJwtJwksMissingKeys.md +1 -0
  74. package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationSdJwtMissingCnf.md +1 -0
  75. package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationSdJwtMissingEmail.md +1 -0
  76. package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationSdJwtMissingIat.md +1 -0
  77. package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationSdJwtMissingIss.md +1 -0
  78. package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationSdJwtSignatureFailed.md +1 -0
  79. package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationSdJwtUnsupportedHeaderAlg.md +1 -0
  80. package/build/src/third_party/issue-descriptions/emailVerificationRequestUserLoggedOut.md +1 -0
  81. package/build/src/third_party/issue-descriptions/emailVerificationRequestWellKnownAccountsEndpointCrossOrigin.md +1 -0
  82. package/build/src/third_party/issue-descriptions/emailVerificationRequestWellKnownHttpNotFound.md +1 -0
  83. package/build/src/third_party/issue-descriptions/emailVerificationRequestWellKnownInvalidContentType.md +1 -0
  84. package/build/src/third_party/issue-descriptions/emailVerificationRequestWellKnownInvalidResponse.md +1 -0
  85. package/build/src/third_party/issue-descriptions/emailVerificationRequestWellKnownIssuanceEndpointCrossOrigin.md +1 -0
  86. package/build/src/third_party/issue-descriptions/emailVerificationRequestWellKnownListEmpty.md +1 -0
  87. package/build/src/third_party/issue-descriptions/emailVerificationRequestWellKnownMissingAccountsEndpoint.md +1 -0
  88. package/build/src/third_party/issue-descriptions/emailVerificationRequestWellKnownMissingIssuanceEndpoint.md +1 -0
  89. package/build/src/third_party/issue-descriptions/emailVerificationRequestWellKnownNoResponse.md +1 -0
  90. package/build/src/third_party/issue-descriptions/emailVerificationRequestWellKnownUnsupportedSigningAlgorithm.md +1 -0
  91. package/build/src/tools/console.js +7 -0
  92. package/build/src/tools/emulation.js +1 -0
  93. package/build/src/tools/extensions.js +5 -2
  94. package/build/src/tools/input.js +11 -3
  95. package/build/src/tools/lighthouse.js +11 -6
  96. package/build/src/tools/memory.js +109 -10
  97. package/build/src/tools/network.js +2 -2
  98. package/build/src/tools/pages.js +13 -5
  99. package/build/src/tools/performance.js +8 -7
  100. package/build/src/tools/screencast.js +32 -10
  101. package/build/src/tools/screenshot.js +158 -76
  102. package/build/src/tools/script.js +1 -1
  103. package/build/src/tools/slim/tools.js +3 -0
  104. package/build/src/tools/snapshot.js +3 -2
  105. package/build/src/tools/thirdPartyDeveloper.js +12 -2
  106. package/build/src/tools/webmcp.js +2 -0
  107. package/build/src/trace-processing/parse.js +5 -5
  108. package/build/src/version.js +1 -1
  109. package/package.json +4 -3
@@ -139,15 +139,21 @@ export class ToolHandler {
139
139
  const startTime = Date.now();
140
140
  let success = false;
141
141
  try {
142
- logger(`${this.tool.name} request: ${JSON.stringify(params, null, ' ')}`);
142
+ logger?.(`${this.tool.name} request: ${JSON.stringify(params, null, ' ')}`);
143
143
  const context = await this.getContext();
144
- logger(`${this.tool.name} context: resolved`);
144
+ logger?.(`${this.tool.name} context: resolved`);
145
145
  await context.detectOpenDevToolsWindows();
146
146
  const response = this.serverArgs.slim
147
147
  ? new SlimMcpResponse(this.serverArgs)
148
148
  : new McpResponse(this.serverArgs);
149
149
  response.setRedactNetworkHeaders(this.serverArgs.redactNetworkHeaders);
150
150
  try {
151
+ if (this.tool.verifyFilesSchema) {
152
+ for (const key of this.tool.verifyFilesSchema) {
153
+ const filePath = params[key];
154
+ await context.validatePath(filePath);
155
+ }
156
+ }
151
157
  if (isPageScopedTool(this.tool)) {
152
158
  const pageId = typeof params.pageId === 'number' ? params.pageId : undefined;
153
159
  const page = this.serverArgs.experimentalPageIdRouting &&
@@ -173,7 +179,7 @@ export class ToolHandler {
173
179
  catch (err) {
174
180
  response.setError(err);
175
181
  }
176
- const { content, structuredContent } = await response.handle(this.tool.name, context);
182
+ const { content, structuredContent } = await response.handle(this.tool.name, context, this.serverArgs.experimentalToonFormat ?? false);
177
183
  const result = {
178
184
  content,
179
185
  };
@@ -187,7 +193,7 @@ export class ToolHandler {
187
193
  return result;
188
194
  }
189
195
  catch (err) {
190
- logger(`${this.tool.name} error:`, err, err?.stack);
196
+ logger?.(`${this.tool.name} error:`, err, err?.stack);
191
197
  let errorText = err && 'message' in err ? err.message : String(err);
192
198
  if ('cause' in err && err.cause) {
193
199
  errorText += `\nCause: ${err.cause.message}`;
@@ -138,7 +138,7 @@ export class WaitForHelper {
138
138
  }
139
139
  return;
140
140
  })
141
- .catch(error => logger(error));
141
+ .catch(error => logger?.(error));
142
142
  try {
143
143
  await action();
144
144
  }
@@ -157,7 +157,7 @@ export class WaitForHelper {
157
157
  await this.waitForStableDom();
158
158
  }
159
159
  catch (error) {
160
- logger(error);
160
+ logger?.(error);
161
161
  }
162
162
  finally {
163
163
  this.#abortController.abort();
@@ -58,6 +58,18 @@ export const commands = {
58
58
  },
59
59
  },
60
60
  },
61
+ close_heapsnapshot: {
62
+ description: 'Closes a previously loaded memory heapsnapshot, freeing its memory. (requires flag: --memoryDebugging=true)',
63
+ category: 'Memory',
64
+ args: {
65
+ filePath: {
66
+ name: 'filePath',
67
+ type: 'string',
68
+ description: 'A path to the .heapsnapshot file to close.',
69
+ required: true,
70
+ },
71
+ },
72
+ },
61
73
  close_page: {
62
74
  description: 'Closes the page by its index. The last open page cannot be closed.',
63
75
  category: 'Navigation automation',
@@ -247,7 +259,7 @@ export const commands = {
247
259
  },
248
260
  },
249
261
  get_heapsnapshot_class_nodes: {
250
- description: 'Loads a memory heapsnapshot and returns instances of a specific class with their IDs. (requires flag: --experimentalMemory=true)',
262
+ description: 'Loads a memory heapsnapshot and returns instances of a specific class with their IDs. (requires flag: --memoryDebugging=true)',
251
263
  category: 'Memory',
252
264
  args: {
253
265
  filePath: {
@@ -277,7 +289,7 @@ export const commands = {
277
289
  },
278
290
  },
279
291
  get_heapsnapshot_details: {
280
- description: 'Loads a memory heapsnapshot and returns all available information including statistics, static data, and aggregated node information. Supports pagination for aggregates. (requires flag: --experimentalMemory=true)',
292
+ description: 'Loads a memory heapsnapshot and returns all available information including statistics, static data, and aggregated node information. Supports pagination for aggregates. (requires flag: --memoryDebugging=true)',
281
293
  category: 'Memory',
282
294
  args: {
283
295
  filePath: {
@@ -300,8 +312,56 @@ export const commands = {
300
312
  },
301
313
  },
302
314
  },
315
+ get_heapsnapshot_dominators: {
316
+ description: 'Loads a memory heapsnapshot and returns the dominator chain for a specific node ID. This helps to identify which objects are keeping the target node alive. (requires flag: --memoryDebugging=true)',
317
+ category: 'Memory',
318
+ args: {
319
+ filePath: {
320
+ name: 'filePath',
321
+ type: 'string',
322
+ description: 'A path to a .heapsnapshot file to read.',
323
+ required: true,
324
+ },
325
+ nodeId: {
326
+ name: 'nodeId',
327
+ type: 'number',
328
+ description: 'The node ID to get the dominator chain for.',
329
+ required: true,
330
+ },
331
+ },
332
+ },
333
+ get_heapsnapshot_edges: {
334
+ description: 'Loads a memory heapsnapshot and returns outgoing edges (references) for a specific node ID. (requires flag: --memoryDebugging=true)',
335
+ category: 'Memory',
336
+ args: {
337
+ filePath: {
338
+ name: 'filePath',
339
+ type: 'string',
340
+ description: 'A path to a .heapsnapshot file to read.',
341
+ required: true,
342
+ },
343
+ nodeId: {
344
+ name: 'nodeId',
345
+ type: 'number',
346
+ description: 'The node ID to get outgoing edges for.',
347
+ required: true,
348
+ },
349
+ pageIdx: {
350
+ name: 'pageIdx',
351
+ type: 'number',
352
+ description: 'The page index for pagination.',
353
+ required: false,
354
+ },
355
+ pageSize: {
356
+ name: 'pageSize',
357
+ type: 'number',
358
+ description: 'The page size for pagination.',
359
+ required: false,
360
+ },
361
+ },
362
+ },
303
363
  get_heapsnapshot_retainers: {
304
- description: 'Loads a memory heapsnapshot and returns retainers for a specific node ID. (requires flag: --experimentalMemory=true)',
364
+ description: 'Loads a memory heapsnapshot and returns retainers for a specific node ID. (requires flag: --memoryDebugging=true)',
305
365
  category: 'Memory',
306
366
  args: {
307
367
  filePath: {
@@ -330,8 +390,44 @@ export const commands = {
330
390
  },
331
391
  },
332
392
  },
393
+ get_heapsnapshot_retaining_paths: {
394
+ description: 'Loads a memory heapsnapshot and returns retaining paths for a specific node ID. This helps to understand why a node is not being garbage collected. (requires flag: --memoryDebugging=true)',
395
+ category: 'Memory',
396
+ args: {
397
+ filePath: {
398
+ name: 'filePath',
399
+ type: 'string',
400
+ description: 'A path to a .heapsnapshot file to read.',
401
+ required: true,
402
+ },
403
+ nodeId: {
404
+ name: 'nodeId',
405
+ type: 'number',
406
+ description: 'The node ID to get retaining paths for.',
407
+ required: true,
408
+ },
409
+ maxDepth: {
410
+ name: 'maxDepth',
411
+ type: 'number',
412
+ description: 'The maximum depth to search for retaining paths.',
413
+ required: false,
414
+ },
415
+ maxNodes: {
416
+ name: 'maxNodes',
417
+ type: 'number',
418
+ description: 'The maximum number of nodes to return.',
419
+ required: false,
420
+ },
421
+ maxSiblings: {
422
+ name: 'maxSiblings',
423
+ type: 'number',
424
+ description: 'The maximum number of siblings to return.',
425
+ required: false,
426
+ },
427
+ },
428
+ },
333
429
  get_heapsnapshot_summary: {
334
- description: 'Loads a memory heapsnapshot and returns snapshot summary stats. (requires flag: --experimentalMemory=true)',
430
+ description: 'Loads a memory heapsnapshot and returns snapshot summary stats. (requires flag: --memoryDebugging=true)',
335
431
  category: 'Memory',
336
432
  args: {
337
433
  filePath: {
@@ -477,6 +573,12 @@ export const commands = {
477
573
  required: false,
478
574
  default: false,
479
575
  },
576
+ serviceWorkerId: {
577
+ name: 'serviceWorkerId',
578
+ type: 'string',
579
+ description: 'Filter messages to only return messages of the specified service worker.',
580
+ required: false,
581
+ },
480
582
  },
481
583
  },
482
584
  list_extensions: {
@@ -146,14 +146,20 @@ export const cliOptions = {
146
146
  type: 'boolean',
147
147
  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.',
148
148
  },
149
- experimentalMemory: {
149
+ memoryDebugging: {
150
150
  type: 'boolean',
151
- describe: 'Whether to enable experimental memory tools.',
151
+ describe: 'Whether to enable memory debugging tools.',
152
+ alias: 'experimentalMemory',
152
153
  },
153
154
  experimentalStructuredContent: {
154
155
  type: 'boolean',
155
156
  describe: 'Whether to output structured formatted content.',
156
157
  },
158
+ experimentalToonFormat: {
159
+ type: 'boolean',
160
+ describe: 'Whether to format structured data in text response using Token-Oriented Object Notation. Defaults to false which represents the embedded content as formatted JSON instead.',
161
+ hidden: true,
162
+ },
157
163
  experimentalIncludeAllPages: {
158
164
  type: 'boolean',
159
165
  describe: 'Whether to include all kinds of pages such as webviews or background pages as pages.',
@@ -179,12 +185,22 @@ export const cliOptions = {
179
185
  },
180
186
  categoryExperimentalWebmcp: {
181
187
  type: 'boolean',
182
- describe: 'Set to true to enable debugging WebMCP tools. Requires Chrome 149+ with the following flags: `--enable-features=WebMCPTesting,DevToolsWebMCPSupport`',
188
+ describe: 'Set to true to enable debugging WebMCP tools. Requires Chrome 149+ with the following flags: `--enable-features=WebMCP,DevToolsWebMCPSupport`',
183
189
  },
184
190
  chromeArg: {
185
191
  type: 'array',
186
192
  describe: 'Additional arguments for Chrome. Only applies when Chrome is launched by chrome-devtools-mcp.',
187
193
  },
194
+ blockedUrlPattern: {
195
+ type: 'array',
196
+ describe: 'Restricts network access by blocking specified URL patterns (uses https://urlpattern.spec.whatwg.org/). Silently detaches from targets with blocked URLs upon connection, and blocks runtime requests (including navigations and subresources). Accepts an array of patterns.',
197
+ conflicts: ['allowedUrlPattern'],
198
+ },
199
+ allowedUrlPattern: {
200
+ type: 'array',
201
+ describe: 'Restricts network access by allowing only specified URL patterns (uses https://urlpattern.spec.whatwg.org/). Requires Chrome 149+. Silently detaches from targets with unallowed URLs upon connection, and blocks runtime requests (including navigations and subresources). Accepts an array of patterns.',
202
+ conflicts: ['blockedUrlPattern'],
203
+ },
188
204
  ignoreDefaultChromeArg: {
189
205
  type: 'array',
190
206
  describe: 'Explicitly disable default arguments for Chrome. Only applies when Chrome is launched by chrome-devtools-mcp.',
@@ -240,6 +256,50 @@ export const cliOptions = {
240
256
  hidden: true,
241
257
  describe: 'Include watchdog PID in Clearcut request headers (for testing).',
242
258
  },
259
+ screenshotFormat: {
260
+ type: 'string',
261
+ description: 'Override the default output format used by take_screenshot when the caller does not specify one. JPEG and WebP are ~3-5x smaller than PNG, which helps reduce context size in AI conversations. Unset preserves the existing default ("png").',
262
+ choices: ['jpeg', 'png', 'webp'],
263
+ },
264
+ screenshotQuality: {
265
+ type: 'number',
266
+ description: 'Override the default compression quality (0-100) used by take_screenshot for JPEG and WebP when the caller does not specify one. Lower values mean smaller files. Ignored for PNG. Unset preserves the Puppeteer default.',
267
+ coerce: (value) => {
268
+ if (value === undefined) {
269
+ return;
270
+ }
271
+ if (!Number.isInteger(value) || value < 0 || value > 100) {
272
+ throw new Error(`Invalid screenshotQuality ${value}. Expected an integer between 0 and 100.`);
273
+ }
274
+ return value;
275
+ },
276
+ },
277
+ screenshotMaxWidth: {
278
+ type: 'number',
279
+ description: 'Maximum width in pixels for screenshots. If the captured image is wider, it is downscaled (preserving aspect ratio) before being returned. Reduces context size in AI conversations. Unset means no resize.',
280
+ coerce: (value) => {
281
+ if (value === undefined) {
282
+ return;
283
+ }
284
+ if (!Number.isInteger(value) || value <= 0) {
285
+ throw new Error(`Invalid screenshotMaxWidth ${value}. Expected a positive integer.`);
286
+ }
287
+ return value;
288
+ },
289
+ },
290
+ screenshotMaxHeight: {
291
+ type: 'number',
292
+ description: 'Maximum height in pixels for screenshots. If the captured image is taller, it is downscaled (preserving aspect ratio) before being returned. Can be combined with --screenshot-max-width; the smaller scale factor wins. Unset means no resize.',
293
+ coerce: (value) => {
294
+ if (value === undefined) {
295
+ return;
296
+ }
297
+ if (!Number.isInteger(value) || value <= 0) {
298
+ throw new Error(`Invalid screenshotMaxHeight ${value}. Expected a positive integer.`);
299
+ }
300
+ return value;
301
+ },
302
+ },
243
303
  slim: {
244
304
  type: 'boolean',
245
305
  describe: 'Exposes a "slim" set of 3 tools covering navigation, script execution and screenshots only. Useful for basic browser tasks.',
@@ -259,7 +319,7 @@ export function parseArguments(version, argv = process.argv, env = process.env)
259
319
  const yargsInstance = yargs(hideBin(argv))
260
320
  .scriptName('npx chrome-devtools-mcp@latest')
261
321
  .options(cliOptions)
262
- .check(args => {
322
+ .middleware(args => {
263
323
  // We can't set default in the options else
264
324
  // Yargs will complain
265
325
  if (!args.channel &&
@@ -272,7 +332,6 @@ export function parseArguments(version, argv = process.argv, env = process.env)
272
332
  console.error("turning off usage statistics. process.env['CI'] || process.env['CHROME_DEVTOOLS_MCP_NO_USAGE_STATISTICS'] is set.");
273
333
  args.usageStatistics = false;
274
334
  }
275
- return true;
276
335
  })
277
336
  .example([
278
337
  [
@@ -19,7 +19,7 @@ export const args = parseArguments(VERSION);
19
19
  const logFile = args.logFile ? saveLogsToFile(args.logFile) : undefined;
20
20
  if (process.env['CHROME_DEVTOOLS_MCP_CRASH_ON_UNCAUGHT'] !== 'true') {
21
21
  process.on('unhandledRejection', (reason, promise) => {
22
- logger('Unhandled promise rejection', promise, reason);
22
+ logger?.('Unhandled promise rejection', promise, reason);
23
23
  });
24
24
  }
25
25
  // Shutdown on stdin EOF (stdio MCP convention — the client closes the
@@ -32,13 +32,13 @@ async function shutdown(reason) {
32
32
  return;
33
33
  }
34
34
  shuttingDown = true;
35
- logger(`Shutting down (${reason})`);
35
+ logger?.(`Shutting down (${reason})`);
36
36
  // Backstop in case browser teardown hangs (e.g. unresponsive Chrome,
37
37
  // slow beforeunload handlers, many tabs). Exits 0 because we still
38
38
  // honored the shutdown request; the log line preserves observability.
39
39
  // Unref'd so it doesn't keep the loop alive on the clean path.
40
40
  setTimeout(() => {
41
- logger('Shutdown timeout exceeded, forcing exit');
41
+ logger?.('Shutdown timeout exceeded, forcing exit');
42
42
  process.exit(0);
43
43
  }, 10000).unref();
44
44
  await closeBrowser();
@@ -59,13 +59,13 @@ process.on('SIGINT', () => {
59
59
  process.on('SIGHUP', () => {
60
60
  void shutdown('SIGHUP');
61
61
  });
62
- logger(`Starting Chrome DevTools MCP Server v${VERSION}`);
62
+ logger?.(`Starting Chrome DevTools MCP Server v${VERSION}`);
63
63
  const { server } = await createMcpServer(args, {
64
64
  logFile,
65
65
  });
66
66
  const transport = new StdioServerTransport();
67
67
  await server.connect(transport);
68
- logger('Chrome DevTools MCP Server connected');
68
+ logger?.('Chrome DevTools MCP Server connected');
69
69
  logDisclaimers(args);
70
70
  void ClearcutLogger.get()?.logDailyActiveIfNeeded();
71
71
  void ClearcutLogger.get()?.logServerStart(computeFlagUsage(args, cliOptions));
@@ -41,6 +41,8 @@ export async function ensureBrowserConnected(options) {
41
41
  targetFilter: makeTargetFilter(enableExtensions),
42
42
  defaultViewport: null,
43
43
  handleDevToolsAsPage: true,
44
+ blocklist: options.blocklist,
45
+ allowlist: options.allowlist,
44
46
  };
45
47
  let autoConnect = false;
46
48
  if (options.wsEndpoint) {
@@ -94,7 +96,7 @@ export async function ensureBrowserConnected(options) {
94
96
  else {
95
97
  throw new Error('Either browserURL, wsEndpoint, channel or userDataDir must be provided');
96
98
  }
97
- logger('Connecting Puppeteer to ', JSON.stringify(connectOptions));
99
+ logger?.('Connecting Puppeteer to ', JSON.stringify(connectOptions));
98
100
  try {
99
101
  // Assign mode before browser so a concurrent closeBrowser() never sees
100
102
  // `browser` set with `browserMode` still undefined (would fall through
@@ -108,7 +110,7 @@ export async function ensureBrowserConnected(options) {
108
110
  cause: err,
109
111
  });
110
112
  }
111
- logger('Connected Puppeteer');
113
+ logger?.('Connected Puppeteer');
112
114
  return browser;
113
115
  }
114
116
  export function detectDisplay() {
@@ -174,6 +176,8 @@ export async function launch(options) {
174
176
  acceptInsecureCerts: options.acceptInsecureCerts,
175
177
  handleDevToolsAsPage: true,
176
178
  enableExtensions: options.enableExtensions,
179
+ blocklist: options.blocklist,
180
+ allowlist: options.allowlist,
177
181
  });
178
182
  if (options.logFile) {
179
183
  // FIXME: we are probably subscribing too late to catch startup logs. We
@@ -227,12 +231,12 @@ export async function closeBrowser() {
227
231
  }
228
232
  if (mode === 'launched') {
229
233
  await b.close().catch(err => {
230
- logger('Failed to close browser', err);
234
+ logger?.('Failed to close browser', err);
231
235
  });
232
236
  return;
233
237
  }
234
238
  await b.disconnect().catch(err => {
235
- logger('Failed to disconnect from browser', err);
239
+ logger?.('Failed to disconnect from browser', err);
236
240
  });
237
241
  }
238
242
  //# sourceMappingURL=browser.js.map
@@ -50,14 +50,14 @@ function waitForFile(filePath, removed = false) {
50
50
  }
51
51
  export async function startDaemon(mcpArgs = [], sessionId) {
52
52
  if (isDaemonRunning(sessionId)) {
53
- logger('Daemon is already running');
53
+ logger?.('Daemon is already running');
54
54
  return;
55
55
  }
56
56
  const pidFilePath = getPidFilePath(sessionId);
57
57
  if (fs.existsSync(pidFilePath)) {
58
58
  fs.unlinkSync(pidFilePath);
59
59
  }
60
- logger('Starting daemon...', ...mcpArgs);
60
+ logger?.('Starting daemon...', ...mcpArgs);
61
61
  const child = spawn(process.execPath, [DAEMON_SCRIPT_PATH, ...mcpArgs], {
62
62
  detached: true,
63
63
  stdio: 'ignore',
@@ -85,26 +85,26 @@ export async function sendCommand(command, sessionId) {
85
85
  const transport = new PipeTransport(socket, socket);
86
86
  transport.onmessage = async (message) => {
87
87
  clearTimeout(timer);
88
- logger('onmessage', message);
88
+ logger?.('onmessage', message);
89
89
  resolve(JSON.parse(message));
90
90
  };
91
91
  socket.on('error', error => {
92
92
  clearTimeout(timer);
93
- logger('Socket error:', error);
93
+ logger?.('Socket error:', error);
94
94
  reject(error);
95
95
  });
96
96
  socket.on('close', () => {
97
97
  clearTimeout(timer);
98
- logger('Socket closed:');
98
+ logger?.('Socket closed:');
99
99
  reject(new Error('Socket closed'));
100
100
  });
101
- logger('Sending message', command);
101
+ logger?.('Sending message', command);
102
102
  transport.send(JSON.stringify(command));
103
103
  });
104
104
  }
105
105
  export async function stopDaemon(sessionId) {
106
106
  if (!isDaemonRunning(sessionId)) {
107
- logger('Daemon is not running');
107
+ logger?.('Daemon is not running');
108
108
  return;
109
109
  }
110
110
  const pidFilePath = getPidFilePath(sessionId);
@@ -14,9 +14,9 @@ import { Client, PipeTransport, StdioClientTransport, } from '../third_party/ind
14
14
  import { VERSION } from '../version.js';
15
15
  import { DAEMON_CLIENT_NAME, getPidFilePath, getSocketPath, INDEX_SCRIPT_PATH, IS_WINDOWS, isDaemonRunning, } from './utils.js';
16
16
  const sessionId = process.env.CHROME_DEVTOOLS_MCP_SESSION_ID || '';
17
- logger(`Daemon sessionId: ${sessionId}`);
17
+ logger?.(`Daemon sessionId: ${sessionId}`);
18
18
  if (isDaemonRunning(sessionId)) {
19
- logger('Another daemon process is running.');
19
+ logger?.('Another daemon process is running.');
20
20
  process.exit(1);
21
21
  }
22
22
  const pidFilePath = getPidFilePath(sessionId);
@@ -80,7 +80,7 @@ finally {
80
80
  }
81
81
  }
82
82
  }
83
- logger(`Writing ${process.pid.toString()} to ${pidFilePath}`);
83
+ logger?.(`Writing ${process.pid.toString()} to ${pidFilePath}`);
84
84
  const socketPath = getSocketPath(sessionId);
85
85
  const startDate = new Date();
86
86
  const mcpServerArgs = process.argv.slice(2);
@@ -173,13 +173,13 @@ async function startSocketServer() {
173
173
  server = createServer(socket => {
174
174
  const transport = new PipeTransport(socket, socket);
175
175
  transport.onmessage = async (message) => {
176
- logger('onmessage', message);
176
+ logger?.('onmessage', message);
177
177
  const response = await handleRequest(JSON.parse(message));
178
178
  transport.send(JSON.stringify(response));
179
179
  socket.end();
180
180
  };
181
181
  socket.on('error', error => {
182
- logger('Socket error:', error);
182
+ logger?.('Socket error:', error);
183
183
  });
184
184
  });
185
185
  server.listen({
@@ -198,7 +198,7 @@ async function startSocketServer() {
198
198
  }
199
199
  });
200
200
  server.on('error', error => {
201
- logger('Server error:', error);
201
+ logger?.('Server error:', error);
202
202
  reject(error);
203
203
  });
204
204
  });
@@ -209,13 +209,13 @@ async function cleanup() {
209
209
  await mcpClient?.close();
210
210
  }
211
211
  catch (error) {
212
- logger('Error closing MCP client:', error);
212
+ logger?.('Error closing MCP client:', error);
213
213
  }
214
214
  try {
215
215
  await mcpTransport?.close();
216
216
  }
217
217
  catch (error) {
218
- logger('Error closing MCP transport:', error);
218
+ logger?.('Error closing MCP transport:', error);
219
219
  }
220
220
  if (server) {
221
221
  await new Promise(resolve => {
@@ -230,7 +230,7 @@ async function cleanup() {
230
230
  // ignore errors
231
231
  }
232
232
  }
233
- logger(`unlinking ${pidFilePath}`);
233
+ logger?.(`unlinking ${pidFilePath}`);
234
234
  if (fs.existsSync(pidFilePath)) {
235
235
  fs.unlinkSync(pidFilePath);
236
236
  }
@@ -248,14 +248,14 @@ process.on('SIGHUP', () => {
248
248
  });
249
249
  // Handle uncaught errors
250
250
  process.on('uncaughtException', error => {
251
- logger('Uncaught exception:', error);
251
+ logger?.('Uncaught exception:', error);
252
252
  });
253
253
  process.on('unhandledRejection', error => {
254
- logger('Unhandled rejection:', error);
254
+ logger?.('Unhandled rejection:', error);
255
255
  });
256
256
  // Start the server
257
257
  const started = startSocketServer().catch(error => {
258
- logger('Failed to start daemon server:', error);
258
+ logger?.('Failed to start daemon server:', error);
259
259
  process.exit(1);
260
260
  });
261
261
  //# sourceMappingURL=daemon.js.map
@@ -56,13 +56,13 @@ export function getPidFilePath(sessionId) {
56
56
  export function getDaemonPid(sessionId) {
57
57
  try {
58
58
  const pidFile = getPidFilePath(sessionId);
59
- logger(`Daemon pid file ${pidFile} sessionId=${sessionId}`);
59
+ logger?.(`Daemon pid file ${pidFile} sessionId=${sessionId}`);
60
60
  if (!fs.existsSync(pidFile)) {
61
61
  return null;
62
62
  }
63
63
  const pidContent = fs.readFileSync(pidFile, 'utf-8');
64
64
  const pid = parseInt(pidContent.trim(), 10);
65
- logger(`Daemon pid: ${pid}`);
65
+ logger?.(`Daemon pid: ${pid}`);
66
66
  if (isNaN(pid)) {
67
67
  return null;
68
68
  }
@@ -3,7 +3,7 @@
3
3
  * Copyright 2025 Google LLC
4
4
  * SPDX-License-Identifier: Apache-2.0
5
5
  */
6
- import { CDPSessionEvent } from './third_party/index.js';
6
+ import { CDPSessionEvent } from '../third_party/index.js';
7
7
  /**
8
8
  * This class makes a puppeteer connection look like DevTools CDPConnection.
9
9
  *