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.
Files changed (51) hide show
  1. package/build/src/McpContext.js +78 -41
  2. package/build/src/McpPage.js +5 -1
  3. package/build/src/ToolHandler.js +45 -74
  4. package/build/src/bin/chrome-devtools-mcp-main.js +4 -4
  5. package/build/src/bin/chrome-devtools.js +29 -30
  6. package/build/src/bin/opera-devtools-cli-options.js +1 -1
  7. package/build/src/bin/opera-devtools-mcp-cli-options.js +1 -1
  8. package/build/src/browser.js +5 -16
  9. package/build/src/config/browser-options.js +126 -0
  10. package/build/src/config/category-options.js +81 -0
  11. package/build/src/{bin/chrome-devtools-cli-options.js → config/cli-options.js} +392 -26
  12. package/build/src/{bin/chrome-devtools-mcp-cli-options.js → config/mcp-options.js} +143 -164
  13. package/build/src/daemon/client.js +9 -0
  14. package/build/src/daemon/utils.js +6 -0
  15. package/build/src/devtools/DevtoolsUtils.js +27 -21
  16. package/build/src/formatters/NetworkFormatter.js +5 -2
  17. package/build/src/index.js +166 -100
  18. package/build/src/processors/HeapSnapshotManager.js +12 -0
  19. package/build/src/telemetry/ClearcutLogger.js +19 -6
  20. package/build/src/telemetry/transformation.js +4 -0
  21. package/build/src/telemetry/types.js +4 -0
  22. package/build/src/third_party/THIRD_PARTY_NOTICES +5 -5
  23. package/build/src/third_party/bundled-packages.json +3 -3
  24. package/build/src/third_party/devtools-formatter-worker.js +22 -0
  25. package/build/src/third_party/devtools-heap-snapshot-worker.js +100 -20
  26. package/build/src/third_party/index.js +15459 -14256
  27. package/build/src/third_party/issue-descriptions/federatedAuthRequestAccountsBlockedByConnectionAllowlist.md +1 -0
  28. package/build/src/third_party/issue-descriptions/federatedAuthRequestConfigBlockedByConnectionAllowlist.md +1 -0
  29. package/build/src/third_party/issue-descriptions/federatedAuthRequestIdTokenBlockedByConnectionAllowlist.md +1 -0
  30. package/build/src/third_party/issue-descriptions/federatedAuthRequestWellKnownBlockedByConnectionAllowlist.md +1 -0
  31. package/build/src/tools/ToolDefinition.js +15 -0
  32. package/build/src/tools/categories.js +0 -6
  33. package/build/src/tools/comments.js +90 -0
  34. package/build/src/tools/console.js +1 -1
  35. package/build/src/tools/emulation.js +1 -1
  36. package/build/src/tools/extensions.js +1 -1
  37. package/build/src/tools/memory.js +60 -6
  38. package/build/src/tools/network.js +2 -2
  39. package/build/src/tools/pages.js +26 -15
  40. package/build/src/tools/performance.js +4 -3
  41. package/build/src/tools/screencast.js +3 -2
  42. package/build/src/tools/screenshot.js +39 -8
  43. package/build/src/tools/script.js +17 -4
  44. package/build/src/tools/slim/tools.js +41 -33
  45. package/build/src/tools/snapshot.js +1 -1
  46. package/build/src/tools/tools.js +2 -0
  47. package/build/src/utils/WaitForHelper.js +12 -1
  48. package/build/src/utils/bytes.js +105 -0
  49. package/build/src/utils/url.js +73 -0
  50. package/build/src/version.js +1 -1
  51. package/package.json +23 -4
@@ -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
- export { buildFlag } from './ToolHandler.js';
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 async function createMcpServer(serverArgs, options) {
33
- // Opera forces `usageStatistics` off in ./opera/policy.ts, so this stays
34
- // dormant. Kept identical to upstream so the seam is the policy, not this file.
35
- if (serverArgs.usageStatistics) {
36
- ClearcutLogger.initialize({
37
- persistence: new FilePersistence(),
38
- logFile: serverArgs.logFile,
39
- appVersion: VERSION,
40
- clearcutEndpoint: serverArgs.clearcutEndpoint,
41
- clearcutForceFlushIntervalMs: serverArgs.clearcutForceFlushIntervalMs,
42
- clearcutIncludePidHeader: serverArgs.clearcutIncludePidHeader,
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
- const server = new McpServer({
46
- name: 'chrome_devtools',
47
- title: 'Chrome DevTools MCP server',
48
- version: VERSION,
49
- }, { capabilities: { logging: {} } });
50
- server.server.setRequestHandler(SetLevelRequestSchema, () => {
51
- return {};
52
- });
53
- // Roots are client state rather than browser state, so the last listing stays
54
- // valid across browser reconnects and only the client can invalidate it, via
55
- // the `roots/list_changed` notification handled below
56
- let lastRoots;
57
- // `timeout` is only passed where a tool call is waiting on the result – the
58
- // background refreshes below block nobody, so bounding them would just discard
59
- // roots a slow client was about to send
60
- const updateRoots = async (timeout) => {
61
- if (!server.server.getClientCapabilities()?.roots) {
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
- lastRoots = roots.roots;
67
- context?.setRoots(lastRoots);
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
- server.server.oninitialized = () => {
74
- const clientName = server.server.getClientVersion()?.name;
75
- if (clientName) {
76
- ClearcutLogger.get()?.setClientName(clientName);
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 browser = serverArgs.browserUrl || serverArgs.wsEndpoint || serverArgs.autoConnect
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
- ? serverArgs.channel
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
- if (lastRoots === undefined) {
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
- context.setRoots(lastRoots);
143
- void updateRoots();
213
+ void this.#updateRoots();
144
214
  }
145
215
  }
146
- return context;
216
+ return this.#context;
147
217
  }
148
- const operaHooks = createOperaToolHooks({
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
- const tools = createTools(serverArgs);
171
- for (const tool of tools) {
172
- registerTool(tool);
173
- }
174
- await loadIssueDescriptions();
175
- return { server };
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(args.context).length > 0) {
91
- tool_invocation.context = args.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.49.0
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.6.0
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.0
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.1
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: eaad2491924990cb310a4c753b2851e64d515f00
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.49.0",
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.6.0",
9
- "https://github.com/ChromeDevTools/devtools-frontend": "eaad2491924990cb310a4c753b2851e64d515f00"
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),