chrome-devtools-mcp 0.25.0 → 0.26.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 +1 -1
- package/build/src/ToolHandler.js +188 -0
- package/build/src/bin/chrome-devtools-cli-options.js +1 -1
- package/build/src/bin/chrome-devtools-mcp-main.js +4 -3
- package/build/src/bin/chrome-devtools.js +0 -2
- package/build/src/index.js +9 -164
- package/build/src/telemetry/ClearcutLogger.js +27 -0
- package/build/src/telemetry/errors.js +14 -0
- package/build/src/telemetry/types.js +0 -8
- package/build/src/third_party/THIRD_PARTY_NOTICES +9 -11
- package/build/src/third_party/bundled-packages.json +2 -2
- package/build/src/third_party/devtools-formatter-worker.js +31 -33
- package/build/src/third_party/devtools-heap-snapshot-worker.js +42 -44
- package/build/src/third_party/index.js +1560 -1074
- package/build/src/third_party/lighthouse-devtools-mcp-bundle.js +4236 -4219
- package/build/src/tools/input.js +28 -7
- package/build/src/tools/lighthouse.js +7 -7
- package/build/src/version.js +1 -1
- package/package.json +6 -6
package/README.md
CHANGED
|
@@ -161,7 +161,7 @@ To install Chrome DevTools MCP with skills, add the marketplace registry in Clau
|
|
|
161
161
|
Then, install the plugin:
|
|
162
162
|
|
|
163
163
|
```sh
|
|
164
|
-
/plugin install chrome-devtools-mcp
|
|
164
|
+
/plugin install chrome-devtools-mcp@chrome-devtools-plugins
|
|
165
165
|
```
|
|
166
166
|
|
|
167
167
|
Restart Claude Code to have the MCP server and skills load (check with `/skills`).
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Google LLC
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
import { logger } from './logger.js';
|
|
7
|
+
import { McpResponse } from './McpResponse.js';
|
|
8
|
+
import { SlimMcpResponse } from './SlimMcpResponse.js';
|
|
9
|
+
import { ClearcutLogger } from './telemetry/ClearcutLogger.js';
|
|
10
|
+
import { bucketizeLatency } from './telemetry/metricUtils.js';
|
|
11
|
+
import { labels, OFF_BY_DEFAULT_CATEGORIES } from './tools/categories.js';
|
|
12
|
+
import { pageIdSchema } from './tools/ToolDefinition.js';
|
|
13
|
+
export function buildFlag(category) {
|
|
14
|
+
return `category${category.charAt(0).toUpperCase() + category.slice(1)}`;
|
|
15
|
+
}
|
|
16
|
+
function buildDisabledMessage(toolName, flag, categoryLabel) {
|
|
17
|
+
const reason = categoryLabel
|
|
18
|
+
? `is in category ${categoryLabel} which`
|
|
19
|
+
: `requires experimental feature ${flag} and`;
|
|
20
|
+
return `Tool ${toolName} ${reason} is currently disabled. Enable it by running chrome-devtools start ${flag}=true. For more information check the README.`;
|
|
21
|
+
}
|
|
22
|
+
function getCategoryStatus(category, serverArgs) {
|
|
23
|
+
const categoryFlag = buildFlag(category);
|
|
24
|
+
const flagValue = serverArgs[categoryFlag];
|
|
25
|
+
const isDisabled = OFF_BY_DEFAULT_CATEGORIES.includes(category)
|
|
26
|
+
? !flagValue
|
|
27
|
+
: flagValue === false;
|
|
28
|
+
if (isDisabled) {
|
|
29
|
+
return {
|
|
30
|
+
categoryFlag,
|
|
31
|
+
disabled: true,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
return {
|
|
35
|
+
disabled: false,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
function getConditionStatus(condition, serverArgs) {
|
|
39
|
+
if (condition && !serverArgs[condition]) {
|
|
40
|
+
return { conditionFlag: condition, disabled: true };
|
|
41
|
+
}
|
|
42
|
+
return { disabled: false };
|
|
43
|
+
}
|
|
44
|
+
function getToolStatusInfo(tool, serverArgs) {
|
|
45
|
+
const category = tool.annotations.category;
|
|
46
|
+
const categoryCheck = getCategoryStatus(category, serverArgs);
|
|
47
|
+
if (category && categoryCheck.disabled) {
|
|
48
|
+
if (!categoryCheck.categoryFlag) {
|
|
49
|
+
throw new Error('when the category is disabled there should always be a flag set');
|
|
50
|
+
}
|
|
51
|
+
return {
|
|
52
|
+
disabled: true,
|
|
53
|
+
reason: buildDisabledMessage(tool.name, `--${categoryCheck.categoryFlag}`, labels[category]),
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
for (const condition of tool.annotations.conditions || []) {
|
|
57
|
+
const conditionCheck = getConditionStatus(condition, serverArgs);
|
|
58
|
+
if (conditionCheck.disabled) {
|
|
59
|
+
if (!conditionCheck.conditionFlag) {
|
|
60
|
+
throw new Error('when the condition is disabled there should always be a flag set');
|
|
61
|
+
}
|
|
62
|
+
return {
|
|
63
|
+
disabled: true,
|
|
64
|
+
reason: buildDisabledMessage(tool.name, `--${conditionCheck.conditionFlag}`),
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return { disabled: false };
|
|
69
|
+
}
|
|
70
|
+
function isPageScopedTool(tool) {
|
|
71
|
+
return 'pageScoped' in tool && tool.pageScoped === true;
|
|
72
|
+
}
|
|
73
|
+
export class ToolHandler {
|
|
74
|
+
tool;
|
|
75
|
+
serverArgs;
|
|
76
|
+
getContext;
|
|
77
|
+
toolMutex;
|
|
78
|
+
inputSchema;
|
|
79
|
+
shouldRegister;
|
|
80
|
+
disabledReason;
|
|
81
|
+
constructor(tool, serverArgs, getContext, toolMutex) {
|
|
82
|
+
this.tool = tool;
|
|
83
|
+
this.serverArgs = serverArgs;
|
|
84
|
+
this.getContext = getContext;
|
|
85
|
+
this.toolMutex = toolMutex;
|
|
86
|
+
const { disabled, reason } = getToolStatusInfo(tool, serverArgs);
|
|
87
|
+
this.disabledReason = reason;
|
|
88
|
+
this.shouldRegister = !(disabled && !serverArgs.viaCli);
|
|
89
|
+
this.inputSchema =
|
|
90
|
+
'pageScoped' in tool &&
|
|
91
|
+
tool.pageScoped &&
|
|
92
|
+
serverArgs.experimentalPageIdRouting &&
|
|
93
|
+
!serverArgs.slim
|
|
94
|
+
? { ...tool.schema, ...pageIdSchema }
|
|
95
|
+
: tool.schema;
|
|
96
|
+
}
|
|
97
|
+
async handle(params) {
|
|
98
|
+
if (this.disabledReason) {
|
|
99
|
+
return {
|
|
100
|
+
content: [
|
|
101
|
+
{
|
|
102
|
+
type: 'text',
|
|
103
|
+
text: this.disabledReason,
|
|
104
|
+
},
|
|
105
|
+
],
|
|
106
|
+
isError: true,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
const guard = await this.toolMutex.acquire();
|
|
110
|
+
const startTime = Date.now();
|
|
111
|
+
let success = false;
|
|
112
|
+
try {
|
|
113
|
+
logger(`${this.tool.name} request: ${JSON.stringify(params, null, ' ')}`);
|
|
114
|
+
const context = await this.getContext();
|
|
115
|
+
logger(`${this.tool.name} context: resolved`);
|
|
116
|
+
await context.detectOpenDevToolsWindows();
|
|
117
|
+
const response = this.serverArgs.slim
|
|
118
|
+
? new SlimMcpResponse(this.serverArgs)
|
|
119
|
+
: new McpResponse(this.serverArgs);
|
|
120
|
+
response.setRedactNetworkHeaders(this.serverArgs.redactNetworkHeaders);
|
|
121
|
+
try {
|
|
122
|
+
if (isPageScopedTool(this.tool)) {
|
|
123
|
+
const pageId = typeof params.pageId === 'number' ? params.pageId : undefined;
|
|
124
|
+
const page = this.serverArgs.experimentalPageIdRouting &&
|
|
125
|
+
pageId !== undefined &&
|
|
126
|
+
!this.serverArgs.slim
|
|
127
|
+
? context.getPageById(pageId)
|
|
128
|
+
: context.getSelectedMcpPage();
|
|
129
|
+
response.setPage(page);
|
|
130
|
+
if (this.tool.blockedByDialog) {
|
|
131
|
+
page.throwIfDialogOpen();
|
|
132
|
+
}
|
|
133
|
+
await this.tool.handler({
|
|
134
|
+
params,
|
|
135
|
+
page,
|
|
136
|
+
}, response, context);
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
await this.tool.handler({
|
|
140
|
+
params,
|
|
141
|
+
}, response, context);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
catch (err) {
|
|
145
|
+
response.setError(err);
|
|
146
|
+
}
|
|
147
|
+
const { content, structuredContent } = await response.handle(this.tool.name, context);
|
|
148
|
+
const result = {
|
|
149
|
+
content,
|
|
150
|
+
};
|
|
151
|
+
if (response.error) {
|
|
152
|
+
result.isError = true;
|
|
153
|
+
}
|
|
154
|
+
success = true;
|
|
155
|
+
if (this.serverArgs.experimentalStructuredContent) {
|
|
156
|
+
result.structuredContent = structuredContent;
|
|
157
|
+
}
|
|
158
|
+
return result;
|
|
159
|
+
}
|
|
160
|
+
catch (err) {
|
|
161
|
+
logger(`${this.tool.name} error:`, err, err?.stack);
|
|
162
|
+
let errorText = err && 'message' in err ? err.message : String(err);
|
|
163
|
+
if ('cause' in err && err.cause) {
|
|
164
|
+
errorText += `\nCause: ${err.cause.message}`;
|
|
165
|
+
}
|
|
166
|
+
return {
|
|
167
|
+
content: [
|
|
168
|
+
{
|
|
169
|
+
type: 'text',
|
|
170
|
+
text: errorText,
|
|
171
|
+
},
|
|
172
|
+
],
|
|
173
|
+
isError: true,
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
finally {
|
|
177
|
+
void ClearcutLogger.get()?.logToolInvocation({
|
|
178
|
+
toolName: this.tool.name,
|
|
179
|
+
params,
|
|
180
|
+
schema: this.inputSchema,
|
|
181
|
+
success,
|
|
182
|
+
latencyMs: bucketizeLatency(Date.now() - startTime),
|
|
183
|
+
});
|
|
184
|
+
guard.dispose();
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
//# sourceMappingURL=ToolHandler.js.map
|
|
@@ -211,7 +211,7 @@ export const commands = {
|
|
|
211
211
|
value: {
|
|
212
212
|
name: 'value',
|
|
213
213
|
type: 'string',
|
|
214
|
-
description: 'The value to fill in',
|
|
214
|
+
description: 'The value to fill in. "true" or "false" for checkboxes and toggles, "true" for radio buttons.',
|
|
215
215
|
required: true,
|
|
216
216
|
},
|
|
217
217
|
includeSnapshot: {
|
|
@@ -7,6 +7,7 @@ import '../polyfill.js';
|
|
|
7
7
|
import process from 'node:process';
|
|
8
8
|
import { createMcpServer, logDisclaimers } from '../index.js';
|
|
9
9
|
import { logger, saveLogsToFile } from '../logger.js';
|
|
10
|
+
import { ClearcutLogger } from '../telemetry/ClearcutLogger.js';
|
|
10
11
|
import { computeFlagUsage } from '../telemetry/flagUtils.js';
|
|
11
12
|
import { StdioServerTransport } from '../third_party/index.js';
|
|
12
13
|
import { checkForUpdates } from '../utils/check-for-updates.js';
|
|
@@ -21,13 +22,13 @@ if (process.env['CHROME_DEVTOOLS_MCP_CRASH_ON_UNCAUGHT'] !== 'true') {
|
|
|
21
22
|
});
|
|
22
23
|
}
|
|
23
24
|
logger(`Starting Chrome DevTools MCP Server v${VERSION}`);
|
|
24
|
-
const { server
|
|
25
|
+
const { server } = await createMcpServer(args, {
|
|
25
26
|
logFile,
|
|
26
27
|
});
|
|
27
28
|
const transport = new StdioServerTransport();
|
|
28
29
|
await server.connect(transport);
|
|
29
30
|
logger('Chrome DevTools MCP Server connected');
|
|
30
31
|
logDisclaimers(args);
|
|
31
|
-
void
|
|
32
|
-
void
|
|
32
|
+
void ClearcutLogger.get()?.logDailyActiveIfNeeded();
|
|
33
|
+
void ClearcutLogger.get()?.logServerStart(computeFlagUsage(args, cliOptions));
|
|
33
34
|
//# sourceMappingURL=chrome-devtools-mcp-main.js.map
|
|
@@ -24,8 +24,6 @@ const defaultArgs = ['--viaCli', '--experimentalStructuredContent'];
|
|
|
24
24
|
const startCliOptions = {
|
|
25
25
|
...cliOptions,
|
|
26
26
|
};
|
|
27
|
-
// Not supported in CLI on purpose.
|
|
28
|
-
delete startCliOptions.autoConnect;
|
|
29
27
|
// Missing CLI serialization.
|
|
30
28
|
delete startCliOptions.viewport;
|
|
31
29
|
// Change the defaults for the CLI.
|
package/build/src/index.js
CHANGED
|
@@ -7,77 +7,16 @@ import { ensureBrowserConnected, ensureBrowserLaunched } from './browser.js';
|
|
|
7
7
|
import { loadIssueDescriptions } from './issue-descriptions.js';
|
|
8
8
|
import { logger } from './logger.js';
|
|
9
9
|
import { McpContext } from './McpContext.js';
|
|
10
|
-
import { McpResponse } from './McpResponse.js';
|
|
11
10
|
import { Mutex } from './Mutex.js';
|
|
12
|
-
import { SlimMcpResponse } from './SlimMcpResponse.js';
|
|
13
11
|
import { ClearcutLogger } from './telemetry/ClearcutLogger.js';
|
|
14
|
-
import { bucketizeLatency } from './telemetry/metricUtils.js';
|
|
15
12
|
import { McpServer, SetLevelRequestSchema, ListRootsResultSchema, RootsListChangedNotificationSchema, } from './third_party/index.js';
|
|
16
|
-
import {
|
|
17
|
-
import { pageIdSchema } from './tools/ToolDefinition.js';
|
|
13
|
+
import { ToolHandler } from './ToolHandler.js';
|
|
18
14
|
import { createTools } from './tools/tools.js';
|
|
19
15
|
import { VERSION } from './version.js';
|
|
20
|
-
export
|
|
21
|
-
return `category${category.charAt(0).toUpperCase() + category.slice(1)}`;
|
|
22
|
-
}
|
|
23
|
-
function buildDisabledMessage(toolName, flag, categoryLabel) {
|
|
24
|
-
const reason = categoryLabel
|
|
25
|
-
? `is in category ${categoryLabel} which`
|
|
26
|
-
: `requires experimental feature ${flag} and`;
|
|
27
|
-
return `Tool ${toolName} ${reason} is currently disabled. Enable it by running chrome-devtools start ${flag}=true. For more information check the README.`;
|
|
28
|
-
}
|
|
29
|
-
function getCategoryStatus(category, serverArgs) {
|
|
30
|
-
const categoryFlag = buildFlag(category);
|
|
31
|
-
const flagValue = serverArgs[categoryFlag];
|
|
32
|
-
const isDisabled = OFF_BY_DEFAULT_CATEGORIES.includes(category)
|
|
33
|
-
? !flagValue
|
|
34
|
-
: flagValue === false;
|
|
35
|
-
if (isDisabled) {
|
|
36
|
-
return {
|
|
37
|
-
categoryFlag,
|
|
38
|
-
disabled: true,
|
|
39
|
-
};
|
|
40
|
-
}
|
|
41
|
-
return {
|
|
42
|
-
disabled: false,
|
|
43
|
-
};
|
|
44
|
-
}
|
|
45
|
-
function getConditionStatus(condition, serverArgs) {
|
|
46
|
-
if (condition && !serverArgs[condition]) {
|
|
47
|
-
return { conditionFlag: condition, disabled: true };
|
|
48
|
-
}
|
|
49
|
-
return { disabled: false };
|
|
50
|
-
}
|
|
51
|
-
function getToolStatusInfo(tool, serverArgs) {
|
|
52
|
-
const category = tool.annotations.category;
|
|
53
|
-
const categoryCheck = getCategoryStatus(category, serverArgs);
|
|
54
|
-
if (category && categoryCheck.disabled) {
|
|
55
|
-
if (!categoryCheck.categoryFlag) {
|
|
56
|
-
throw new Error('when the category is disabled there should always be a flag set');
|
|
57
|
-
}
|
|
58
|
-
return {
|
|
59
|
-
disabled: true,
|
|
60
|
-
reason: buildDisabledMessage(tool.name, `--${categoryCheck.categoryFlag}`, labels[category]),
|
|
61
|
-
};
|
|
62
|
-
}
|
|
63
|
-
for (const condition of tool.annotations.conditions || []) {
|
|
64
|
-
const conditionCheck = getConditionStatus(condition, serverArgs);
|
|
65
|
-
if (conditionCheck.disabled) {
|
|
66
|
-
if (!conditionCheck.conditionFlag) {
|
|
67
|
-
throw new Error('when the condition is disabled there should always be a flag set');
|
|
68
|
-
}
|
|
69
|
-
return {
|
|
70
|
-
disabled: true,
|
|
71
|
-
reason: buildDisabledMessage(tool.name, `--${conditionCheck.conditionFlag}`),
|
|
72
|
-
};
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
return { disabled: false };
|
|
76
|
-
}
|
|
16
|
+
export { buildFlag } from './ToolHandler.js';
|
|
77
17
|
export async function createMcpServer(serverArgs, options) {
|
|
78
|
-
let clearcutLogger;
|
|
79
18
|
if (serverArgs.usageStatistics) {
|
|
80
|
-
|
|
19
|
+
ClearcutLogger.initialize({
|
|
81
20
|
logFile: serverArgs.logFile,
|
|
82
21
|
appVersion: VERSION,
|
|
83
22
|
clearcutEndpoint: serverArgs.clearcutEndpoint,
|
|
@@ -108,7 +47,7 @@ export async function createMcpServer(serverArgs, options) {
|
|
|
108
47
|
server.server.oninitialized = () => {
|
|
109
48
|
const clientName = server.server.getClientVersion()?.name;
|
|
110
49
|
if (clientName) {
|
|
111
|
-
|
|
50
|
+
ClearcutLogger.get()?.setClientName(clientName);
|
|
112
51
|
}
|
|
113
52
|
if (server.server.getClientCapabilities()?.roots) {
|
|
114
53
|
void updateRoots();
|
|
@@ -164,110 +103,16 @@ export async function createMcpServer(serverArgs, options) {
|
|
|
164
103
|
}
|
|
165
104
|
const toolMutex = new Mutex();
|
|
166
105
|
function registerTool(tool) {
|
|
167
|
-
const
|
|
168
|
-
if (
|
|
106
|
+
const toolHandler = new ToolHandler(tool, serverArgs, getContext, toolMutex);
|
|
107
|
+
if (!toolHandler.shouldRegister) {
|
|
169
108
|
return;
|
|
170
109
|
}
|
|
171
|
-
const schema = 'pageScoped' in tool &&
|
|
172
|
-
tool.pageScoped &&
|
|
173
|
-
serverArgs.experimentalPageIdRouting &&
|
|
174
|
-
!serverArgs.slim
|
|
175
|
-
? { ...tool.schema, ...pageIdSchema }
|
|
176
|
-
: tool.schema;
|
|
177
110
|
server.registerTool(tool.name, {
|
|
178
111
|
description: tool.description,
|
|
179
|
-
inputSchema:
|
|
112
|
+
inputSchema: toolHandler.inputSchema,
|
|
180
113
|
annotations: tool.annotations,
|
|
181
114
|
}, async (params) => {
|
|
182
|
-
|
|
183
|
-
return {
|
|
184
|
-
content: [
|
|
185
|
-
{
|
|
186
|
-
type: 'text',
|
|
187
|
-
text: disabledReason,
|
|
188
|
-
},
|
|
189
|
-
],
|
|
190
|
-
isError: true,
|
|
191
|
-
};
|
|
192
|
-
}
|
|
193
|
-
const guard = await toolMutex.acquire();
|
|
194
|
-
const startTime = Date.now();
|
|
195
|
-
let success = false;
|
|
196
|
-
try {
|
|
197
|
-
logger(`${tool.name} request: ${JSON.stringify(params, null, ' ')}`);
|
|
198
|
-
const context = await getContext();
|
|
199
|
-
logger(`${tool.name} context: resolved`);
|
|
200
|
-
await context.detectOpenDevToolsWindows();
|
|
201
|
-
const response = serverArgs.slim
|
|
202
|
-
? new SlimMcpResponse(serverArgs)
|
|
203
|
-
: new McpResponse(serverArgs);
|
|
204
|
-
response.setRedactNetworkHeaders(serverArgs.redactNetworkHeaders);
|
|
205
|
-
try {
|
|
206
|
-
const page = serverArgs.experimentalPageIdRouting &&
|
|
207
|
-
params.pageId &&
|
|
208
|
-
!serverArgs.slim
|
|
209
|
-
? context.getPageById(params.pageId)
|
|
210
|
-
: context.getSelectedMcpPage();
|
|
211
|
-
response.setPage(page);
|
|
212
|
-
if (tool.blockedByDialog) {
|
|
213
|
-
page.throwIfDialogOpen();
|
|
214
|
-
}
|
|
215
|
-
if ('pageScoped' in tool && tool.pageScoped) {
|
|
216
|
-
await tool.handler({
|
|
217
|
-
params,
|
|
218
|
-
page,
|
|
219
|
-
}, response, context);
|
|
220
|
-
}
|
|
221
|
-
else {
|
|
222
|
-
await tool.handler(
|
|
223
|
-
// @ts-expect-error types do not match.
|
|
224
|
-
{
|
|
225
|
-
params,
|
|
226
|
-
}, response, context);
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
catch (err) {
|
|
230
|
-
response.setError(err);
|
|
231
|
-
}
|
|
232
|
-
const { content, structuredContent } = await response.handle(tool.name, context);
|
|
233
|
-
const result = {
|
|
234
|
-
content,
|
|
235
|
-
};
|
|
236
|
-
if (response.error) {
|
|
237
|
-
result.isError = true;
|
|
238
|
-
}
|
|
239
|
-
success = true;
|
|
240
|
-
if (serverArgs.experimentalStructuredContent) {
|
|
241
|
-
result.structuredContent = structuredContent;
|
|
242
|
-
}
|
|
243
|
-
return result;
|
|
244
|
-
}
|
|
245
|
-
catch (err) {
|
|
246
|
-
logger(`${tool.name} error:`, err, err?.stack);
|
|
247
|
-
let errorText = err && 'message' in err ? err.message : String(err);
|
|
248
|
-
if ('cause' in err && err.cause) {
|
|
249
|
-
errorText += `\nCause: ${err.cause.message}`;
|
|
250
|
-
}
|
|
251
|
-
return {
|
|
252
|
-
content: [
|
|
253
|
-
{
|
|
254
|
-
type: 'text',
|
|
255
|
-
text: errorText,
|
|
256
|
-
},
|
|
257
|
-
],
|
|
258
|
-
isError: true,
|
|
259
|
-
};
|
|
260
|
-
}
|
|
261
|
-
finally {
|
|
262
|
-
void clearcutLogger?.logToolInvocation({
|
|
263
|
-
toolName: tool.name,
|
|
264
|
-
params,
|
|
265
|
-
schema,
|
|
266
|
-
success,
|
|
267
|
-
latencyMs: bucketizeLatency(Date.now() - startTime),
|
|
268
|
-
});
|
|
269
|
-
guard.dispose();
|
|
270
|
-
}
|
|
115
|
+
return await toolHandler.handle(params);
|
|
271
116
|
});
|
|
272
117
|
}
|
|
273
118
|
const tools = createTools(serverArgs);
|
|
@@ -275,7 +120,7 @@ export async function createMcpServer(serverArgs, options) {
|
|
|
275
120
|
registerTool(tool);
|
|
276
121
|
}
|
|
277
122
|
await loadIssueDescriptions();
|
|
278
|
-
return { server
|
|
123
|
+
return { server };
|
|
279
124
|
}
|
|
280
125
|
export const logDisclaimers = (args) => {
|
|
281
126
|
console.error(`chrome-devtools-mcp exposes content of the browser instance to the MCP clients allowing them to inspect,
|
|
@@ -136,10 +136,25 @@ function detectOsType() {
|
|
|
136
136
|
return OsType.OS_TYPE_UNSPECIFIED;
|
|
137
137
|
}
|
|
138
138
|
}
|
|
139
|
+
// Not const to allow resetting the instance for testing purposes.
|
|
140
|
+
let _clearcut_logger_instance;
|
|
139
141
|
export class ClearcutLogger {
|
|
140
142
|
#persistence;
|
|
141
143
|
#watchdog;
|
|
142
144
|
#mcpClient;
|
|
145
|
+
static initialize(options) {
|
|
146
|
+
if (_clearcut_logger_instance) {
|
|
147
|
+
throw new Error('ClearcutLogger is already initialized');
|
|
148
|
+
}
|
|
149
|
+
_clearcut_logger_instance = new ClearcutLogger(options);
|
|
150
|
+
return _clearcut_logger_instance;
|
|
151
|
+
}
|
|
152
|
+
static get() {
|
|
153
|
+
return _clearcut_logger_instance;
|
|
154
|
+
}
|
|
155
|
+
static resetForTesting() {
|
|
156
|
+
_clearcut_logger_instance = undefined;
|
|
157
|
+
}
|
|
143
158
|
constructor(options) {
|
|
144
159
|
this.#persistence = options.persistence ?? new FilePersistence();
|
|
145
160
|
this.#watchdog =
|
|
@@ -237,6 +252,18 @@ export class ClearcutLogger {
|
|
|
237
252
|
logger('Error in logDailyActiveIfNeeded:', err);
|
|
238
253
|
}
|
|
239
254
|
}
|
|
255
|
+
async logServerError(args) {
|
|
256
|
+
this.#watchdog.send({
|
|
257
|
+
type: WatchdogMessageType.LOG_EVENT,
|
|
258
|
+
payload: {
|
|
259
|
+
mcp_client: this.#mcpClient,
|
|
260
|
+
server_error: {
|
|
261
|
+
tool_name: args.toolName ?? '',
|
|
262
|
+
error_code: args.errorCode,
|
|
263
|
+
},
|
|
264
|
+
},
|
|
265
|
+
});
|
|
266
|
+
}
|
|
240
267
|
#shouldLogDailyActive(state) {
|
|
241
268
|
if (!state.lastActive) {
|
|
242
269
|
return true;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Google LLC
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*
|
|
6
|
+
* IMPORTANT:
|
|
7
|
+
* 1. this module must only contain ErrorCode.
|
|
8
|
+
* 2. do not refactor ErrorCode to elsewhere.
|
|
9
|
+
*/
|
|
10
|
+
export var ErrorCode;
|
|
11
|
+
(function (ErrorCode) {
|
|
12
|
+
ErrorCode[ErrorCode["ERROR_CODE_UNSPECIFIED"] = 0] = "ERROR_CODE_UNSPECIFIED";
|
|
13
|
+
})(ErrorCode || (ErrorCode = {}));
|
|
14
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -11,14 +11,6 @@ export var OsType;
|
|
|
11
11
|
OsType[OsType["OS_TYPE_MACOS"] = 2] = "OS_TYPE_MACOS";
|
|
12
12
|
OsType[OsType["OS_TYPE_LINUX"] = 3] = "OS_TYPE_LINUX";
|
|
13
13
|
})(OsType || (OsType = {}));
|
|
14
|
-
export var ChromeChannel;
|
|
15
|
-
(function (ChromeChannel) {
|
|
16
|
-
ChromeChannel[ChromeChannel["CHROME_CHANNEL_UNSPECIFIED"] = 0] = "CHROME_CHANNEL_UNSPECIFIED";
|
|
17
|
-
ChromeChannel[ChromeChannel["CHROME_CHANNEL_CANARY"] = 1] = "CHROME_CHANNEL_CANARY";
|
|
18
|
-
ChromeChannel[ChromeChannel["CHROME_CHANNEL_DEV"] = 2] = "CHROME_CHANNEL_DEV";
|
|
19
|
-
ChromeChannel[ChromeChannel["CHROME_CHANNEL_BETA"] = 3] = "CHROME_CHANNEL_BETA";
|
|
20
|
-
ChromeChannel[ChromeChannel["CHROME_CHANNEL_STABLE"] = 4] = "CHROME_CHANNEL_STABLE";
|
|
21
|
-
})(ChromeChannel || (ChromeChannel = {}));
|
|
22
14
|
export var McpClient;
|
|
23
15
|
(function (McpClient) {
|
|
24
16
|
McpClient[McpClient["MCP_CLIENT_UNSPECIFIED"] = 0] = "MCP_CLIENT_UNSPECIFIED";
|
|
@@ -324,7 +324,7 @@ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH RE
|
|
|
324
324
|
|
|
325
325
|
Name: semver
|
|
326
326
|
URL: git+https://github.com/npm/node-semver.git
|
|
327
|
-
Version: 7.
|
|
327
|
+
Version: 7.8.0
|
|
328
328
|
License: ISC
|
|
329
329
|
|
|
330
330
|
The ISC License
|
|
@@ -617,15 +617,13 @@ SOFTWARE.
|
|
|
617
617
|
|
|
618
618
|
Name: fast-uri
|
|
619
619
|
URL: https://github.com/fastify/fast-uri
|
|
620
|
-
Version: 3.1.
|
|
620
|
+
Version: 3.1.2
|
|
621
621
|
License: BSD-3-Clause
|
|
622
622
|
|
|
623
623
|
Copyright (c) 2011-2021, Gary Court until https://github.com/garycourt/uri-js/commit/a1acf730b4bba3f1097c9f52e7d9d3aba8cdcaae
|
|
624
|
-
Copyright (c) 2021-present The Fastify team
|
|
624
|
+
Copyright (c) 2021-present The Fastify team <https://github.com/fastify/fastify#team>
|
|
625
625
|
All rights reserved.
|
|
626
626
|
|
|
627
|
-
The Fastify team members are listed at https://github.com/fastify/fastify#team.
|
|
628
|
-
|
|
629
627
|
Redistribution and use in source and binary forms, with or without
|
|
630
628
|
modification, are permitted provided that the following conditions are met:
|
|
631
629
|
* Redistributions of source code must retain the above copyright
|
|
@@ -1145,7 +1143,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
|
1145
1143
|
|
|
1146
1144
|
Name: ip-address
|
|
1147
1145
|
URL: git://github.com/beaugunderson/ip-address.git
|
|
1148
|
-
Version: 10.
|
|
1146
|
+
Version: 10.2.0
|
|
1149
1147
|
License: MIT
|
|
1150
1148
|
|
|
1151
1149
|
Copyright (C) 2011 by Beau Gunderson
|
|
@@ -1264,7 +1262,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
|
1264
1262
|
|
|
1265
1263
|
Name: basic-ftp
|
|
1266
1264
|
URL: https://github.com/patrickjuchli/basic-ftp.git
|
|
1267
|
-
Version: 5.3.
|
|
1265
|
+
Version: 5.3.1
|
|
1268
1266
|
License: MIT
|
|
1269
1267
|
|
|
1270
1268
|
Copyright (c) 2019 Patrick Juchli
|
|
@@ -3988,7 +3986,7 @@ SOFTWARE.
|
|
|
3988
3986
|
|
|
3989
3987
|
Name: puppeteer-core
|
|
3990
3988
|
URL: https://github.com/puppeteer/puppeteer/tree/main/packages/puppeteer-core
|
|
3991
|
-
Version: 24.
|
|
3989
|
+
Version: 24.43.0
|
|
3992
3990
|
License: Apache-2.0
|
|
3993
3991
|
|
|
3994
3992
|
-------------------- DEPENDENCY DIVIDER --------------------
|
|
@@ -4054,7 +4052,7 @@ SOFTWARE.
|
|
|
4054
4052
|
|
|
4055
4053
|
Name: tldts-core
|
|
4056
4054
|
URL: https://github.com/remusao/tldts#readme
|
|
4057
|
-
Version: 7.0.
|
|
4055
|
+
Version: 7.0.30
|
|
4058
4056
|
License: MIT
|
|
4059
4057
|
|
|
4060
4058
|
Copyright (c) 2017 Thomas Parisot, 2018 Rémi Berson
|
|
@@ -4076,7 +4074,7 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTH
|
|
|
4076
4074
|
|
|
4077
4075
|
Name: tldts-icann
|
|
4078
4076
|
URL: https://github.com/remusao/tldts#readme
|
|
4079
|
-
Version: 7.0.
|
|
4077
|
+
Version: 7.0.30
|
|
4080
4078
|
License: MIT
|
|
4081
4079
|
|
|
4082
4080
|
Copyright (c) 2017 Thomas Parisot, 2018 Rémi Berson
|
|
@@ -4118,7 +4116,7 @@ PERFORMANCE OF THIS SOFTWARE.
|
|
|
4118
4116
|
|
|
4119
4117
|
Name: web-features
|
|
4120
4118
|
URL: git+https://github.com/web-platform-dx/web-features.git
|
|
4121
|
-
Version: 3.
|
|
4119
|
+
Version: 3.26.0
|
|
4122
4120
|
License: Apache-2.0
|
|
4123
4121
|
|
|
4124
4122
|
Apache License
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"@modelcontextprotocol/sdk": "1.29.0",
|
|
3
|
-
"chrome-devtools-frontend": "1.0.
|
|
3
|
+
"chrome-devtools-frontend": "1.0.1626840",
|
|
4
4
|
"core-js": "3.49.0",
|
|
5
5
|
"debug": "4.4.3",
|
|
6
|
-
"lighthouse": "13.
|
|
6
|
+
"lighthouse": "13.3.0",
|
|
7
7
|
"semver": "^7.7.4",
|
|
8
8
|
"urlpattern-polyfill": "^10.1.0",
|
|
9
9
|
"yargs": "18.0.0",
|