chrome-devtools-mcp 0.12.1 → 0.13.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/build/src/main.js CHANGED
@@ -6,21 +6,27 @@
6
6
  import './polyfill.js';
7
7
  import process from 'node:process';
8
8
  import { ensureBrowserConnected, ensureBrowserLaunched } from './browser.js';
9
- import { parseArguments } from './cli.js';
9
+ import { cliOptions, parseArguments } from './cli.js';
10
10
  import { loadIssueDescriptions } from './issue-descriptions.js';
11
11
  import { logger, saveLogsToFile } from './logger.js';
12
12
  import { McpContext } from './McpContext.js';
13
13
  import { McpResponse } from './McpResponse.js';
14
14
  import { Mutex } from './Mutex.js';
15
+ import { ClearcutLogger } from './telemetry/clearcut-logger.js';
16
+ import { computeFlagUsage } from './telemetry/flag-utils.js';
15
17
  import { McpServer, StdioServerTransport, SetLevelRequestSchema, } from './third_party/index.js';
16
18
  import { ToolCategory } from './tools/categories.js';
17
19
  import { tools } from './tools/tools.js';
18
20
  // If moved update release-please config
19
21
  // x-release-please-start-version
20
- const VERSION = '0.12.1';
22
+ const VERSION = '0.13.0';
21
23
  // x-release-please-end
22
24
  export const args = parseArguments(VERSION);
23
25
  const logFile = args.logFile ? saveLogsToFile(args.logFile) : undefined;
26
+ let clearcutLogger;
27
+ if (args.usageStatistics) {
28
+ clearcutLogger = new ClearcutLogger();
29
+ }
24
30
  process.on('unhandledRejection', (reason, promise) => {
25
31
  logger('Unhandled promise rejection', promise, reason);
26
32
  });
@@ -35,9 +41,10 @@ server.server.setRequestHandler(SetLevelRequestSchema, () => {
35
41
  });
36
42
  let context;
37
43
  async function getContext() {
38
- const extraArgs = (args.chromeArg ?? []).map(String);
44
+ const chromeArgs = (args.chromeArg ?? []).map(String);
45
+ const ignoreDefaultChromeArgs = (args.ignoreDefaultChromeArg ?? []).map(String);
39
46
  if (args.proxyServer) {
40
- extraArgs.push(`--proxy-server=${args.proxyServer}`);
47
+ chromeArgs.push(`--proxy-server=${args.proxyServer}`);
41
48
  }
42
49
  const devtools = args.experimentalDevtools ?? false;
43
50
  const browser = args.browserUrl || args.wsEndpoint || args.autoConnect
@@ -58,7 +65,8 @@ async function getContext() {
58
65
  userDataDir: args.userDataDir,
59
66
  logFile,
60
67
  viewport: args.viewport,
61
- args: extraArgs,
68
+ chromeArgs,
69
+ ignoreDefaultChromeArgs,
62
70
  acceptInsecureCerts: args.acceptInsecureCerts,
63
71
  devtools,
64
72
  });
@@ -74,6 +82,11 @@ const logDisclaimers = () => {
74
82
  console.error(`chrome-devtools-mcp exposes content of the browser instance to the MCP clients allowing them to inspect,
75
83
  debug, and modify any data in the browser or DevTools.
76
84
  Avoid sharing sensitive or personal information that you do not want to share with MCP clients.`);
85
+ if (args.usageStatistics) {
86
+ console.error(`
87
+ Google collects usage statistics to improve Chrome DevTools MCP. To opt-out, run with --no-usage-statistics.
88
+ For more details, visit: https://github.com/ChromeDevTools/chrome-devtools-mcp#usage-statistics`);
89
+ }
77
90
  };
78
91
  const toolMutex = new Mutex();
