chrome-devtools-mcp 0.20.3 → 0.21.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 (30) hide show
  1. package/README.md +16 -1
  2. package/build/src/McpContext.js +7 -0
  3. package/build/src/McpResponse.js +77 -3
  4. package/build/src/PageCollector.js +4 -4
  5. package/build/src/bin/chrome-devtools-mcp-cli-options.js +5 -0
  6. package/build/src/bin/chrome-devtools-mcp.js +1 -0
  7. package/build/src/bin/chrome-devtools.js +6 -3
  8. package/build/src/bin/cliDefinitions.js +1 -1
  9. package/build/src/daemon/daemon.js +2 -2
  10. package/build/src/daemon/utils.js +1 -0
  11. package/build/src/index.js +10 -0
  12. package/build/src/telemetry/ClearcutLogger.js +118 -1
  13. package/build/src/telemetry/types.js +5 -0
  14. package/build/src/third_party/THIRD_PARTY_NOTICES +1453 -536
  15. package/build/src/third_party/bundled-packages.json +4 -4
  16. package/build/src/third_party/devtools-formatter-worker.js +0 -2
  17. package/build/src/third_party/index.js +8724 -1970
  18. package/build/src/third_party/issue-descriptions/sharedDictionaryUseErrorCrossOriginNoCorsRequest.md +1 -0
  19. package/build/src/third_party/lighthouse-devtools-mcp-bundle.js +7494 -2395
  20. package/build/src/tools/categories.js +2 -0
  21. package/build/src/tools/inPage.js +84 -0
  22. package/build/src/tools/input.js +4 -1
  23. package/build/src/tools/lighthouse.js +1 -1
  24. package/build/src/tools/memory.js +2 -2
  25. package/build/src/tools/pages.js +6 -1
  26. package/build/src/tools/tools.js +2 -0
  27. package/build/src/version.js +1 -1
  28. package/package.json +6 -6
  29. package/build/src/third_party/issue-descriptions/sharedDictionaryUseErrorNoCorpCrossOriginNoCorsRequest.md +0 -3
  30. package/build/src/third_party/issue-descriptions/sharedDictionaryWriteErrorNoCorpCossOriginNoCorsRequest.md +0 -3
package/README.md CHANGED
@@ -27,6 +27,10 @@ allowing them to inspect, debug, and modify any data in the browser or DevTools.
27
27
  Avoid sharing sensitive or personal information that you don't want to share with
28
28
  MCP clients.
29
29
 
