opera-devtools-mcp 0.6.0 → 0.7.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/McpContext.js +78 -41
- package/build/src/McpPage.js +5 -1
- package/build/src/ToolHandler.js +45 -74
- package/build/src/bin/chrome-devtools-mcp-main.js +4 -4
- package/build/src/bin/chrome-devtools.js +29 -30
- package/build/src/bin/opera-devtools-cli-options.js +1 -1
- package/build/src/bin/opera-devtools-mcp-cli-options.js +1 -1
- package/build/src/browser.js +5 -16
- package/build/src/config/browser-options.js +126 -0
- package/build/src/config/category-options.js +81 -0
- package/build/src/{bin/chrome-devtools-cli-options.js → config/cli-options.js} +392 -26
- package/build/src/{bin/chrome-devtools-mcp-cli-options.js → config/mcp-options.js} +143 -164
- package/build/src/daemon/client.js +9 -0
- package/build/src/daemon/utils.js +6 -0
- package/build/src/devtools/DevtoolsUtils.js +27 -21
- package/build/src/formatters/NetworkFormatter.js +5 -2
- package/build/src/index.js +166 -100
- package/build/src/processors/HeapSnapshotManager.js +12 -0
- package/build/src/telemetry/ClearcutLogger.js +19 -6
- package/build/src/telemetry/transformation.js +4 -0
- package/build/src/telemetry/types.js +4 -0
- package/build/src/third_party/THIRD_PARTY_NOTICES +5 -5
- package/build/src/third_party/bundled-packages.json +3 -3
- package/build/src/third_party/devtools-formatter-worker.js +22 -0
- package/build/src/third_party/devtools-heap-snapshot-worker.js +100 -20
- package/build/src/third_party/index.js +15459 -14256
- package/build/src/third_party/issue-descriptions/federatedAuthRequestAccountsBlockedByConnectionAllowlist.md +1 -0
- package/build/src/third_party/issue-descriptions/federatedAuthRequestConfigBlockedByConnectionAllowlist.md +1 -0
- package/build/src/third_party/issue-descriptions/federatedAuthRequestIdTokenBlockedByConnectionAllowlist.md +1 -0
- package/build/src/third_party/issue-descriptions/federatedAuthRequestWellKnownBlockedByConnectionAllowlist.md +1 -0
- package/build/src/tools/ToolDefinition.js +15 -0
- package/build/src/tools/categories.js +0 -6
- package/build/src/tools/comments.js +90 -0
- package/build/src/tools/console.js +1 -1
- package/build/src/tools/emulation.js +1 -1
- package/build/src/tools/extensions.js +1 -1
- package/build/src/tools/memory.js +60 -6
- package/build/src/tools/network.js +2 -2
- package/build/src/tools/pages.js +26 -15
- package/build/src/tools/performance.js +4 -3
- package/build/src/tools/screencast.js +3 -2
- package/build/src/tools/screenshot.js +39 -8
- package/build/src/tools/script.js +17 -4
- package/build/src/tools/slim/tools.js +41 -33
- package/build/src/tools/snapshot.js +1 -1
- package/build/src/tools/tools.js +2 -0
- package/build/src/utils/WaitForHelper.js +12 -1
- package/build/src/utils/bytes.js +105 -0
- package/build/src/utils/url.js +73 -0
- package/build/src/version.js +1 -1
- package/package.json +23 -4
package/build/src/index.js
CHANGED
|
@@ -5,21 +5,22 @@
|
|
|
5
5
|
*
|
|
6
6
|
* Modified by Opera Software AS.
|
|
7
7
|
*/
|
|
8
|
+
import path from 'node:path';
|
|
9
|
+
import { pathToFileURL } from 'node:url';
|
|
8
10
|
import { ensureBrowserConnected, ensureBrowserLaunched } from './browser.js';
|
|
9
11
|
import { loadIssueDescriptions } from './devtools/issueDescriptions.js';
|
|
10
12
|
import { McpContext } from './McpContext.js';
|
|
11
13
|
import { BROWSER_EXPOSURE_DISCLAIMER, showUsageStatisticsDisclaimer, } from './opera/policy.js';
|
|
12
|
-
import { createOperaToolHooks } from './opera/toolHandlerHooks.js';
|
|
13
14
|
import { buildLaunchOptions } from './opera/browserLaunch.js';
|
|
15
|
+
import { createOperaToolHooks, } from './opera/toolHandlerHooks.js';
|
|
14
16
|
import { ClearcutLogger } from './telemetry/ClearcutLogger.js';
|
|
15
17
|
import { FilePersistence } from './telemetry/persistence.js';
|
|
16
|
-
import { McpServer, SetLevelRequestSchema, ListRootsResultSchema, RootsListChangedNotificationSchema, } from './third_party/index.js';
|
|
18
|
+
import { McpServer as SdkMcpServer, SetLevelRequestSchema, ListRootsResultSchema, RootsListChangedNotificationSchema, Mutex, puppeteer, } from './third_party/index.js';
|
|
17
19
|
import { ToolHandler } from './ToolHandler.js';
|
|
18
20
|
import { createTools } from './tools/tools.js';
|
|
19
21
|
import { logger } from './utils/logger.js';
|
|
20
|
-
import { Mutex } from './third_party/index.js';
|
|
21
22
|
import { VERSION } from './version.js';
|
|
22
|
-
|
|
23
|
+
puppeteer.setFollowSymlinks(false);
|
|
23
24
|
/**
|
|
24
25
|
* Timeout for a `roots/list` that a tool call is waiting on, matching the 5s
|
|
25
26
|
* default used for page operations. `getContext()` awaits it while
|
|
@@ -29,84 +30,151 @@ export { buildFlag } from './ToolHandler.js';
|
|
|
29
30
|
* slow client sends late still land.
|
|
30
31
|
*/
|
|
31
32
|
const ROOTS_REQUEST_TIMEOUT = 5_000;
|
|
32
|
-
export
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
33
|
+
export class McpServer {
|
|
34
|
+
server;
|
|
35
|
+
#serverArgs;
|
|
36
|
+
#options;
|
|
37
|
+
#context;
|
|
38
|
+
/**
|
|
39
|
+
* Client roots stay valid across browser reconnects and only the client can
|
|
40
|
+
* invalidate them through a `roots/list_changed` notification. CLI-configured
|
|
41
|
+
* roots are read from `#serverArgs` when combining roots.
|
|
42
|
+
*/
|
|
43
|
+
#lastClientRoots;
|
|
44
|
+
#toolMutex = new Mutex();
|
|
45
|
+
#operaHooks;
|
|
46
|
+
constructor(serverArgs, options = {}) {
|
|
47
|
+
this.#serverArgs = serverArgs;
|
|
48
|
+
this.#options = options;
|
|
49
|
+
this.#operaHooks = createOperaToolHooks({
|
|
50
|
+
serverArgs: this.#serverArgs,
|
|
51
|
+
logFile: this.#options.logFile,
|
|
52
|
+
resetContext: () => {
|
|
53
|
+
this.#context?.dispose();
|
|
54
|
+
this.#context = undefined;
|
|
55
|
+
},
|
|
43
56
|
});
|
|
57
|
+
if (this.#serverArgs.usageStatistics) {
|
|
58
|
+
ClearcutLogger.initialize({
|
|
59
|
+
persistence: new FilePersistence(),
|
|
60
|
+
logFile: this.#serverArgs.logFile,
|
|
61
|
+
appVersion: VERSION,
|
|
62
|
+
clearcutEndpoint: this.#serverArgs.clearcutEndpoint,
|
|
63
|
+
clearcutForceFlushIntervalMs: this.#serverArgs.clearcutForceFlushIntervalMs,
|
|
64
|
+
clearcutIncludePidHeader: this.#serverArgs.clearcutIncludePidHeader,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
this.server = new SdkMcpServer({
|
|
68
|
+
name: 'chrome_devtools',
|
|
69
|
+
title: 'Chrome DevTools MCP server',
|
|
70
|
+
version: VERSION,
|
|
71
|
+
}, { capabilities: { logging: {} } });
|
|
72
|
+
this.server.server.setRequestHandler(SetLevelRequestSchema, () => {
|
|
73
|
+
return {};
|
|
74
|
+
});
|
|
75
|
+
this.server.server.oninitialized = () => {
|
|
76
|
+
const clientName = this.server.server.getClientVersion()?.name;
|
|
77
|
+
if (clientName) {
|
|
78
|
+
ClearcutLogger.get()?.setClientName(clientName);
|
|
79
|
+
}
|
|
80
|
+
if (this.server.server.getClientCapabilities()?.roots) {
|
|
81
|
+
void this.#updateRoots();
|
|
82
|
+
this.server.server.setNotificationHandler(RootsListChangedNotificationSchema, () => {
|
|
83
|
+
void this.#updateRoots();
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
else if (!this.#serverArgs.allowUnrestrictedPaths &&
|
|
87
|
+
(this.#serverArgs.filesystemRoot ?? []).length === 0) {
|
|
88
|
+
console.warn('[chrome-devtools-mcp] The connecting client did not negotiate the MCP roots ' +
|
|
89
|
+
'capability. File-writing tools will be restricted to the OS temp directory. ' +
|
|
90
|
+
'To restore the previous unrestricted behavior, start the server with ' +
|
|
91
|
+
'--allow-unrestricted-paths.');
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
async connect(transport) {
|
|
96
|
+
return await this.server.connect(transport);
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Closes the MCP connection and disposes internal context/listeners.
|
|
100
|
+
*/
|
|
101
|
+
async close() {
|
|
102
|
+
this.#context?.dispose();
|
|
103
|
+
this.#context = undefined;
|
|
104
|
+
await this.server.close();
|
|
105
|
+
}
|
|
106
|
+
[Symbol.dispose]() {
|
|
107
|
+
this.close().catch(() => {
|
|
108
|
+
// TODO: wire up the logger
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
async [Symbol.asyncDispose]() {
|
|
112
|
+
await this.close();
|
|
113
|
+
}
|
|
114
|
+
static async from(serverArgs, options = {}) {
|
|
115
|
+
const server = new McpServer(serverArgs, options);
|
|
116
|
+
await server.#init();
|
|
117
|
+
return server;
|
|
118
|
+
}
|
|
119
|
+
async #init() {
|
|
120
|
+
const tools = createTools(this.#serverArgs);
|
|
121
|
+
for (const tool of tools) {
|
|
122
|
+
this.#registerTool(tool);
|
|
123
|
+
}
|
|
124
|
+
await loadIssueDescriptions();
|
|
44
125
|
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
126
|
+
#combinedRoots() {
|
|
127
|
+
const configuredRoots = (this.#serverArgs.allowUnrestrictedPaths
|
|
128
|
+
? []
|
|
129
|
+
: (this.#serverArgs.filesystemRoot ?? [])).map(root => {
|
|
130
|
+
const rootPath = path.resolve(String(root));
|
|
131
|
+
return {
|
|
132
|
+
uri: pathToFileURL(rootPath).href,
|
|
133
|
+
name: path.basename(rootPath) || rootPath,
|
|
134
|
+
};
|
|
135
|
+
});
|
|
136
|
+
if (configuredRoots.length === 0 && this.#lastClientRoots === undefined) {
|
|
137
|
+
return undefined;
|
|
138
|
+
}
|
|
139
|
+
return [...configuredRoots, ...(this.#lastClientRoots ?? [])];
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* `timeout` is only passed where a tool call is waiting on the result – the
|
|
143
|
+
* background refreshes below block nobody, so bounding them would just discard
|
|
144
|
+
* roots a slow client was about to send
|
|
145
|
+
*/
|
|
146
|
+
async #updateRoots(timeout) {
|
|
147
|
+
if (!this.server.server.getClientCapabilities()?.roots) {
|
|
62
148
|
return;
|
|
63
149
|
}
|
|
64
150
|
try {
|
|
65
|
-
const roots = await server.server.request({ method: 'roots/list' }, ListRootsResultSchema, timeout === undefined ? undefined : { timeout });
|
|
66
|
-
|
|
67
|
-
context?.setRoots(
|
|
151
|
+
const roots = await this.server.server.request({ method: 'roots/list' }, ListRootsResultSchema, timeout === undefined ? undefined : { timeout });
|
|
152
|
+
this.#lastClientRoots = roots.roots;
|
|
153
|
+
this.#context?.setRoots(this.#combinedRoots());
|
|
68
154
|
}
|
|
69
155
|
catch (e) {
|
|
70
156
|
logger?.('Failed to list roots', e);
|
|
71
157
|
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
const
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
}
|
|
78
|
-
if (server.server.getClientCapabilities()?.roots) {
|
|
79
|
-
void updateRoots();
|
|
80
|
-
server.server.setNotificationHandler(RootsListChangedNotificationSchema, () => {
|
|
81
|
-
void updateRoots();
|
|
82
|
-
});
|
|
83
|
-
}
|
|
84
|
-
else if (!serverArgs.allowUnrestrictedPaths) {
|
|
85
|
-
console.warn('[chrome-devtools-mcp] The connecting client did not negotiate the MCP roots ' +
|
|
86
|
-
'capability. File-writing tools will be restricted to the OS temp directory. ' +
|
|
87
|
-
'To restore the previous unrestricted behavior, start the server with ' +
|
|
88
|
-
'--allow-unrestricted-paths.');
|
|
89
|
-
}
|
|
90
|
-
};
|
|
91
|
-
let context;
|
|
92
|
-
async function getContext() {
|
|
93
|
-
const devtools = serverArgs.experimentalDevtools ?? false;
|
|
94
|
-
const blocklist = serverArgs.blockedUrlPattern
|
|
95
|
-
? serverArgs.blockedUrlPattern.map(String)
|
|
158
|
+
}
|
|
159
|
+
async #getContext() {
|
|
160
|
+
const devtools = this.#serverArgs.experimentalDevtools ?? false;
|
|
161
|
+
const blocklist = this.#serverArgs.blockedUrlPattern
|
|
162
|
+
? this.#serverArgs.blockedUrlPattern.map(String)
|
|
96
163
|
: undefined;
|
|
97
|
-
const allowlist = serverArgs.allowedUrlPattern
|
|
98
|
-
? serverArgs.allowedUrlPattern.map(String)
|
|
164
|
+
const allowlist = this.#serverArgs.allowedUrlPattern
|
|
165
|
+
? this.#serverArgs.allowedUrlPattern.map(String)
|
|
99
166
|
: undefined;
|
|
100
|
-
const
|
|
167
|
+
const channel = this.#serverArgs.channel;
|
|
168
|
+
const browser = this.#serverArgs.browserUrl ||
|
|
169
|
+
this.#serverArgs.wsEndpoint ||
|
|
170
|
+
this.#serverArgs.autoConnect
|
|
101
171
|
? await ensureBrowserConnected({
|
|
102
|
-
browserURL: serverArgs.browserUrl,
|
|
103
|
-
wsEndpoint: serverArgs.wsEndpoint,
|
|
104
|
-
wsHeaders: serverArgs.wsHeaders,
|
|
172
|
+
browserURL: this.#serverArgs.browserUrl,
|
|
173
|
+
wsEndpoint: this.#serverArgs.wsEndpoint,
|
|
174
|
+
wsHeaders: this.#serverArgs.wsHeaders,
|
|
105
175
|
// Important: only pass channel, if autoConnect is true.
|
|
106
|
-
channel: serverArgs.autoConnect
|
|
107
|
-
|
|
108
|
-
: undefined,
|
|
109
|
-
userDataDir: serverArgs.userDataDir,
|
|
176
|
+
channel: this.#serverArgs.autoConnect ? channel : undefined,
|
|
177
|
+
userDataDir: this.#serverArgs.userDataDir,
|
|
110
178
|
devtools,
|
|
111
179
|
blocklist,
|
|
112
180
|
allowlist,
|
|
@@ -114,52 +182,45 @@ export async function createMcpServer(serverArgs, options) {
|
|
|
114
182
|
: await ensureBrowserLaunched(
|
|
115
183
|
// Pass the already-derived values so the launched browser uses the
|
|
116
184
|
// exact same flags as the connected branch and `McpContext`.
|
|
117
|
-
buildLaunchOptions(serverArgs, options.logFile, {
|
|
185
|
+
buildLaunchOptions(this.#serverArgs, this.#options.logFile, {
|
|
118
186
|
devtools,
|
|
119
187
|
blocklist,
|
|
120
188
|
allowlist,
|
|
121
189
|
}));
|
|
122
|
-
if (context?.browser !== browser) {
|
|
123
|
-
context?.dispose();
|
|
124
|
-
context = await McpContext.from(browser, logger, {
|
|
190
|
+
if (this.#context?.browser !== browser) {
|
|
191
|
+
this.#context?.dispose();
|
|
192
|
+
this.#context = await McpContext.from(browser, logger, {
|
|
125
193
|
experimentalDevToolsDebugging: devtools,
|
|
126
|
-
experimentalIncludeAllPages: serverArgs.experimentalIncludeAllPages,
|
|
127
|
-
performanceCrux: serverArgs.performanceCrux,
|
|
194
|
+
experimentalIncludeAllPages: this.#serverArgs.experimentalIncludeAllPages,
|
|
195
|
+
performanceCrux: this.#serverArgs.performanceCrux,
|
|
196
|
+
sourceMaps: this.#serverArgs.sourceMaps,
|
|
128
197
|
allowList: allowlist,
|
|
129
198
|
blocklist: blocklist,
|
|
130
|
-
allowUnrestrictedPaths: serverArgs.allowUnrestrictedPaths,
|
|
199
|
+
allowUnrestrictedPaths: this.#serverArgs.allowUnrestrictedPaths,
|
|
131
200
|
// Surfaces a one-time note in the next response after a reconnect.
|
|
132
|
-
reconnected: context !== undefined,
|
|
201
|
+
reconnected: this.#context !== undefined,
|
|
202
|
+
categoryExtensions: this.#serverArgs.categoryExtensions,
|
|
133
203
|
});
|
|
134
|
-
|
|
204
|
+
this.#context.setRoots(this.#combinedRoots());
|
|
205
|
+
if (this.#lastClientRoots === undefined) {
|
|
135
206
|
// Nothing listed yet, so this call has to wait – bounded, since it is
|
|
136
207
|
// holding the tool mutex, and a later background refresh still lands
|
|
137
|
-
await updateRoots(ROOTS_REQUEST_TIMEOUT);
|
|
208
|
+
await this.#updateRoots(ROOTS_REQUEST_TIMEOUT);
|
|
138
209
|
}
|
|
139
210
|
else {
|
|
140
211
|
// Carry the known roots over and refresh out of band, so a reconnect
|
|
141
212
|
// never pays for a client round-trip
|
|
142
|
-
|
|
143
|
-
void updateRoots();
|
|
213
|
+
void this.#updateRoots();
|
|
144
214
|
}
|
|
145
215
|
}
|
|
146
|
-
return context;
|
|
216
|
+
return this.#context;
|
|
147
217
|
}
|
|
148
|
-
|
|
149
|
-
serverArgs,
|
|
150
|
-
logFile: options.logFile,
|
|
151
|
-
resetContext: () => {
|
|
152
|
-
context?.dispose();
|
|
153
|
-
context = undefined;
|
|
154
|
-
},
|
|
155
|
-
});
|
|
156
|
-
const toolMutex = new Mutex();
|
|
157
|
-
function registerTool(tool) {
|
|
158
|
-
const toolHandler = new ToolHandler(tool, serverArgs, getContext, toolMutex, operaHooks);
|
|
218
|
+
#registerTool(tool) {
|
|
219
|
+
const toolHandler = new ToolHandler(tool, this.#serverArgs, () => this.#getContext(), this.#toolMutex, this.#operaHooks);
|
|
159
220
|
if (!toolHandler.shouldRegister) {
|
|
160
221
|
return;
|
|
161
222
|
}
|
|
162
|
-
server.registerTool(tool.name, {
|
|
223
|
+
this.server.registerTool(tool.name, {
|
|
163
224
|
description: tool.description,
|
|
164
225
|
inputSchema: toolHandler.registeredInputSchema,
|
|
165
226
|
annotations: tool.annotations,
|
|
@@ -167,12 +228,17 @@ export async function createMcpServer(serverArgs, options) {
|
|
|
167
228
|
return await toolHandler.handle(params, extra);
|
|
168
229
|
});
|
|
169
230
|
}
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Creates and initializes a Chrome DevTools MCP server instance.
|
|
234
|
+
*
|
|
235
|
+
* Maintained as a public API for backwards compatibility because external
|
|
236
|
+
* consumers and integrations rely on `createMcpServer()`. For new code,
|
|
237
|
+
* prefer using `McpServer.from(serverArgs, options)`.
|
|
238
|
+
*/
|
|
239
|
+
export async function createMcpServer(serverArgs, options = {}) {
|
|
240
|
+
const server = await McpServer.from(serverArgs, options);
|
|
241
|
+
return { server: server.server };
|
|
176
242
|
}
|
|
177
243
|
export const logDisclaimers = (args) => {
|
|
178
244
|
console.error(BROWSER_EXPOSURE_DISCLAIMER);
|
|
@@ -7,10 +7,17 @@ import fsSync from 'node:fs';
|
|
|
7
7
|
import path from 'node:path';
|
|
8
8
|
import { DevTools } from '../third_party/index.js';
|
|
9
9
|
import { createIdGenerator, stableIdSymbol, } from '../utils/id.js';
|
|
10
|
+
const VALID_EXTENSIONS = ['.heapsnapshot', '.heaptimeline'];
|
|
11
|
+
function hasValidHeapSnapshotExtension(filePath) {
|
|
12
|
+
return VALID_EXTENSIONS.some(ext => filePath.endsWith(ext));
|
|
13
|
+
}
|
|
10
14
|
export class HeapSnapshotManager {
|
|
11
15
|
#snapshotIdGenerator = createIdGenerator();
|
|
12
16
|
#snapshots = new Map();
|
|
13
17
|
async getSnapshot(filePath) {
|
|
18
|
+
if (!hasValidHeapSnapshotExtension(filePath)) {
|
|
19
|
+
throw new Error(`File ${filePath} must have a .heapsnapshot or .heaptimeline extension.`);
|
|
20
|
+
}
|
|
14
21
|
const absolutePath = path.resolve(filePath);
|
|
15
22
|
const cached = this.#snapshots.get(absolutePath);
|
|
16
23
|
if (cached) {
|
|
@@ -235,6 +242,11 @@ export class HeapSnapshotManager {
|
|
|
235
242
|
const snapshot = await this.getSnapshot(filePath);
|
|
236
243
|
return await snapshot.getDuplicateStrings();
|
|
237
244
|
}
|
|
245
|
+
async queryObjects(filePath, options) {
|
|
246
|
+
const snapshot = await this.getSnapshot(filePath);
|
|
247
|
+
const provider = snapshot.queryObjects(options);
|
|
248
|
+
return await provider.serializeItemsRange(0, Infinity);
|
|
249
|
+
}
|
|
238
250
|
hasSnapshots() {
|
|
239
251
|
return this.#snapshots.size > 0;
|
|
240
252
|
}
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
import process from 'node:process';
|
|
7
7
|
import { DAEMON_CLIENT_NAME } from '../daemon/utils.js';
|
|
8
8
|
import { logger } from '../utils/logger.js';
|
|
9
|
-
import { sanitizeParams, stripUnderscoreBeforeNumber } from './transformation.js';
|
|
9
|
+
import { bucketizeDaysSince, bucketizeLatency, buildContext, sanitizeParams, stripUnderscoreBeforeNumber, } from './transformation.js';
|
|
10
10
|
import { McpClient, WatchdogMessageType, OsType, } from './types.js';
|
|
11
11
|
import { WatchdogClient } from './WatchdogClient.js';
|
|
12
12
|
const MS_PER_DAY = 24 * 60 * 60 * 1000;
|
|
@@ -58,7 +58,10 @@ export class ClearcutLogger {
|
|
|
58
58
|
}
|
|
59
59
|
setClientName(clientName) {
|
|
60
60
|
const lowerName = clientName.toLowerCase();
|
|
61
|
-
if (lowerName.includes('claude')) {
|
|
61
|
+
if (lowerName.includes('claude-desktop')) {
|
|
62
|
+
this.#mcpClient = McpClient.MCP_CLIENT_CLAUDE_DESKTOP;
|
|
63
|
+
}
|
|
64
|
+
else if (lowerName.includes('claude')) {
|
|
62
65
|
this.#mcpClient = McpClient.MCP_CLIENT_CLAUDE_CODE;
|
|
63
66
|
}
|
|
64
67
|
else if (lowerName.includes('gemini')) {
|
|
@@ -70,25 +73,35 @@ export class ClearcutLogger {
|
|
|
70
73
|
else if (lowerName.includes('openclaw')) {
|
|
71
74
|
this.#mcpClient = McpClient.MCP_CLIENT_OPENCLAW;
|
|
72
75
|
}
|
|
76
|
+
else if (lowerName.includes('opencode')) {
|
|
77
|
+
this.#mcpClient = McpClient.MCP_CLIENT_OPENCODE;
|
|
78
|
+
}
|
|
73
79
|
else if (lowerName.includes('codex')) {
|
|
74
80
|
this.#mcpClient = McpClient.MCP_CLIENT_CODEX;
|
|
75
81
|
}
|
|
76
82
|
else if (lowerName.includes('antigravity')) {
|
|
77
83
|
this.#mcpClient = McpClient.MCP_CLIENT_ANTIGRAVITY;
|
|
78
84
|
}
|
|
85
|
+
else if (lowerName.includes('grok') || lowerName.includes('xai')) {
|
|
86
|
+
this.#mcpClient = McpClient.MCP_CLIENT_GROK;
|
|
87
|
+
}
|
|
88
|
+
else if (lowerName.includes('copilot')) {
|
|
89
|
+
this.#mcpClient = McpClient.MCP_CLIENT_GITHUB_COPILOT;
|
|
90
|
+
}
|
|
79
91
|
else {
|
|
80
92
|
this.#mcpClient = McpClient.MCP_CLIENT_OTHER;
|
|
81
93
|
}
|
|
82
94
|
}
|
|
83
95
|
async logToolInvocation(args) {
|
|
96
|
+
const context = buildContext(args.devToolsData, args.pageUrl);
|
|
84
97
|
const sanitizedToolName = stripUnderscoreBeforeNumber(args.toolName);
|
|
85
98
|
const tool_invocation = {
|
|
86
99
|
tool_name: sanitizedToolName,
|
|
87
100
|
success: args.success,
|
|
88
|
-
latency_ms: args.latencyMs,
|
|
101
|
+
latency_ms: bucketizeLatency(args.latencyMs),
|
|
89
102
|
};
|
|
90
|
-
if (Object.keys(
|
|
91
|
-
tool_invocation.context =
|
|
103
|
+
if (Object.keys(context).length > 0) {
|
|
104
|
+
tool_invocation.context = context;
|
|
92
105
|
}
|
|
93
106
|
if (Object.keys(args.params).length > 0) {
|
|
94
107
|
tool_invocation.tool_params = {
|
|
@@ -130,7 +143,7 @@ export class ClearcutLogger {
|
|
|
130
143
|
payload: {
|
|
131
144
|
mcp_client: this.#mcpClient,
|
|
132
145
|
daily_active: {
|
|
133
|
-
days_since_last_active: daysSince,
|
|
146
|
+
days_since_last_active: bucketizeDaysSince(daysSince),
|
|
134
147
|
},
|
|
135
148
|
},
|
|
136
149
|
});
|
|
@@ -13,6 +13,10 @@ export function bucketizeLatency(latencyMs) {
|
|
|
13
13
|
}
|
|
14
14
|
return LATENCY_BUCKETS[LATENCY_BUCKETS.length - 1];
|
|
15
15
|
}
|
|
16
|
+
export const MAX_ACTIVE_DAYS = 31;
|
|
17
|
+
export function bucketizeDaysSince(days) {
|
|
18
|
+
return Math.min(days, MAX_ACTIVE_DAYS);
|
|
19
|
+
}
|
|
16
20
|
export const PARAM_BLOCKLIST = new Set(['uid', 'reqid', 'msgid']);
|
|
17
21
|
const SUPPORTED_ZOD_TYPES = [
|
|
18
22
|
'ZodString',
|
|
@@ -20,6 +20,10 @@ export var McpClient;
|
|
|
20
20
|
McpClient[McpClient["MCP_CLIENT_OPENCLAW"] = 5] = "MCP_CLIENT_OPENCLAW";
|
|
21
21
|
McpClient[McpClient["MCP_CLIENT_CODEX"] = 6] = "MCP_CLIENT_CODEX";
|
|
22
22
|
McpClient[McpClient["MCP_CLIENT_ANTIGRAVITY"] = 7] = "MCP_CLIENT_ANTIGRAVITY";
|
|
23
|
+
McpClient[McpClient["MCP_CLIENT_GROK"] = 8] = "MCP_CLIENT_GROK";
|
|
24
|
+
McpClient[McpClient["MCP_CLIENT_OPENCODE"] = 9] = "MCP_CLIENT_OPENCODE";
|
|
25
|
+
McpClient[McpClient["MCP_CLIENT_CLAUDE_DESKTOP"] = 10] = "MCP_CLIENT_CLAUDE_DESKTOP";
|
|
26
|
+
McpClient[McpClient["MCP_CLIENT_GITHUB_COPILOT"] = 11] = "MCP_CLIENT_GITHUB_COPILOT";
|
|
23
27
|
McpClient[McpClient["MCP_CLIENT_OTHER"] = 3] = "MCP_CLIENT_OTHER";
|
|
24
28
|
})(McpClient || (McpClient = {}));
|
|
25
29
|
// IPC types for messages between the main process and the
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Name: core-js
|
|
2
2
|
URL: https://core-js.io
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.50.0
|
|
4
4
|
License: MIT
|
|
5
5
|
|
|
6
6
|
Copyright (c) 2013–2025 Denis Pushkarev (zloirock.ru)
|
|
@@ -745,21 +745,21 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
|
745
745
|
|
|
746
746
|
Name: puppeteer-core
|
|
747
747
|
URL: https://github.com/puppeteer/puppeteer/tree/main/packages/puppeteer-core
|
|
748
|
-
Version: 25.
|
|
748
|
+
Version: 25.10.0
|
|
749
749
|
License: Apache-2.0
|
|
750
750
|
|
|
751
751
|
-------------------- DEPENDENCY DIVIDER --------------------
|
|
752
752
|
|
|
753
753
|
Name: @puppeteer/browsers
|
|
754
754
|
URL: https://github.com/puppeteer/puppeteer/tree/main/packages/browsers
|
|
755
|
-
Version: 3.2.
|
|
755
|
+
Version: 3.2.2
|
|
756
756
|
License: Apache-2.0
|
|
757
757
|
|
|
758
758
|
-------------------- DEPENDENCY DIVIDER --------------------
|
|
759
759
|
|
|
760
760
|
Name: ws
|
|
761
761
|
URL: https://github.com/websockets/ws
|
|
762
|
-
Version: 8.21.
|
|
762
|
+
Version: 8.21.3
|
|
763
763
|
License: MIT
|
|
764
764
|
|
|
765
765
|
Copyright (c) 2011 Einar Otto Stangvik <einaros@gmail.com>
|
|
@@ -795,7 +795,7 @@ License: MIT
|
|
|
795
795
|
|
|
796
796
|
Name: devtools-frontend
|
|
797
797
|
URL: https://github.com/ChromeDevTools/devtools-frontend
|
|
798
|
-
Version:
|
|
798
|
+
Version: 2a5562dea4bfa759c15700d2311fc623c1b684d1
|
|
799
799
|
License: Apache-2.0
|
|
800
800
|
|
|
801
801
|
// Copyright 2014 The Chromium Authors
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"@modelcontextprotocol/sdk": "1.30.0",
|
|
3
|
-
"core-js": "3.
|
|
3
|
+
"core-js": "3.50.0",
|
|
4
4
|
"lighthouse": "13.4.1",
|
|
5
5
|
"semver": "7.8.5",
|
|
6
6
|
"yargs": "18.1.0",
|
|
7
7
|
"zod": "3.25.76",
|
|
8
|
-
"puppeteer-core": "25.
|
|
9
|
-
"https://github.com/ChromeDevTools/devtools-frontend": "
|
|
8
|
+
"puppeteer-core": "25.10.0",
|
|
9
|
+
"https://github.com/ChromeDevTools/devtools-frontend": "2a5562dea4bfa759c15700d2311fc623c1b684d1"
|
|
10
10
|
}
|
|
@@ -8557,6 +8557,16 @@ class ColorConverter {
|
|
|
8557
8557
|
const rgbOutput = NAMED_GAMUTS.displayP3_INVERSE.multiply(xyzInput);
|
|
8558
8558
|
return applyTransferFns(NAMED_TRANSFER_FN.sRGB_INVERSE, rgbOutput.values[0], rgbOutput.values[1], rgbOutput.values[2]);
|
|
8559
8559
|
}
|
|
8560
|
+
static displayP3LinearToXyzd50(r, g, b) {
|
|
8561
|
+
const rgbInput = new Vector3([r, g, b]);
|
|
8562
|
+
const xyzOutput = NAMED_GAMUTS.displayP3.multiply(rgbInput);
|
|
8563
|
+
return xyzOutput.values;
|
|
8564
|
+
}
|
|
8565
|
+
static xyzd50ToDisplayP3Linear(x, y, z) {
|
|
8566
|
+
const xyzInput = new Vector3([x, y, z]);
|
|
8567
|
+
const rgbResult = NAMED_GAMUTS.displayP3_INVERSE.multiply(xyzInput);
|
|
8568
|
+
return rgbResult.values;
|
|
8569
|
+
}
|
|
8560
8570
|
static proPhotoToXyzd50(r, g, b) {
|
|
8561
8571
|
const [mappedR, mappedG, mappedB] = applyTransferFns(NAMED_TRANSFER_FN.proPhotoRGB, r, g, b);
|
|
8562
8572
|
const rgbInput = new Vector3([mappedR, mappedG, mappedB]);
|
|
@@ -8756,6 +8766,8 @@ function getColorSpace(colorSpaceText) {
|
|
|
8756
8766
|
return "srgb-linear" ;
|
|
8757
8767
|
case "display-p3" :
|
|
8758
8768
|
return "display-p3" ;
|
|
8769
|
+
case "display-p3-linear" :
|
|
8770
|
+
return "display-p3-linear" ;
|
|
8759
8771
|
case "a98-rgb" :
|
|
8760
8772
|
return "a98-rgb" ;
|
|
8761
8773
|
case "prophoto-rgb" :
|
|
@@ -8974,6 +8986,7 @@ class Lab {
|
|
|
8974
8986
|
["srgb" ]: (self) => new ColorFunction("srgb" , ...ColorConverter.xyzd50ToSrgb(...self.#toXyzd50()), self.alpha),
|
|
8975
8987
|
["srgb-linear" ]: (self) => new ColorFunction("srgb-linear" , ...ColorConverter.xyzd50TosRGBLinear(...self.#toXyzd50()), self.alpha),
|
|
8976
8988
|
["display-p3" ]: (self) => new ColorFunction("display-p3" , ...ColorConverter.xyzd50ToDisplayP3(...self.#toXyzd50()), self.alpha),
|
|
8989
|
+
["display-p3-linear" ]: (self) => new ColorFunction("display-p3-linear" , ...ColorConverter.xyzd50ToDisplayP3Linear(...self.#toXyzd50()), self.alpha),
|
|
8977
8990
|
["a98-rgb" ]: (self) => new ColorFunction("a98-rgb" , ...ColorConverter.xyzd50ToAdobeRGB(...self.#toXyzd50()), self.alpha),
|
|
8978
8991
|
["prophoto-rgb" ]: (self) => new ColorFunction("prophoto-rgb" , ...ColorConverter.xyzd50ToProPhoto(...self.#toXyzd50()), self.alpha),
|
|
8979
8992
|
["rec2020" ]: (self) => new ColorFunction("rec2020" , ...ColorConverter.xyzd50ToRec2020(...self.#toXyzd50()), self.alpha),
|
|
@@ -9090,6 +9103,7 @@ class LCH {
|
|
|
9090
9103
|
["srgb" ]: (self) => new ColorFunction("srgb" , ...ColorConverter.xyzd50ToSrgb(...self.#toXyzd50()), self.alpha),
|
|
9091
9104
|
["srgb-linear" ]: (self) => new ColorFunction("srgb-linear" , ...ColorConverter.xyzd50TosRGBLinear(...self.#toXyzd50()), self.alpha),
|
|
9092
9105
|
["display-p3" ]: (self) => new ColorFunction("display-p3" , ...ColorConverter.xyzd50ToDisplayP3(...self.#toXyzd50()), self.alpha),
|
|
9106
|
+
["display-p3-linear" ]: (self) => new ColorFunction("display-p3-linear" , ...ColorConverter.xyzd50ToDisplayP3Linear(...self.#toXyzd50()), self.alpha),
|
|
9093
9107
|
["a98-rgb" ]: (self) => new ColorFunction("a98-rgb" , ...ColorConverter.xyzd50ToAdobeRGB(...self.#toXyzd50()), self.alpha),
|
|
9094
9108
|
["prophoto-rgb" ]: (self) => new ColorFunction("prophoto-rgb" , ...ColorConverter.xyzd50ToProPhoto(...self.#toXyzd50()), self.alpha),
|
|
9095
9109
|
["rec2020" ]: (self) => new ColorFunction("rec2020" , ...ColorConverter.xyzd50ToRec2020(...self.#toXyzd50()), self.alpha),
|
|
@@ -9208,6 +9222,7 @@ class Oklab {
|
|
|
9208
9222
|
["srgb" ]: (self) => new ColorFunction("srgb" , ...ColorConverter.xyzd50ToSrgb(...self.#toXyzd50()), self.alpha),
|
|
9209
9223
|
["srgb-linear" ]: (self) => new ColorFunction("srgb-linear" , ...ColorConverter.xyzd50TosRGBLinear(...self.#toXyzd50()), self.alpha),
|
|
9210
9224
|
["display-p3" ]: (self) => new ColorFunction("display-p3" , ...ColorConverter.xyzd50ToDisplayP3(...self.#toXyzd50()), self.alpha),
|
|
9225
|
+
["display-p3-linear" ]: (self) => new ColorFunction("display-p3-linear" , ...ColorConverter.xyzd50ToDisplayP3Linear(...self.#toXyzd50()), self.alpha),
|
|
9211
9226
|
["a98-rgb" ]: (self) => new ColorFunction("a98-rgb" , ...ColorConverter.xyzd50ToAdobeRGB(...self.#toXyzd50()), self.alpha),
|
|
9212
9227
|
["prophoto-rgb" ]: (self) => new ColorFunction("prophoto-rgb" , ...ColorConverter.xyzd50ToProPhoto(...self.#toXyzd50()), self.alpha),
|
|
9213
9228
|
["rec2020" ]: (self) => new ColorFunction("rec2020" , ...ColorConverter.xyzd50ToRec2020(...self.#toXyzd50()), self.alpha),
|
|
@@ -9324,6 +9339,7 @@ class Oklch {
|
|
|
9324
9339
|
["srgb" ]: (self) => new ColorFunction("srgb" , ...ColorConverter.xyzd50ToSrgb(...self.#toXyzd50()), self.alpha),
|
|
9325
9340
|
["srgb-linear" ]: (self) => new ColorFunction("srgb-linear" , ...ColorConverter.xyzd50TosRGBLinear(...self.#toXyzd50()), self.alpha),
|
|
9326
9341
|
["display-p3" ]: (self) => new ColorFunction("display-p3" , ...ColorConverter.xyzd50ToDisplayP3(...self.#toXyzd50()), self.alpha),
|
|
9342
|
+
["display-p3-linear" ]: (self) => new ColorFunction("display-p3-linear" , ...ColorConverter.xyzd50ToDisplayP3Linear(...self.#toXyzd50()), self.alpha),
|
|
9327
9343
|
["a98-rgb" ]: (self) => new ColorFunction("a98-rgb" , ...ColorConverter.xyzd50ToAdobeRGB(...self.#toXyzd50()), self.alpha),
|
|
9328
9344
|
["prophoto-rgb" ]: (self) => new ColorFunction("prophoto-rgb" , ...ColorConverter.xyzd50ToProPhoto(...self.#toXyzd50()), self.alpha),
|
|
9329
9345
|
["rec2020" ]: (self) => new ColorFunction("rec2020" , ...ColorConverter.xyzd50ToRec2020(...self.#toXyzd50()), self.alpha),
|
|
@@ -9443,6 +9459,7 @@ class ColorFunction {
|
|
|
9443
9459
|
["srgb" ]: (self) => new ColorFunction("srgb" , ...ColorConverter.xyzd50ToSrgb(...self.#toXyzd50()), self.alpha),
|
|
9444
9460
|
["srgb-linear" ]: (self) => new ColorFunction("srgb-linear" , ...ColorConverter.xyzd50TosRGBLinear(...self.#toXyzd50()), self.alpha),
|
|
9445
9461
|
["display-p3" ]: (self) => new ColorFunction("display-p3" , ...ColorConverter.xyzd50ToDisplayP3(...self.#toXyzd50()), self.alpha),
|
|
9462
|
+
["display-p3-linear" ]: (self) => new ColorFunction("display-p3-linear" , ...ColorConverter.xyzd50ToDisplayP3Linear(...self.#toXyzd50()), self.alpha),
|
|
9446
9463
|
["a98-rgb" ]: (self) => new ColorFunction("a98-rgb" , ...ColorConverter.xyzd50ToAdobeRGB(...self.#toXyzd50()), self.alpha),
|
|
9447
9464
|
["prophoto-rgb" ]: (self) => new ColorFunction("prophoto-rgb" , ...ColorConverter.xyzd50ToProPhoto(...self.#toXyzd50()), self.alpha),
|
|
9448
9465
|
["rec2020" ]: (self) => new ColorFunction("rec2020" , ...ColorConverter.xyzd50ToRec2020(...self.#toXyzd50()), self.alpha),
|
|
@@ -9459,6 +9476,8 @@ class ColorFunction {
|
|
|
9459
9476
|
return ColorConverter.srgbLinearToXyzd50(p0, p1, p2);
|
|
9460
9477
|
case "display-p3" :
|
|
9461
9478
|
return ColorConverter.displayP3ToXyzd50(p0, p1, p2);
|
|
9479
|
+
case "display-p3-linear" :
|
|
9480
|
+
return ColorConverter.displayP3LinearToXyzd50(p0, p1, p2);
|
|
9462
9481
|
case "a98-rgb" :
|
|
9463
9482
|
return ColorConverter.adobeRGBToXyzd50(p0, p1, p2);
|
|
9464
9483
|
case "prophoto-rgb" :
|
|
@@ -9614,6 +9633,7 @@ class HSL {
|
|
|
9614
9633
|
["srgb" ]: (self) => new ColorFunction("srgb" , ...ColorConverter.xyzd50ToSrgb(...self.#toXyzd50()), self.alpha),
|
|
9615
9634
|
["srgb-linear" ]: (self) => new ColorFunction("srgb-linear" , ...ColorConverter.xyzd50TosRGBLinear(...self.#toXyzd50()), self.alpha),
|
|
9616
9635
|
["display-p3" ]: (self) => new ColorFunction("display-p3" , ...ColorConverter.xyzd50ToDisplayP3(...self.#toXyzd50()), self.alpha),
|
|
9636
|
+
["display-p3-linear" ]: (self) => new ColorFunction("display-p3-linear" , ...ColorConverter.xyzd50ToDisplayP3Linear(...self.#toXyzd50()), self.alpha),
|
|
9617
9637
|
["a98-rgb" ]: (self) => new ColorFunction("a98-rgb" , ...ColorConverter.xyzd50ToAdobeRGB(...self.#toXyzd50()), self.alpha),
|
|
9618
9638
|
["prophoto-rgb" ]: (self) => new ColorFunction("prophoto-rgb" , ...ColorConverter.xyzd50ToProPhoto(...self.#toXyzd50()), self.alpha),
|
|
9619
9639
|
["rec2020" ]: (self) => new ColorFunction("rec2020" , ...ColorConverter.xyzd50ToRec2020(...self.#toXyzd50()), self.alpha),
|
|
@@ -9741,6 +9761,7 @@ class HWB {
|
|
|
9741
9761
|
["srgb" ]: (self) => new ColorFunction("srgb" , ...ColorConverter.xyzd50ToSrgb(...self.#toXyzd50()), self.alpha),
|
|
9742
9762
|
["srgb-linear" ]: (self) => new ColorFunction("srgb-linear" , ...ColorConverter.xyzd50TosRGBLinear(...self.#toXyzd50()), self.alpha),
|
|
9743
9763
|
["display-p3" ]: (self) => new ColorFunction("display-p3" , ...ColorConverter.xyzd50ToDisplayP3(...self.#toXyzd50()), self.alpha),
|
|
9764
|
+
["display-p3-linear" ]: (self) => new ColorFunction("display-p3-linear" , ...ColorConverter.xyzd50ToDisplayP3Linear(...self.#toXyzd50()), self.alpha),
|
|
9744
9765
|
["a98-rgb" ]: (self) => new ColorFunction("a98-rgb" , ...ColorConverter.xyzd50ToAdobeRGB(...self.#toXyzd50()), self.alpha),
|
|
9745
9766
|
["prophoto-rgb" ]: (self) => new ColorFunction("prophoto-rgb" , ...ColorConverter.xyzd50ToProPhoto(...self.#toXyzd50()), self.alpha),
|
|
9746
9767
|
["rec2020" ]: (self) => new ColorFunction("rec2020" , ...ColorConverter.xyzd50ToRec2020(...self.#toXyzd50()), self.alpha),
|
|
@@ -9969,6 +9990,7 @@ class Legacy {
|
|
|
9969
9990
|
["srgb" ]: (self) => new ColorFunction("srgb" , ...ColorConverter.xyzd50ToSrgb(...self.#toXyzd50()), self.alpha),
|
|
9970
9991
|
["srgb-linear" ]: (self) => new ColorFunction("srgb-linear" , ...ColorConverter.xyzd50TosRGBLinear(...self.#toXyzd50()), self.alpha),
|
|
9971
9992
|
["display-p3" ]: (self) => new ColorFunction("display-p3" , ...ColorConverter.xyzd50ToDisplayP3(...self.#toXyzd50()), self.alpha),
|
|
9993
|
+
["display-p3-linear" ]: (self) => new ColorFunction("display-p3-linear" , ...ColorConverter.xyzd50ToDisplayP3Linear(...self.#toXyzd50()), self.alpha),
|
|
9972
9994
|
["a98-rgb" ]: (self) => new ColorFunction("a98-rgb" , ...ColorConverter.xyzd50ToAdobeRGB(...self.#toXyzd50()), self.alpha),
|
|
9973
9995
|
["prophoto-rgb" ]: (self) => new ColorFunction("prophoto-rgb" , ...ColorConverter.xyzd50ToProPhoto(...self.#toXyzd50()), self.alpha),
|
|
9974
9996
|
["rec2020" ]: (self) => new ColorFunction("rec2020" , ...ColorConverter.xyzd50ToRec2020(...self.#toXyzd50()), self.alpha),
|