79
92
  function registerTool(tool) {
@@ -89,12 +102,22 @@ function registerTool(tool) {
89
102
  args.categoryNetwork === false) {
90
103
  return;
91
104
  }
105
+ if (tool.annotations.conditions?.includes('computerVision') &&
106
+ !args.experimentalVision) {
107
+ return;
108
+ }
109
+ if (tool.annotations.conditions?.includes('experimentalInteropTools') &&
110
+ !args.experimentalInteropTools) {
111
+ return;
112
+ }
92
113
  server.registerTool(tool.name, {
93
114
  description: tool.description,
94
115
  inputSchema: tool.schema,
95
116
  annotations: tool.annotations,
96
117
  }, async (params) => {
97
118
  const guard = await toolMutex.acquire();
119
+ const startTime = Date.now();
120
+ let success = false;
98
121
  try {
99
122
  logger(`${tool.name} request: ${JSON.stringify(params, null, ' ')}`);
100
123
  const context = await getContext();
@@ -104,10 +127,15 @@ function registerTool(tool) {
104
127
  await tool.handler({
105
128
  params,
106
129
  }, response, context);
107
- const content = await response.handle(tool.name, context);
108
- return {
130
+ const { content, structuredContent } = await response.handle(tool.name, context);
131
+ const result = {
109
132
  content,
110
133
  };
134
+ success = true;
135
+ if (args.experimentalStructuredContent) {
136
+ result.structuredContent = structuredContent;
137
+ }
138
+ return result;
111
139
  }
112
140
  catch (err) {
113
141
  logger(`${tool.name} error:`, err, err?.stack);
@@ -126,6 +154,11 @@ function registerTool(tool) {
126
154
  };
127
155
  }
128
156
  finally {
157
+ void clearcutLogger?.logToolInvocation({
158
+ toolName: tool.name,
159
+ success,
160
+ latencyMs: Date.now() - startTime,
161
+ });
129
162
  guard.dispose();
130
163
  }
131
164
  });
@@ -138,3 +171,4 @@ const transport = new StdioServerTransport();
138
171
  await server.connect(transport);
139
172
  logger('Chrome DevTools MCP Server connected');
140
173
  logDisclaimers();
174
+ void clearcutLogger?.logServerStart(computeFlagUsage(args, cliOptions));
@@ -0,0 +1,28 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Google LLC
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+ import { ClearcutSender } from './clearcut-sender.js';
7
+ export class ClearcutLogger {
8
+ #sender;
9
+ constructor(sender) {
10
+ this.#sender = sender ?? new ClearcutSender();
11
+ }
12
+ async logToolInvocation(args) {
13
+ await this.#sender.send({
14
+ tool_invocation: {
15
+ tool_name: args.toolName,
16
+ success: args.success,
17
+ latency_ms: args.latencyMs,
18
+ },
19
+ });
20
+ }
21
+ async logServerStart(flagUsage) {
22
+ await this.#sender.send({
23
+ server_start: {
24
+ flag_usage: flagUsage,
25
+ },
26
+ });
27
+ }
28
+ }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Google LLC
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+ import { logger } from '../logger.js';
7
+ export class ClearcutSender {
8
+ async send(event) {
9
+ logger('Telemetry event', JSON.stringify(event, null, 2));
10
+ }
11
+ }
@@ -0,0 +1,45 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Google LLC
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+ import { toSnakeCase } from '../utils/string.js';
7
+ /**
8
+ * Computes telemetry flag usage from parsed arguments and CLI options.
9
+ *
10
+ * Iterates over the defined CLI options to construct a payload:
11
+ * - Flag names are converted to snake_case (e.g. `browserUrl` -> `browser_url`).
12
+ * - A flag is logged as `{flag_name}_present` if:
13
+ * - It has no default value, OR
14
+ * - The provided value differs from the default value.
15
+ * - Boolean flags are logged with their literal value.
16
+ * - String flags with defined `choices` (Enums) are logged as their uppercase value.
17
+ */
18
+ export function computeFlagUsage(args, options) {
19
+ const usage = {};
20
+ for (const [flagName, config] of Object.entries(options)) {
21
+ const value = args[flagName];
22
+ const snakeCaseName = toSnakeCase(flagName);
23
+ // If there isn't a default value provided for the flag,
24
+ // we're going to log whether it's present on the args user
25
+ // provided or not. If there is a default value, we only log presence
26
+ // if the value differs from the default, implying explicit user intent.
27
+ if (!('default' in config) || value !== config.default) {
28
+ usage[`${snakeCaseName}_present`] = value !== undefined && value !== null;
29
+ }
30
+ if (config.type === 'boolean' && typeof value === 'boolean') {
31
+ // For boolean options, we're going to log the value directly.
32
+ usage[snakeCaseName] = value;
33
+ }
34
+ else if (config.type === 'string' &&
35
+ typeof value === 'string' &&
36
+ 'choices' in config &&
37
+ config.choices) {
38
+ // For enums, log the value as uppercase
39
+ // We're going to have an enum for such flags with choices represented
40
+ // as an `enum` where the keys of the enum will map to the uppercase `choice`.
41
+ usage[snakeCaseName] = value.toUpperCase();
42
+ }
43
+ }
44
+ return usage;
45
+ }
@@ -0,0 +1,27 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Google LLC
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+ // Enums
7
+ export var OsType;
8
+ (function (OsType) {
9
+ OsType[OsType["OS_TYPE_UNSPECIFIED"] = 0] = "OS_TYPE_UNSPECIFIED";
10
+ OsType[OsType["OS_TYPE_WINDOWS"] = 1] = "OS_TYPE_WINDOWS";
11
+ OsType[OsType["OS_TYPE_MACOS"] = 2] = "OS_TYPE_MACOS";
12
+ OsType[OsType["OS_TYPE_LINUX"] = 3] = "OS_TYPE_LINUX";
13
+ })(OsType || (OsType = {}));
14
+ export var ChromeChannel;
15
+ (function (ChromeChannel) {
16
+ ChromeChannel[ChromeChannel["CHROME_CHANNEL_UNSPECIFIED"] = 0] = "CHROME_CHANNEL_UNSPECIFIED";
17
+ ChromeChannel[ChromeChannel["CHROME_CHANNEL_CANARY"] = 1] = "CHROME_CHANNEL_CANARY";
18
+ ChromeChannel[ChromeChannel["CHROME_CHANNEL_DEV"] = 2] = "CHROME_CHANNEL_DEV";
19
+ ChromeChannel[ChromeChannel["CHROME_CHANNEL_BETA"] = 3] = "CHROME_CHANNEL_BETA";
20
+ ChromeChannel[ChromeChannel["CHROME_CHANNEL_STABLE"] = 4] = "CHROME_CHANNEL_STABLE";
21
+ })(ChromeChannel || (ChromeChannel = {}));
22
+ export var McpClient;
23
+ (function (McpClient) {
24
+ McpClient[McpClient["MCP_CLIENT_UNSPECIFIED"] = 0] = "MCP_CLIENT_UNSPECIFIED";
25
+ McpClient[McpClient["MCP_CLIENT_CLAUDE_CODE"] = 1] = "MCP_CLIENT_CLAUDE_CODE";
26
+ McpClient[McpClient["MCP_CLIENT_GEMINI_CLI"] = 2] = "MCP_CLIENT_GEMINI_CLI";
27
+ })(McpClient || (McpClient = {}));
@@ -421,7 +421,7 @@ SOFTWARE.
421
421
 