30
+ `chrome-devtools-mcp` officially supports Google Chrome and [Chrome for Testing](https://developer.chrome.com/blog/chrome-for-testing/) only.
31
+ Other Chromium-based browser may work, but this is not guaranteed, and you may encounter unexpected behavior. Use at your own discretion.
32
+ We are committed to providing fixes and support for the latest version of [Extended Stable Chrome](https://chromiumdash.appspot.com/schedule).
33
+
30
34
  Performance tools may send trace URLs to the Google CrUX API to fetch real-user
31
35
  experience data. This helps provide a holistic performance picture by
32
36
  presenting field data alongside lab data. This data is collected by the [Chrome
@@ -53,7 +57,7 @@ Collection is disabled if CHROME_DEVTOOLS_MCP_NO_USAGE_STATISTICS or CI env vari
53
57
 
54
58
  - [Node.js](https://nodejs.org/) v20.19 or a newer [latest maintenance LTS](https://github.com/nodejs/Release#release-schedule) version.
55
59
  - [Chrome](https://www.google.com/chrome/) current stable version or newer.
56
- - [npm](https://www.npmjs.com/).
60
+ - [npm](https://www.npmjs.com/)
57
61
 
58
62
  ## Getting started
59
63
 
@@ -194,6 +198,17 @@ startup_timeout_ms = 20_000
194
198
 
195
199
  </details>
196
200
 
201
+ <details>
202
+ <summary>Command Code</summary>
203
+
204
+ Use the Command Code CLI to add the Chrome DevTools MCP server (<a href="https://commandcode.ai/docs/mcp">MCP guide</a>):
205
+
206
+ ```bash
207
+ cmd mcp add chrome-devtools --scope user npx chrome-devtools-mcp@latest
208
+ ```
209
+
210
+ </details>
211
+
197
212
  <details>
198
213
  <summary>Copilot CLI</summary>
199
214
 
@@ -48,6 +48,7 @@ export class McpContext {
48
48
  #extensionRegistry = new ExtensionRegistry();
49
49
  #isRunningTrace = false;
50
50
  #screenRecorderData = null;
51
+ #inPageTools;
51
52
  #nextPageId = 1;
52
53
  #extensionPages = new WeakMap();
53
54
  #extensionServiceWorkerMap = new WeakMap();
@@ -328,6 +329,12 @@ export class McpContext {
328
329
  this.#selectedPage = newPage;
329
330
  this.#updateSelectedPageTimeouts();
330
331
  }
332
+ setInPageTools(toolGroup) {
333
+ this.#inPageTools = toolGroup;
334
+ }
335
+ getInPageTools() {
336
+ return this.#inPageTools;
337
+ }
331
338
  #updateSelectedPageTimeouts() {
332
339
  const page = this.#getSelectedMcpPage();
333
340
  // For waiters 5sec timeout should be sufficient.
@@ -12,6 +12,50 @@ import { DevTools } from './third_party/index.js';
12
12
  import { handleDialog } from './tools/pages.js';
13
13
  import { getInsightOutput, getTraceSummary } from './trace-processing/parse.js';
14
14
  import { paginate } from './utils/pagination.js';
15
+ async function getToolGroup(page) {
16
+ // Check if there is a `devtoolstooldiscovery` event listener
17
+ const windowHandle = await page.pptrPage.evaluateHandle(() => window);
18
+ // @ts-expect-error internal API
19
+ const client = page.pptrPage._client();
20
+ const { listeners } = await client.send('DOMDebugger.getEventListeners', {
21
+ objectId: windowHandle.remoteObject().objectId,
22
+ });
23
+ if (listeners.find(l => l.type === 'devtoolstooldiscovery') === undefined) {
24
+ return;
25
+ }
26
+ const toolGroup = await page.pptrPage.evaluate(() => {
27
+ return new Promise(resolve => {
28
+ const event = new CustomEvent('devtoolstooldiscovery');
29
+ // @ts-expect-error Adding custom property
30
+ event.respondWith = (toolGroup) => {
31
+ if (!window.__dtmcp) {
32
+ window.__dtmcp = {};
33
+ }
34
+ window.__dtmcp.toolGroup = toolGroup;
35
+ // When receiving a toolGroup for the first time, expose a simple execution helper
36
+ if (!window.__dtmcp.executeTool) {
37
+ window.__dtmcp.executeTool = async (toolName, args) => {
38
+ if (!window.__dtmcp?.toolGroup) {
39
+ throw new Error('No tools found on the page');
40
+ }
41
+ const tool = window.__dtmcp.toolGroup.tools.find(t => t.name === toolName);
42
+ if (!tool) {
43
+ throw new Error(`Tool ${toolName} not found`);
44
+ }
45
+ return await tool.execute(args);
46
+ };
47
+ }
48
+ resolve(toolGroup);
49
+ };
50
+ window.dispatchEvent(event);
51
+ // If the page does not synchronously call `event.respondWith`, return instead of timing out
52
+ setTimeout(() => {
53
+ resolve(undefined);
54
+ }, 0);
55
+ });
56
+ });
57
+ return toolGroup;
58
+ }
15
59
  export class McpResponse {
16
60
  #includePages = false;
17
61
  #includeExtensionServiceWorkers = false;
@@ -28,6 +72,7 @@ export class McpResponse {
28
72
  #networkRequestsOptions;
29
73
  #consoleDataOptions;
30
74
  #listExtensions;
75
+ #listInPageTools;
31
76
  #devToolsData;
32
77
  #tabId;
33
78
  #args;
@@ -59,6 +104,11 @@ export class McpResponse {
59
104
  setListExtensions() {
60
105
  this.#listExtensions = true;
61
106
  }
107
+ setListInPageTools() {
108
+ if (this.#args.categoryInPageTools) {
109
+ this.#listInPageTools = true;
110
+ }
111
+ }
62
112
  setIncludeNetworkRequests(value, options) {
63
113
  if (!value) {
64
114
  this.#networkRequestsOptions = undefined;
@@ -94,8 +144,8 @@ export class McpResponse {
94
144
  includePreservedMessages: options?.includePreservedMessages,
95
145
  };
96
146
  }
97
- attachNetworkRequest(reqid, options) {
98
- this.#attachedNetworkRequestId = reqid;
147
+ attachNetworkRequest(reqId, options) {
148
+ this.#attachedNetworkRequestId = reqId;
99
149
  this.#attachedNetworkRequestOptions = options;
100
150
  }
101
151
  attachConsoleMessage(msgid) {
@@ -223,7 +273,7 @@ export class McpResponse {
223
273
  elementIdResolver: context.resolveCdpElementId.bind(context, this.#page),
224
274
  });
225
275
  if (!formatter.isValid()) {
226
- throw new Error("Can't provide detals for the msgid " + consoleMessageStableId);
276
+ throw new Error("Can't provide details for the msgid " + consoleMessageStableId);
227
277
  }
228
278
  detailedConsoleMessage = formatter;
229
279
  }
@@ -232,6 +282,11 @@ export class McpResponse {
232
282
  if (this.#listExtensions) {
233
283
  extensions = context.listExtensions();
234
284
  }
285
+ let inPageTools;
286
+ if (this.#listInPageTools) {
287
+ inPageTools = await getToolGroup(context.getSelectedMcpPage());
288
+ context.setInPageTools(inPageTools);
289
+ }
235
290
  let consoleMessages;
236
291
  if (this.#consoleDataOptions?.include) {
237
292
  if (!this.#page) {
@@ -308,6 +363,7 @@ export class McpResponse {
308
363
  traceSummary: this.#attachedTraceSummary,
309
364
  extensions,
310
365
  lighthouseResult: this.#attachedLighthouseResult,
366
+ inPageTools,
311
367
  });
312
368
  }
313
369
  format(toolName, context, data) {
@@ -498,6 +554,24 @@ Call ${handleDialog.name} to handle it before continuing.`);
498
554
  response.push(extensionsMessage);
499
555
  }
500
556
  }
557
+ if (this.#listInPageTools) {
558
+ structuredContent.inPageTools = data.inPageTools ?? undefined;
559
+ response.push('## In-page tools');
560
+ if (!data.inPageTools || !data.inPageTools.tools) {
561
+ response.push('No in-page tools available.');
562
+ }
563
+ else {
564
+ const toolGroup = data.inPageTools;
565
+ response.push(`${toolGroup.name}: ${toolGroup.description}`);
566
+ response.push('Available tools:');
567
+ const toolDefinitionsMessage = toolGroup.tools
568
+ .map(tool => {
569
+ return `name="${tool.name}", description="${tool.description}", inputSchema=${JSON.stringify(tool.inputSchema)}`;
570
+ })
571
+ .join('\n');
572
+ response.push(toolDefinitionsMessage);
573
+ }
574
+ }
501
575
  if (this.#networkRequestsOptions?.include && data.networkRequests) {
502
576
  const requests = data.networkRequests;
503
577
  response.push('## Network requests');
@@ -198,10 +198,10 @@ class PageEventSubscriber {
198
198
  #resetIssueAggregator() {
199
199
  this.#issueManager = new FakeIssuesManager();
200
200
  if (this.#issueAggregator) {
201
- this.#issueAggregator.removeEventListener("AggregatedIssueUpdated" /* DevTools.IssueAggregatorEvents.AGGREGATED_ISSUE_UPDATED */, this.#onAggregatedissue);
201
+ this.#issueAggregator.removeEventListener("AggregatedIssueUpdated" /* DevTools.IssueAggregatorEvents.AGGREGATED_ISSUE_UPDATED */, this.#onAggregatedIssue);
202
202
  }
203
203
  this.#issueAggregator = new DevTools.IssueAggregator(this.#issueManager);
204
- this.#issueAggregator.addEventListener("AggregatedIssueUpdated" /* DevTools.IssueAggregatorEvents.AGGREGATED_ISSUE_UPDATED */, this.#onAggregatedissue);
204
+ this.#issueAggregator.addEventListener("AggregatedIssueUpdated" /* DevTools.IssueAggregatorEvents.AGGREGATED_ISSUE_UPDATED */, this.#onAggregatedIssue);
205
205
  }
206
206
  async subscribe() {
207
207
  this.#resetIssueAggregator();
@@ -222,13 +222,13 @@ class PageEventSubscriber {
222
222
  this.#session.off('Audits.issueAdded', this.#onIssueAdded);
223
223
  this.#session.off('Runtime.exceptionThrown', this.#onExceptionThrown);
224
224
  if (this.#issueAggregator) {
225
- this.#issueAggregator.removeEventListener("AggregatedIssueUpdated" /* DevTools.IssueAggregatorEvents.AGGREGATED_ISSUE_UPDATED */, this.#onAggregatedissue);
225
+ this.#issueAggregator.removeEventListener("AggregatedIssueUpdated" /* DevTools.IssueAggregatorEvents.AGGREGATED_ISSUE_UPDATED */, this.#onAggregatedIssue);
226
226
  }
227
227
  void this.#session.send('Audits.disable').catch(() => {
228
228
  // might fail.
229
229
  });
230
230
  }
231
- #onAggregatedissue = (event) => {
231
+ #onAggregatedIssue = (event) => {
232
232
  if (this.#seenIssues.has(event.data)) {
233
233
  return;
234
234
  }
@@ -197,6 +197,11 @@ export const cliOptions = {
197
197
  conflicts: ['browserUrl', 'autoConnect', 'wsEndpoint'],
198
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.',
199
199
  },
200
+ categoryInPageTools: {
201
+ type: 'boolean',
202
+ hidden: true,
203
+ describe: 'Set to true to enable tools exposed by the inspected page itself',
204
+ },
200
205
  performanceCrux: {
201
206
  type: 'boolean',
202
207
  default: true,
@@ -4,6 +4,7 @@
4
4
  * Copyright 2025 Google LLC
5
5
  * SPDX-License-Identifier: Apache-2.0
6
6
  */
7
+ process.title = 'chrome-devtools-mcp';
7
8
  import { version } from 'node:process';
8
9
  const [major, minor] = version.substring(1).split('.').map(Number);
9
10
  if (major === 20 && minor < 19) {
@@ -4,6 +4,7 @@
4
4
  * Copyright 2026 Google LLC
5
5
  * SPDX-License-Identifier: Apache-2.0
6
6
  */
7
+ process.title = 'chrome-devtools';
7
8
  import process from 'node:process';
8
9
  import { startDaemon, stopDaemon, sendCommand, handleResponse, } from '../daemon/client.js';
9
10
  import { isDaemonRunning, serializeArgs } from '../daemon/utils.js';
@@ -26,7 +27,7 @@ delete startCliOptions.autoConnect;
26
27
  // Missing CLI serialization.
27
28
  delete startCliOptions.viewport;
28
29
  // CLI is generated based on the default tool definitions. To enable conditional
29
- // tools, they needs to be enabled during CLI generation.
30
+ // tools, they need to be enabled during CLI generation.
30
31
  delete startCliOptions.experimentalPageIdRouting;
31
32
  delete startCliOptions.experimentalVision;
32
33
  delete startCliOptions.experimentalInteropTools;
@@ -42,9 +43,11 @@ if (!('default' in cliOptions.headless)) {
42
43
  throw new Error('headless cli option unexpectedly does not have a default');
43
44
  }
44
45
  if ('default' in cliOptions.isolated) {
45
- throw new Error('headless cli option unexpectedly does not have a default');
46
+ throw new Error('isolated cli option unexpectedly has a default');
46
47
  }
47
48
  startCliOptions.headless.default = true;
49
+ startCliOptions.isolated.description =
50
+ 'If specified, creates a temporary user-data-dir that is automatically cleaned up after the browser is closed. Defaults to true unless userDataDir is provided.';
48
51
  const y = yargs(hideBin(process.argv))
49
52
  .scriptName('chrome-devtools')
50
53
  .showHelpOnFail(true)
@@ -63,7 +66,7 @@ y.command('start', 'Start or restart chrome-devtools-mcp', y => y
63
66
  await stopDaemon();
64
67
  }
65
68
  // Defaults but we do not want to affect the yargs conflict resolution.
66
- if (argv.isolated === undefined) {
69
+ if (argv.isolated === undefined && argv.userDataDir === undefined) {
67
70
  argv.isolated = true;
68
71
  }
69
72
  if (argv.headless === undefined) {
@@ -503,7 +503,7 @@ export const commands = {
503
503
  },
504
504
  },
505
505
  take_memory_snapshot: {
506
- description: 'Capture a memory heapsnapshot of the currently selected page to memory leak debugging',
506
+ description: 'Capture a heap snapshot of the currently selected page. Use to analyze the memory distribution of JavaScript objects and debug memory leaks.',
507
507
  category: 'Performance',
508
508
  args: {
509
509
  filePath: {
@@ -11,7 +11,7 @@ import process from 'node:process';
11
11
  import { logger } from '../logger.js';
12
12
  import { Client, PipeTransport, StdioClientTransport, } from '../third_party/index.js';
13
13
  import { VERSION } from '../version.js';
14
- import { getDaemonPid, getPidFilePath, getSocketPath, INDEX_SCRIPT_PATH, IS_WINDOWS, isDaemonRunning, } from './utils.js';
14
+ import { DAEMON_CLIENT_NAME, getDaemonPid, getPidFilePath, getSocketPath, INDEX_SCRIPT_PATH, IS_WINDOWS, isDaemonRunning, } from './utils.js';
15
15
  const pid = getDaemonPid();
16
16
  if (isDaemonRunning(pid)) {
17
17
  logger('Another daemon process is running.');
@@ -42,7 +42,7 @@ async function setupMCPClient() {
42
42
  env: process.env,
43
43
  });
44
44
  mcpClient = new Client({
45
- name: 'chrome-devtools-cli-daemon',
45
+ name: DAEMON_CLIENT_NAME,
46
46
  version: VERSION,
47
47
  }, {
48
48
  capabilities: {},
@@ -11,6 +11,7 @@ import { logger } from '../logger.js';
11
11
  export const DAEMON_SCRIPT_PATH = path.join(import.meta.dirname, 'daemon.js');
12
12
  export const INDEX_SCRIPT_PATH = path.join(import.meta.dirname, '..', 'bin', 'chrome-devtools-mcp.js');
13
13
  const APP_NAME = 'chrome-devtools-mcp';
14
+ export const DAEMON_CLIENT_NAME = 'chrome-devtools-cli-daemon';
14
15
  // Using these paths due to strict limits on the POSIX socket path length.
15
16
  export function getSocketPath() {
16
17
  const uid = os.userInfo().uid;
@@ -36,6 +36,12 @@ export async function createMcpServer(serverArgs, options) {
36
36
  server.server.setRequestHandler(SetLevelRequestSchema, () => {
37
37
  return {};
38
38
  });
39
+ server.server.oninitialized = () => {
40
+ const clientName = server.server.getClientVersion()?.name;
41
+ if (clientName) {
42
+ clearcutLogger?.setClientName(clientName);
43
+ }
44
+ };
39
45
  let context;
40
46
  async function getContext() {
41
47
  const chromeArgs = (serverArgs.chromeArg ?? []).map(String);
@@ -98,6 +104,10 @@ export async function createMcpServer(serverArgs, options) {
98
104
  !serverArgs.categoryExtensions) {
99
105
  return;
100
106
  }
107
+ if (tool.annotations.category === ToolCategory.IN_PAGE &&
108
+ !serverArgs.categoryInPageTools) {
109
+ return;
110
+ }
101
111
  if (tool.annotations.conditions?.includes('computerVision') &&
102
112
  !serverArgs.experimentalVision) {
103
113
  return;
@@ -4,11 +4,99 @@
4
4
  * SPDX-License-Identifier: Apache-2.0
5
5
  */
6
6
  import process from 'node:process';
7
+ import { DAEMON_CLIENT_NAME } from '../daemon/utils.js';
7
8
  import { logger } from '../logger.js';
8
9
  import { FilePersistence } from './persistence.js';
9
- import { WatchdogMessageType, OsType } from './types.js';
10
+ import { McpClient, WatchdogMessageType, OsType, } from './types.js';
10
11
  import { WatchdogClient } from './WatchdogClient.js';
11
12
  const MS_PER_DAY = 24 * 60 * 60 * 1000;
13
+ const PARAM_BLOCKLIST = new Set(['uid']);
14
+ const SUPPORTED_ZOD_TYPES = [
15
+ 'ZodString',
16
+ 'ZodNumber',
17
+ 'ZodBoolean',
18
+ 'ZodArray',
19
+ 'ZodEnum',
20
+ ];
21
+ function isZodType(type) {
22
+ return SUPPORTED_ZOD_TYPES.includes(type);
23
+ }
24
+ function getZodType(zodType) {
25
+ const def = zodType._def;
26
+ const typeName = def.typeName;
27
+ if (typeName === 'ZodOptional' ||
28
+ typeName === 'ZodDefault' ||
29
+ typeName === 'ZodNullable') {
30
+ return getZodType(def.innerType);
31
+ }
32
+ if (typeName === 'ZodEffects') {
33
+ return getZodType(def.schema);
34
+ }
35
+ if (isZodType(typeName)) {
36
+ return typeName;
37
+ }
38
+ throw new Error(`Unsupported zod type for tool parameter: ${typeName}`);
39
+ }
40
+ function transformName(zodType, name) {
41
+ if (zodType === 'ZodString') {
42
+ return `${name}_length`;
43
+ }
44
+ else if (zodType === 'ZodArray') {
45
+ return `${name}_count`;
46
+ }
47
+ else {
48
+ return name;
49
+ }
50
+ }
51
+ function transformValue(zodType, value) {
52
+ if (zodType === 'ZodString') {
53
+ return value.length;
54
+ }
55
+ else if (zodType === 'ZodArray') {
56
+ return value.length;
57
+ }
58
+ else {
59
+ return value;
60
+ }
61
+ }
62
+ function hasEquivalentType(zodType, value) {
63
+ if (zodType === 'ZodString') {
64
+ return typeof value === 'string';
65
+ }
66
+ else if (zodType === 'ZodArray') {
67
+ return Array.isArray(value);
68
+ }
69
+ else if (zodType === 'ZodNumber') {
70
+ return typeof value === 'number';
71
+ }
72
+ else if (zodType === 'ZodBoolean') {
73
+ return typeof value === 'boolean';
74
+ }
75
+ else if (zodType === 'ZodEnum') {
76
+ return (typeof value === 'string' ||
77
+ typeof value === 'number' ||
78
+ typeof value === 'boolean');
79
+ }
80
+ else {
81
+ return false;
82
+ }
83
+ }
84
+ export function sanitizeParams(params, schema) {
85
+ const transformed = {};
86
+ for (const [name, value] of Object.entries(params)) {
87
+ if (PARAM_BLOCKLIST.has(name)) {
88
+ continue;
89
+ }
90
+ const zodType = getZodType(schema[name]);
91
+ if (!hasEquivalentType(zodType, value)) {
92
+ throw new Error(`parameter ${name} has type ${zodType} but value ${value} is not of equivalent type`);
93
+ }
94
+ const transformedName = transformName(zodType, name);
95
+ const transformedValue = transformValue(zodType, value);
96
+ transformed[transformedName] = transformedValue;
97
+ }
98
+ return transformed;
99
+ }
12
100
  function detectOsType() {
13
101
  switch (process.platform) {
14
102
  case 'win32':
@@ -24,6 +112,7 @@ function detectOsType() {
24
112
  export class ClearcutLogger {
25
113
  #persistence;
26
114
  #watchdog;
115
+ #mcpClient;
27
116
  constructor(options) {
28
117
  this.#persistence = options.persistence ?? new FilePersistence();
29
118
  this.#watchdog =
@@ -37,11 +126,37 @@ export class ClearcutLogger {
37
126
  clearcutForceFlushIntervalMs: options.clearcutForceFlushIntervalMs,
38
127
  clearcutIncludePidHeader: options.clearcutIncludePidHeader,
39
128
  });
129
+ this.#mcpClient = McpClient.MCP_CLIENT_UNSPECIFIED;
130
+ }
131
+ setClientName(clientName) {
132
+ const lowerName = clientName.toLowerCase();
133
+ if (lowerName.includes('claude')) {
134
+ this.#mcpClient = McpClient.MCP_CLIENT_CLAUDE_CODE;
135
+ }
136
+ else if (lowerName.includes('gemini')) {
137
+ this.#mcpClient = McpClient.MCP_CLIENT_GEMINI_CLI;
138
+ }
139
+ else if (clientName === DAEMON_CLIENT_NAME) {
140
+ this.#mcpClient = McpClient.MCP_CLIENT_DT_MCP_CLI;
141
+ }
142
+ else if (lowerName.includes('openclaw')) {
143
+ this.#mcpClient = McpClient.MCP_CLIENT_OPENCLAW;
144
+ }
145
+ else if (lowerName.includes('codex')) {
146
+ this.#mcpClient = McpClient.MCP_CLIENT_CODEX;
147
+ }
148
+ else if (lowerName.includes('antigravity')) {
149
+ this.#mcpClient = McpClient.MCP_CLIENT_ANTIGRAVITY;
150
+ }
151
+ else {
152
+ this.#mcpClient = McpClient.MCP_CLIENT_OTHER;
153
+ }
40
154
  }
41
155
  async logToolInvocation(args) {
42
156
  this.#watchdog.send({
43
157
  type: WatchdogMessageType.LOG_EVENT,
44
158
  payload: {
159
+ mcp_client: this.#mcpClient,
45
160
  tool_invocation: {
46
161
  tool_name: args.toolName,
47
162
  success: args.success,
@@ -54,6 +169,7 @@ export class ClearcutLogger {
54
169
  this.#watchdog.send({
55
170
  type: WatchdogMessageType.LOG_EVENT,
56
171
  payload: {
172
+ mcp_client: this.#mcpClient,
57
173
  server_start: {
58
174
  flag_usage: flagUsage,
59
175
  },
@@ -74,6 +190,7 @@ export class ClearcutLogger {
74
190
  this.#watchdog.send({
75
191
  type: WatchdogMessageType.LOG_EVENT,
76
192
  payload: {
193
+ mcp_client: this.#mcpClient,
77
194
  daily_active: {
78
195
  days_since_last_active: daysSince,
79
196
  },
@@ -24,6 +24,11 @@ export var McpClient;
24
24
  McpClient[McpClient["MCP_CLIENT_UNSPECIFIED"] = 0] = "MCP_CLIENT_UNSPECIFIED";
25
25
  McpClient[McpClient["MCP_CLIENT_CLAUDE_CODE"] = 1] = "MCP_CLIENT_CLAUDE_CODE";
26
26
  McpClient[McpClient["MCP_CLIENT_GEMINI_CLI"] = 2] = "MCP_CLIENT_GEMINI_CLI";
27
+ McpClient[McpClient["MCP_CLIENT_DT_MCP_CLI"] = 4] = "MCP_CLIENT_DT_MCP_CLI";
28
+ McpClient[McpClient["MCP_CLIENT_OPENCLAW"] = 5] = "MCP_CLIENT_OPENCLAW";
29
+ McpClient[McpClient["MCP_CLIENT_CODEX"] = 6] = "MCP_CLIENT_CODEX";
30
+ McpClient[McpClient["MCP_CLIENT_ANTIGRAVITY"] = 7] = "MCP_CLIENT_ANTIGRAVITY";
31
+ McpClient[McpClient["MCP_CLIENT_OTHER"] = 3] = "MCP_CLIENT_OTHER";
27
32
  })(McpClient || (McpClient = {}));
28
33
  // IPC types for messages between the main process and the
29
34
  // telemetry watchdog process.