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.
- package/README.md +16 -1
- package/build/src/McpContext.js +7 -0
- package/build/src/McpResponse.js +77 -3
- package/build/src/PageCollector.js +4 -4
- package/build/src/bin/chrome-devtools-mcp-cli-options.js +5 -0
- package/build/src/bin/chrome-devtools-mcp.js +1 -0
- package/build/src/bin/chrome-devtools.js +6 -3
- package/build/src/bin/cliDefinitions.js +1 -1
- package/build/src/daemon/daemon.js +2 -2
- package/build/src/daemon/utils.js +1 -0
- package/build/src/index.js +10 -0
- package/build/src/telemetry/ClearcutLogger.js +118 -1
- package/build/src/telemetry/types.js +5 -0
- package/build/src/third_party/THIRD_PARTY_NOTICES +1453 -536
- package/build/src/third_party/bundled-packages.json +4 -4
- package/build/src/third_party/devtools-formatter-worker.js +0 -2
- package/build/src/third_party/index.js +8724 -1970
- package/build/src/third_party/issue-descriptions/sharedDictionaryUseErrorCrossOriginNoCorsRequest.md +1 -0
- package/build/src/third_party/lighthouse-devtools-mcp-bundle.js +7494 -2395
- package/build/src/tools/categories.js +2 -0
- package/build/src/tools/inPage.js +84 -0
- package/build/src/tools/input.js +4 -1
- package/build/src/tools/lighthouse.js +1 -1
- package/build/src/tools/memory.js +2 -2
- package/build/src/tools/pages.js +6 -1
- package/build/src/tools/tools.js +2 -0
- package/build/src/version.js +1 -1
- package/package.json +6 -6
- package/build/src/third_party/issue-descriptions/sharedDictionaryUseErrorNoCorpCrossOriginNoCorsRequest.md +0 -3
- 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
|
|
package/build/src/McpContext.js
CHANGED
|
@@ -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.
|
package/build/src/McpResponse.js
CHANGED
|
@@ -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(
|
|
98
|
-
this.#attachedNetworkRequestId =
|
|
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
|
|
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.#
|
|
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.#
|
|
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.#
|
|
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
|
-
#
|
|
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 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
|
|
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('
|
|
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
|
|
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:
|
|
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;
|
package/build/src/index.js
CHANGED
|
@@ -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.
|