422
422
  Name: @modelcontextprotocol/sdk
423
423
  URL: https://modelcontextprotocol.io
424
- Version: 1.24.3
424
+ Version: 1.25.2
425
425
  License: MIT
426
426
 
427
427
  MIT License
@@ -635,14 +635,14 @@ SOFTWARE.
635
635
 
636
636
  Name: puppeteer-core
637
637
  URL: https://github.com/puppeteer/puppeteer/tree/main/packages/puppeteer-core
638
- Version: 24.33.0
638
+ Version: 24.35.0
639
639
  License: Apache-2.0
640
640
 
641
641
  -------------------- DEPENDENCY DIVIDER --------------------
642
642
 
643
643
  Name: @puppeteer/browsers
644
644
  URL: https://github.com/puppeteer/puppeteer/tree/main/packages/browsers
645
- Version: 2.11.0
645
+ Version: 2.11.1
646
646
  License: Apache-2.0
647
647
 
648
648
  -------------------- DEPENDENCY DIVIDER --------------------
@@ -1054,7 +1054,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
1054
1054
 
1055
1055
  Name: basic-ftp
1056
1056
  URL: https://github.com/patrickjuchli/basic-ftp.git
1057
- Version: 5.0.5
1057
+ Version: 5.1.0
1058
1058
  License: MIT
1059
1059
 
1060
1060
  Copyright (c) 2019 Patrick Juchli
@@ -1388,7 +1388,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
1388
1388
 
1389
1389
  Name: ws
1390
1390
  URL: https://github.com/websockets/ws
1391
- Version: 8.18.3
1391
+ Version: 8.19.0
1392
1392
  License: MIT
1393
1393
 
1394
1394
  Copyright (c) 2011 Einar Otto Stangvik <einaros@gmail.com>