chrome-devtools-mcp 0.16.0 → 0.17.1
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 +56 -9
- package/build/src/DevtoolsUtils.js +113 -0
- package/build/src/McpContext.js +3 -0
- package/build/src/McpResponse.js +1 -8
- package/build/src/PageCollector.js +4 -9
- package/build/src/cli.js +9 -0
- package/build/src/formatters/ConsoleFormatter.js +144 -72
- package/build/src/main.js +8 -4
- package/build/src/telemetry/{clearcut-logger.js → ClearcutLogger.js} +1 -1
- package/build/src/telemetry/watchdog/main.js +1 -1
- 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 +0 -2
- package/build/src/third_party/index.js +1424 -1311
- package/build/src/third_party/issue-descriptions/connectionAllowlistInvalidAllowlistItemType.md +12 -0
- package/build/src/third_party/issue-descriptions/connectionAllowlistInvalidHeader.md +12 -0
- package/build/src/third_party/issue-descriptions/connectionAllowlistInvalidUrlPattern.md +8 -0
- package/build/src/third_party/issue-descriptions/connectionAllowlistItemNotInnerList.md +12 -0
- package/build/src/third_party/issue-descriptions/connectionAllowlistMoreThanOneList.md +7 -0
- package/build/src/third_party/issue-descriptions/connectionAllowlistReportingEndpointNotToken.md +10 -0
- package/build/src/tools/performance.js +31 -1
- package/package.json +5 -4
- package/build/src/third_party/issue-descriptions/federatedAuthRequestClientMetadataHttpNotFound.md +0 -1
- package/build/src/third_party/issue-descriptions/federatedAuthRequestClientMetadataInvalidResponse.md +0 -1
- package/build/src/third_party/issue-descriptions/federatedAuthRequestClientMetadataNoResponse.md +0 -1
- /package/build/src/telemetry/{watchdog-client.js → WatchdogClient.js} +0 -0
- /package/build/src/telemetry/{flag-utils.js → flagUtils.js} +0 -0
- /package/build/src/telemetry/{metric-utils.js → metricUtils.js} +0 -0
- /package/build/src/telemetry/watchdog/{clearcut-sender.js → ClearcutSender.js} +0 -0
package/README.md
CHANGED
|
@@ -27,6 +27,12 @@ allowing them to inspect, debug, and modify any data in the browser or DevTools.
|
|
|
27
27
|
Avoid sharing sensitive or personal information that you don't want to share with
|
|
28
28
|
MCP clients.
|
|
29
29
|
|
|
30
|
+
Performance tools may send trace URLs to the Google CrUX API to fetch real-user
|
|
31
|
+
experience data. This helps provide a holistic performance picture by
|
|
32
|
+
presenting field data alongside lab data. This data is collected by the [Chrome
|
|
33
|
+
User Experience Report (CrUX)](https://developer.chrome.com/docs/crux). To disable
|
|
34
|
+
this, run with the `--no-performance-crux` flag.
|
|
35
|
+
|
|
30
36
|
## **Usage statistics**
|
|
31
37
|
|
|
32
38
|
Google collects usage statistics (such as tool invocation success rates, latency, and environment information) to improve the reliability and performance of Chrome DevTools MCP.
|
|
@@ -107,12 +113,31 @@ Chrome DevTools MCP will not start the browser instance automatically using this
|
|
|
107
113
|
|
|
108
114
|
<details>
|
|
109
115
|
<summary>Claude Code</summary>
|
|
110
|
-
|
|
116
|
+
|
|
117
|
+
**Install via CLI (MCP only)**
|
|
118
|
+
|
|
119
|
+
Use the Claude Code CLI to add the Chrome DevTools MCP server (<a href="https://code.claude.com/docs/en/mcp">guide</a>):
|
|
111
120
|
|
|
112
121
|
```bash
|
|
113
122
|
claude mcp add chrome-devtools --scope user npx chrome-devtools-mcp@latest
|
|
114
123
|
```
|
|
115
124
|
|
|
125
|
+
**Install as a Plugin (MCP + Skills)**
|
|
126
|
+
|
|
127
|
+
To install Chrome DevTools MCP with skills, add the marketplace registry in Claude Code:
|
|
128
|
+
|
|
129
|
+
```sh
|
|
130
|
+
/plugin marketplace add ChromeDevTools/chrome-devtools-mcp
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Then, install the plugin:
|
|
134
|
+
|
|
135
|
+
```sh
|
|
136
|
+
/plugin install chrome-devtools-mcp
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Restart Claude Code to have the MCP server and skills load (check with `/skills`).
|
|
140
|
+
|
|
116
141
|
</details>
|
|
117
142
|
|
|
118
143
|
<details>
|
|
@@ -260,6 +285,30 @@ Or, from the IDE **Activity Bar** > `Kiro` > `MCP Servers` > `Click Open MCP Con
|
|
|
260
285
|
|
|
261
286
|
</details>
|
|
262
287
|
|
|
288
|
+
<details>
|
|
289
|
+
<summary>Katalon Studio</summary>
|
|
290
|
+
|
|
291
|
+
The Chrome DevTools MCP server can be used with <a href="https://docs.katalon.com/katalon-studio/studioassist/mcp-servers/setting-up-chrome-devtools-mcp-server-for-studioassist">Katalon StudioAssist</a> via an MCP proxy.
|
|
292
|
+
|
|
293
|
+
**Step 1:** Install the MCP proxy by following the <a href="https://docs.katalon.com/katalon-studio/studioassist/mcp-servers/setting-up-mcp-proxy-for-stdio-mcp-servers">MCP proxy setup guide</a>.
|
|
294
|
+
|
|
295
|
+
**Step 2:** Start the Chrome DevTools MCP server with the proxy:
|
|
296
|
+
|
|
297
|
+
```bash
|
|
298
|
+
mcp-proxy --transport streamablehttp --port 8080 -- npx -y chrome-devtools-mcp@latest
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
**Note:** You may need to pick another port if 8080 is already in use.
|
|
302
|
+
|
|
303
|
+
**Step 3:** In Katalon Studio, add the server to StudioAssist with the following settings:
|
|
304
|
+
|
|
305
|
+
- **Connection URL:** `http://127.0.0.1:8080/mcp`
|
|
306
|
+
- **Transport type:** `HTTP`
|
|
307
|
+
|
|
308
|
+
Once connected, the Chrome DevTools MCP tools will be available in StudioAssist.
|
|
309
|
+
|
|
310
|
+
</details>
|
|
311
|
+
|
|
263
312
|
<details>
|
|
264
313
|
<summary>OpenCode</summary>
|
|
265
314
|
|
|
@@ -466,6 +515,11 @@ The Chrome DevTools MCP server supports the following configuration option:
|
|
|
466
515
|
- **Type:** boolean
|
|
467
516
|
- **Default:** `true`
|
|
468
517
|
|
|
518
|
+
- **`--performanceCrux`/ `--performance-crux`**
|
|
519
|
+
Set to false to disable sending URLs from performance traces to CrUX API to get field performance data.
|
|
520
|
+
- **Type:** boolean
|
|
521
|
+
- **Default:** `true`
|
|
522
|
+
|
|
469
523
|
- **`--usageStatistics`/ `--usage-statistics`**
|
|
470
524
|
Set to false to opt-out of usage statistics collection. Google collects usage data to improve the tool, handled under the Google Privacy Policy (https://policies.google.com/privacy). This is independent from Chrome browser metrics. Disabled if CHROME_DEVTOOLS_MCP_NO_USAGE_STATISTICS or CI env variables are set.
|
|
471
525
|
- **Type:** boolean
|
|
@@ -658,11 +712,4 @@ Please consult [these instructions](./docs/debugging-android.md).
|
|
|
658
712
|
|
|
659
713
|
## Known limitations
|
|
660
714
|
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
Some MCP clients allow sandboxing the MCP server using macOS Seatbelt or Linux
|
|
664
|
-
containers. If sandboxes are enabled, `chrome-devtools-mcp` is not able to start
|
|
665
|
-
Chrome that requires permissions to create its own sandboxes. As a workaround,
|
|
666
|
-
either disable sandboxing for `chrome-devtools-mcp` in your MCP client or use
|
|
667
|
-
`--browser-url` to connect to a Chrome instance that you start manually outside
|
|
668
|
-
of the MCP client sandbox.
|
|
715
|
+
See [Troubleshooting](./docs/troubleshooting.md).
|
|
@@ -169,6 +169,119 @@ const SKIP_ALL_PAUSES = {
|
|
|
169
169
|
// Do nothing.
|
|
170
170
|
},
|
|
171
171
|
};
|
|
172
|
+
/**
|
|
173
|
+
* Constructed from Runtime.ExceptionDetails of an uncaught error.
|
|
174
|
+
*
|
|
175
|
+
* TODO: Also construct from a RemoteObject of subtype 'error'.
|
|
176
|
+
*
|
|
177
|
+
* Consists of the message, a fully resolved stack trace and a fully resolved 'cause' chain.
|
|
178
|
+
*/
|
|
179
|
+
export class SymbolizedError {
|
|
180
|
+
message;
|
|
181
|
+
stackTrace;
|
|
182
|
+
cause;
|
|
183
|
+
constructor(message, stackTrace, cause) {
|
|
184
|
+
this.message = message;
|
|
185
|
+
this.stackTrace = stackTrace;
|
|
186
|
+
this.cause = cause;
|
|
187
|
+
}
|
|
188
|
+
static async fromDetails(opts) {
|
|
189
|
+
const message = SymbolizedError.#getMessage(opts.details);
|
|
190
|
+
if (!opts.includeStackAndCause || !opts.devTools) {
|
|
191
|
+
return new SymbolizedError(message, opts.resolvedStackTraceForTesting, opts.resolvedCauseForTesting);
|
|
192
|
+
}
|
|
193
|
+
let stackTrace;
|
|
194
|
+
if (opts.resolvedStackTraceForTesting) {
|
|
195
|
+
stackTrace = opts.resolvedStackTraceForTesting;
|
|
196
|
+
}
|
|
197
|
+
else if (opts.details.stackTrace) {
|
|
198
|
+
try {
|
|
199
|
+
stackTrace = await createStackTrace(opts.devTools, opts.details.stackTrace, opts.targetId);
|
|
200
|
+
}
|
|
201
|
+
catch {
|
|
202
|
+
// ignore
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
// TODO: Turn opts.details.exception into a JSHandle and retrieve the 'cause' property.
|
|
206
|
+
// If its an Error, recursively create a SymbolizedError.
|
|
207
|
+
let cause;
|
|
208
|
+
if (opts.resolvedCauseForTesting) {
|
|
209
|
+
cause = opts.resolvedCauseForTesting;
|
|
210
|
+
}
|
|
211
|
+
else if (opts.details.exception) {
|
|
212
|
+
try {
|
|
213
|
+
const causeRemoteObj = await SymbolizedError.#lookupCause(opts.devTools, opts.details.exception, opts.targetId);
|
|
214
|
+
if (causeRemoteObj) {
|
|
215
|
+
cause = await SymbolizedError.fromError({
|
|
216
|
+
devTools: opts.devTools,
|
|
217
|
+
error: causeRemoteObj,
|
|
218
|
+
targetId: opts.targetId,
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
catch {
|
|
223
|
+
// Ignore
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
return new SymbolizedError(message, stackTrace, cause);
|
|
227
|
+
}
|
|
228
|
+
static async fromError(opts) {
|
|
229
|
+
const details = await SymbolizedError.#getExceptionDetails(opts.devTools, opts.error, opts.targetId);
|
|
230
|
+
if (details) {
|
|
231
|
+
return SymbolizedError.fromDetails({
|
|
232
|
+
details,
|
|
233
|
+
devTools: opts.devTools,
|
|
234
|
+
targetId: opts.targetId,
|
|
235
|
+
includeStackAndCause: true,
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
return new SymbolizedError(SymbolizedError.#getMessageFromException(opts.error));
|
|
239
|
+
}
|
|
240
|
+
static #getMessage(details) {
|
|
241
|
+
// For Runtime.exceptionThrown with a present exception object, `details.text` will be "Uncaught" and
|
|
242
|
+
// we have to manually parse out the error text from the exception description.
|
|
243
|
+
// In the case of Runtime.getExceptionDetails, `details.text` has the Error.message.
|
|
244
|
+
if (details.text === 'Uncaught' && details.exception) {
|
|
245
|
+
return ('Uncaught ' +
|
|
246
|
+
SymbolizedError.#getMessageFromException(details.exception));
|
|
247
|
+
}
|
|
248
|
+
return details.text;
|
|
249
|
+
}
|
|
250
|
+
static #getMessageFromException(error) {
|
|
251
|
+
const messageWithRest = error.description?.split('\n at ', 2) ?? [];
|
|
252
|
+
return messageWithRest[0] ?? '';
|
|
253
|
+
}
|
|
254
|
+
static async #getExceptionDetails(devTools, error, targetId) {
|
|
255
|
+
if (!devTools || (error.type !== 'object' && error.subtype !== 'error')) {
|
|
256
|
+
return null;
|
|
257
|
+
}
|
|
258
|
+
const targetManager = devTools.universe.context.get(DevTools.TargetManager);
|
|
259
|
+
const target = targetId
|
|
260
|
+
? targetManager.targetById(targetId) || devTools.target
|
|
261
|
+
: devTools.target;
|
|
262
|
+
const model = target.model(DevTools.RuntimeModel);
|
|
263
|
+
return ((await model.getExceptionDetails(error.objectId)) ?? null);
|
|
264
|
+
}
|
|
265
|
+
static async #lookupCause(devTools, error, targetId) {
|
|
266
|
+
if (!devTools || (error.type !== 'object' && error.subtype !== 'error')) {
|
|
267
|
+
return null;
|
|
268
|
+
}
|
|
269
|
+
const targetManager = devTools.universe.context.get(DevTools.TargetManager);
|
|
270
|
+
const target = targetId
|
|
271
|
+
? targetManager.targetById(targetId) || devTools.target
|
|
272
|
+
: devTools.target;
|
|
273
|
+
const properties = await target.runtimeAgent().invoke_getProperties({
|
|
274
|
+
objectId: error.objectId,
|
|
275
|
+
});
|
|
276
|
+
if (properties.getError()) {
|
|
277
|
+
return null;
|
|
278
|
+
}
|
|
279
|
+
return properties.result.find(prop => prop.name === 'cause')?.value ?? null;
|
|
280
|
+
}
|
|
281
|
+
static createForTesting(message, stackTrace, cause) {
|
|
282
|
+
return new SymbolizedError(message, stackTrace, cause);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
172
285
|
export async function createStackTraceForConsoleMessage(devTools, consoleMessage) {
|
|
173
286
|
const message = consoleMessage;
|
|
174
287
|
const rawStackTrace = message._rawStackTrace();
|
package/build/src/McpContext.js
CHANGED
package/build/src/McpResponse.js
CHANGED
|
@@ -194,11 +194,6 @@ export class McpResponse {
|
|
|
194
194
|
}
|
|
195
195
|
detailedConsoleMessage = formatter;
|
|
196
196
|
}
|
|
197
|
-
else {
|
|
198
|
-
detailedConsoleMessage = await ConsoleFormatter.from(message, {
|
|
199
|
-
id: consoleMessageStableId,
|
|
200
|
-
});
|
|
201
|
-
}
|
|
202
197
|
}
|
|
203
198
|
let extensions;
|
|
204
199
|
if (this.#listExtensions) {
|
|
@@ -239,9 +234,7 @@ export class McpResponse {
|
|
|
239
234
|
}
|
|
240
235
|
return formatter;
|
|
241
236
|
}
|
|
242
|
-
return
|
|
243
|
-
id: consoleMessageStableId,
|
|
244
|
-
});
|
|
237
|
+
return null;
|
|
245
238
|
}))).filter(item => item !== null);
|
|
246
239
|
}
|
|
247
240
|
let networkRequests;
|
|
@@ -7,12 +7,10 @@ import { FakeIssuesManager } from './DevtoolsUtils.js';
|
|
|
7
7
|
import { logger } from './logger.js';
|
|
8
8
|
import { DevTools } from './third_party/index.js';
|
|
9
9
|
export class UncaughtError {
|
|
10
|
-
|
|
11
|
-
stackTrace;
|
|
10
|
+
details;
|
|
12
11
|
targetId;
|
|
13
|
-
constructor(
|
|
14
|
-
this.
|
|
15
|
-
this.stackTrace = stackTrace;
|
|
12
|
+
constructor(details, targetId) {
|
|
13
|
+
this.details = details;
|
|
16
14
|
this.targetId = targetId;
|
|
17
15
|
}
|
|
18
16
|
}
|
|
@@ -238,10 +236,7 @@ class PageEventSubscriber {
|
|
|
238
236
|
this.#page.emit('issue', event.data);
|
|
239
237
|
};
|
|
240
238
|
#onExceptionThrown = (event) => {
|
|
241
|
-
|
|
242
|
-
const messageWithRest = exception?.description?.split('\n at ', 2) ?? [];
|
|
243
|
-
const message = text + ' ' + (messageWithRest[0] ?? '');
|
|
244
|
-
this.#page.emit('uncaughtError', new UncaughtError(message, stackTrace, this.#targetId));
|
|
239
|
+
this.#page.emit('uncaughtError', new UncaughtError(event.exceptionDetails, this.#targetId));
|
|
245
240
|
};
|
|
246
241
|
// On navigation, we reset issue aggregation.
|
|
247
242
|
#onFrameNavigated = (frame) => {
|
package/build/src/cli.js
CHANGED
|
@@ -188,6 +188,11 @@ export const cliOptions = {
|
|
|
188
188
|
hidden: true,
|
|
189
189
|
describe: 'Set to false to exclude tools related to extensions.',
|
|
190
190
|
},
|
|
191
|
+
performanceCrux: {
|
|
192
|
+
type: 'boolean',
|
|
193
|
+
default: true,
|
|
194
|
+
describe: 'Set to false to disable sending URLs from performance traces to CrUX API to get field performance data.',
|
|
195
|
+
},
|
|
191
196
|
usageStatistics: {
|
|
192
197
|
type: 'boolean',
|
|
193
198
|
default: true,
|
|
@@ -277,6 +282,10 @@ export function parseArguments(version, argv = process.argv) {
|
|
|
277
282
|
'$0 --no-usage-statistics',
|
|
278
283
|
'Do not send usage statistics https://github.com/ChromeDevTools/chrome-devtools-mcp#usage-statistics.',
|
|
279
284
|
],
|
|
285
|
+
[
|
|
286
|
+
'$0 --no-performance-crux',
|
|
287
|
+
'Disable CrUX (field data) integration in performance tools.',
|
|
288
|
+
],
|
|
280
289
|
]);
|
|
281
290
|
return yargsInstance
|
|
282
291
|
.wrap(Math.min(120, yargsInstance.terminalWidth()))
|
|
@@ -3,35 +3,80 @@
|
|
|
3
3
|
* Copyright 2026 Google LLC
|
|
4
4
|
* SPDX-License-Identifier: Apache-2.0
|
|
5
5
|
*/
|
|
6
|
-
|
|
6
|
+
var _a;
|
|
7
|
+
import { createStackTraceForConsoleMessage, SymbolizedError, } from '../DevtoolsUtils.js';
|
|
8
|
+
import { UncaughtError } from '../PageCollector.js';
|
|
9
|
+
import * as DevTools from '../third_party/index.js';
|
|
7
10
|
export class ConsoleFormatter {
|
|
8
|
-
#
|
|
9
|
-
#resolvedArgs = [];
|
|
10
|
-
#resolvedStackTrace;
|
|
11
|
+
static #STACK_TRACE_MAX_LINES = 50;
|
|
11
12
|
#id;
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
#type;
|
|
14
|
+
#text;
|
|
15
|
+
#argCount;
|
|
16
|
+
#resolvedArgs;
|
|
17
|
+
#stack;
|
|
18
|
+
#cause;
|
|
19
|
+
#isIgnored;
|
|
20
|
+
constructor(params) {
|
|
21
|
+
this.#id = params.id;
|
|
22
|
+
this.#type = params.type;
|
|
23
|
+
this.#text = params.text;
|
|
24
|
+
this.#argCount = params.argCount ?? 0;
|
|
25
|
+
this.#resolvedArgs = params.resolvedArgs ?? [];
|
|
26
|
+
this.#stack = params.stack;
|
|
27
|
+
this.#cause = params.cause;
|
|
28
|
+
this.#isIgnored = params.isIgnored;
|
|
16
29
|
}
|
|
17
30
|
static async from(msg, options) {
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
31
|
+
const ignoreListManager = options?.devTools?.universe.context.get(DevTools.DevTools.IgnoreListManager);
|
|
32
|
+
const isIgnored = options.isIgnoredForTesting ||
|
|
33
|
+
(frame => {
|
|
34
|
+
if (!ignoreListManager) {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
if (frame.uiSourceCode) {
|
|
38
|
+
return ignoreListManager.isUserOrSourceMapIgnoreListedUISourceCode(frame.uiSourceCode);
|
|
39
|
+
}
|
|
40
|
+
if (frame.url) {
|
|
41
|
+
return ignoreListManager.isUserIgnoreListedURL(frame.url);
|
|
42
|
+
}
|
|
43
|
+
return false;
|
|
44
|
+
});
|
|
45
|
+
if (msg instanceof UncaughtError) {
|
|
46
|
+
const error = await SymbolizedError.fromDetails({
|
|
47
|
+
devTools: options?.devTools,
|
|
48
|
+
details: msg.details,
|
|
49
|
+
targetId: msg.targetId,
|
|
50
|
+
includeStackAndCause: options?.fetchDetailedData,
|
|
51
|
+
resolvedStackTraceForTesting: options?.resolvedStackTraceForTesting,
|
|
52
|
+
resolvedCauseForTesting: options?.resolvedCauseForTesting,
|
|
53
|
+
});
|
|
54
|
+
return new _a({
|
|
55
|
+
id: options.id,
|
|
56
|
+
type: 'error',
|
|
57
|
+
text: error.message,
|
|
58
|
+
stack: error.stackTrace,
|
|
59
|
+
cause: error.cause,
|
|
60
|
+
isIgnored,
|
|
61
|
+
});
|
|
21
62
|
}
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
// No `instanceof` as tests mock `ConsoleMessage`.
|
|
26
|
-
return 'args' in msg && typeof msg.args === 'function';
|
|
27
|
-
}
|
|
28
|
-
async #loadDetailedData(devTools) {
|
|
29
|
-
if (this.#msg instanceof Error) {
|
|
30
|
-
return;
|
|
63
|
+
let resolvedArgs = [];
|
|
64
|
+
if (options.resolvedArgsForTesting) {
|
|
65
|
+
resolvedArgs = options.resolvedArgsForTesting;
|
|
31
66
|
}
|
|
32
|
-
if (
|
|
33
|
-
|
|
67
|
+
else if (options.fetchDetailedData) {
|
|
68
|
+
resolvedArgs = await Promise.all(msg.args().map(async (arg, i) => {
|
|
34
69
|
try {
|
|
70
|
+
const remoteObject = arg.remoteObject();
|
|
71
|
+
if (remoteObject.type === 'object' &&
|
|
72
|
+
remoteObject.subtype === 'error') {
|
|
73
|
+
return await SymbolizedError.fromError({
|
|
74
|
+
devTools: options.devTools,
|
|
75
|
+
error: remoteObject,
|
|
76
|
+
// @ts-expect-error Internal ConsoleMessage API
|
|
77
|
+
targetId: msg._targetId(),
|
|
78
|
+
});
|
|
79
|
+
}
|
|
35
80
|
return await arg.jsonValue();
|
|
36
81
|
}
|
|
37
82
|
catch {
|
|
@@ -39,71 +84,66 @@ export class ConsoleFormatter {
|
|
|
39
84
|
}
|
|
40
85
|
}));
|
|
41
86
|
}
|
|
42
|
-
|
|
87
|
+
let stack;
|
|
88
|
+
if (options.resolvedStackTraceForTesting) {
|
|
89
|
+
stack = options.resolvedStackTraceForTesting;
|
|
90
|
+
}
|
|
91
|
+
else if (options.fetchDetailedData && options.devTools) {
|
|
43
92
|
try {
|
|
44
|
-
|
|
45
|
-
this.#resolvedStackTrace = await createStackTraceForConsoleMessage(devTools, this.#msg);
|
|
46
|
-
}
|
|
47
|
-
else if (this.#msg.stackTrace) {
|
|
48
|
-
this.#resolvedStackTrace = await createStackTrace(devTools, this.#msg.stackTrace, this.#msg.targetId);
|
|
49
|
-
}
|
|
93
|
+
stack = await createStackTraceForConsoleMessage(options.devTools, msg);
|
|
50
94
|
}
|
|
51
95
|
catch {
|
|
52
96
|
// ignore
|
|
53
97
|
}
|
|
54
98
|
}
|
|
99
|
+
return new _a({
|
|
100
|
+
id: options.id,
|
|
101
|
+
type: msg.type(),
|
|
102
|
+
text: msg.text(),
|
|
103
|
+
argCount: resolvedArgs.length || msg.args().length,
|
|
104
|
+
resolvedArgs,
|
|
105
|
+
stack,
|
|
106
|
+
isIgnored,
|
|
107
|
+
});
|
|
55
108
|
}
|
|
56
109
|
// The short format for a console message.
|
|
57
110
|
toString() {
|
|
58
|
-
|
|
59
|
-
const text = this.#getText();
|
|
60
|
-
const argsCount = this.#getArgsCount();
|
|
61
|
-
const idPart = this.#id !== undefined ? `msgid=${this.#id} ` : '';
|
|
62
|
-
return `${idPart}[${type}] ${text} (${argsCount} args)`;
|
|
111
|
+
return `msgid=${this.#id} [${this.#type}] ${this.#text} (${this.#argCount} args)`;
|
|
63
112
|
}
|
|
64
113
|
// The verbose format for a console message, including all details.
|
|
65
114
|
toStringDetailed() {
|
|
66
115
|
const result = [
|
|
67
|
-
|
|
68
|
-
`Message: ${this.#
|
|
116
|
+
`ID: ${this.#id}`,
|
|
117
|
+
`Message: ${this.#type}> ${this.#text}`,
|
|
69
118
|
this.#formatArgs(),
|
|
70
|
-
this.#formatStackTrace(this.#
|
|
119
|
+
this.#formatStackTrace(this.#stack, this.#cause, {
|
|
120
|
+
includeHeading: true,
|
|
121
|
+
}),
|
|
71
122
|
].filter(line => !!line);
|
|
72
123
|
return result.join('\n');
|
|
73
124
|
}
|
|
74
|
-
#getType() {
|
|
75
|
-
if (!this.#isConsoleMessage(this.#msg)) {
|
|
76
|
-
return 'error';
|
|
77
|
-
}
|
|
78
|
-
return this.#msg.type();
|
|
79
|
-
}
|
|
80
|
-
#getText() {
|
|
81
|
-
if (!this.#isConsoleMessage(this.#msg)) {
|
|
82
|
-
return this.#msg.message;
|
|
83
|
-
}
|
|
84
|
-
return this.#msg.text();
|
|
85
|
-
}
|
|
86
125
|
#getArgs() {
|
|
87
|
-
if (!this.#isConsoleMessage(this.#msg)) {
|
|
88
|
-
return [];
|
|
89
|
-
}
|
|
90
126
|
if (this.#resolvedArgs.length > 0) {
|
|
91
127
|
const args = [...this.#resolvedArgs];
|
|
92
128
|
// If there is no text, the first argument serves as text (see formatMessage).
|
|
93
|
-
if (!this.#
|
|
129
|
+
if (!this.#text) {
|
|
94
130
|
args.shift();
|
|
95
131
|
}
|
|
96
132
|
return args;
|
|
97
133
|
}
|
|
98
134
|
return [];
|
|
99
135
|
}
|
|
100
|
-
#getArgsCount() {
|
|
101
|
-
if (!this.#isConsoleMessage(this.#msg)) {
|
|
102
|
-
return 0;
|
|
103
|
-
}
|
|
104
|
-
return this.#resolvedArgs.length || this.#msg.args().length;
|
|
105
|
-
}
|
|
106
136
|
#formatArg(arg) {
|
|
137
|
+
if (arg instanceof SymbolizedError) {
|
|
138
|
+
return [
|
|
139
|
+
arg.message,
|
|
140
|
+
this.#formatStackTrace(arg.stackTrace, arg.cause, {
|
|
141
|
+
includeHeading: false,
|
|
142
|
+
}),
|
|
143
|
+
]
|
|
144
|
+
.filter(line => !!line)
|
|
145
|
+
.join('\n');
|
|
146
|
+
}
|
|
107
147
|
return typeof arg === 'object' ? JSON.stringify(arg) : String(arg);
|
|
108
148
|
}
|
|
109
149
|
#formatArgs() {
|
|
@@ -117,25 +157,47 @@ export class ConsoleFormatter {
|
|
|
117
157
|
}
|
|
118
158
|
return result.join('\n');
|
|
119
159
|
}
|
|
120
|
-
#formatStackTrace(stackTrace) {
|
|
160
|
+
#formatStackTrace(stackTrace, cause, opts) {
|
|
121
161
|
if (!stackTrace) {
|
|
122
162
|
return '';
|
|
123
163
|
}
|
|
164
|
+
const lines = this.#formatStackTraceInner(stackTrace, cause);
|
|
165
|
+
const includedLines = lines.slice(0, _a.#STACK_TRACE_MAX_LINES);
|
|
166
|
+
const reminderCount = lines.length - includedLines.length;
|
|
124
167
|
return [
|
|
125
|
-
'### Stack trace',
|
|
126
|
-
|
|
127
|
-
|
|
168
|
+
opts.includeHeading ? '### Stack trace' : '',
|
|
169
|
+
...includedLines,
|
|
170
|
+
reminderCount > 0 ? `... and ${reminderCount} more frames` : '',
|
|
128
171
|
'Note: line and column numbers use 1-based indexing',
|
|
129
|
-
]
|
|
172
|
+
]
|
|
173
|
+
.filter(line => !!line)
|
|
174
|
+
.join('\n');
|
|
175
|
+
}
|
|
176
|
+
#formatStackTraceInner(stackTrace, cause) {
|
|
177
|
+
if (!stackTrace) {
|
|
178
|
+
return [];
|
|
179
|
+
}
|
|
180
|
+
return [
|
|
181
|
+
...this.#formatFragment(stackTrace.syncFragment),
|
|
182
|
+
...stackTrace.asyncFragments
|
|
183
|
+
.map(this.#formatAsyncFragment.bind(this))
|
|
184
|
+
.flat(),
|
|
185
|
+
...this.#formatCause(cause),
|
|
186
|
+
];
|
|
130
187
|
}
|
|
131
188
|
#formatFragment(fragment) {
|
|
132
|
-
|
|
189
|
+
const frames = fragment.frames.filter(frame => !this.#isIgnored(frame));
|
|
190
|
+
return frames.map(this.#formatFrame.bind(this));
|
|
133
191
|
}
|
|
134
192
|
#formatAsyncFragment(fragment) {
|
|
193
|
+
const formattedFrames = this.#formatFragment(fragment);
|
|
194
|
+
if (formattedFrames.length === 0) {
|
|
195
|
+
return [];
|
|
196
|
+
}
|
|
135
197
|
const separatorLineLength = 40;
|
|
136
198
|
const prefix = `--- ${fragment.description || 'async'} `;
|
|
137
199
|
const separator = prefix + '-'.repeat(separatorLineLength - prefix.length);
|
|
138
|
-
return separator
|
|
200
|
+
return [separator, ...formattedFrames];
|
|
139
201
|
}
|
|
140
202
|
#formatFrame(frame) {
|
|
141
203
|
let result = `at ${frame.name ?? '<anonymous>'}`;
|
|
@@ -148,21 +210,31 @@ export class ConsoleFormatter {
|
|
|
148
210
|
}
|
|
149
211
|
return result;
|
|
150
212
|
}
|
|
213
|
+
#formatCause(cause) {
|
|
214
|
+
if (!cause) {
|
|
215
|
+
return [];
|
|
216
|
+
}
|
|
217
|
+
return [
|
|
218
|
+
`Caused by: ${cause.message}`,
|
|
219
|
+
...this.#formatStackTraceInner(cause.stackTrace, cause.cause),
|
|
220
|
+
];
|
|
221
|
+
}
|
|
151
222
|
toJSON() {
|
|
152
223
|
return {
|
|
153
|
-
type: this.#
|
|
154
|
-
text: this.#
|
|
155
|
-
argsCount: this.#
|
|
224
|
+
type: this.#type,
|
|
225
|
+
text: this.#text,
|
|
226
|
+
argsCount: this.#argCount,
|
|
156
227
|
id: this.#id,
|
|
157
228
|
};
|
|
158
229
|
}
|
|
159
230
|
toJSONDetailed() {
|
|
160
231
|
return {
|
|
161
232
|
id: this.#id,
|
|
162
|
-
type: this.#
|
|
163
|
-
text: this.#
|
|
233
|
+
type: this.#type,
|
|
234
|
+
text: this.#text,
|
|
164
235
|
args: this.#getArgs().map(arg => typeof arg === 'object' ? arg : String(arg)),
|
|
165
|
-
stackTrace: this.#
|
|
236
|
+
stackTrace: this.#stack,
|
|
166
237
|
};
|
|
167
238
|
}
|
|
168
239
|
}
|
|
240
|
+
_a = ConsoleFormatter;
|
package/build/src/main.js
CHANGED
|
@@ -12,15 +12,15 @@ import { logger, saveLogsToFile } from './logger.js';
|
|
|
12
12
|
import { McpContext } from './McpContext.js';
|
|
13
13
|
import { McpResponse } from './McpResponse.js';
|
|
14
14
|
import { Mutex } from './Mutex.js';
|
|
15
|
-
import { ClearcutLogger } from './telemetry/
|
|
16
|
-
import { computeFlagUsage } from './telemetry/
|
|
17
|
-
import { bucketizeLatency } from './telemetry/
|
|
15
|
+
import { ClearcutLogger } from './telemetry/ClearcutLogger.js';
|
|
16
|
+
import { computeFlagUsage } from './telemetry/flagUtils.js';
|
|
17
|
+
import { bucketizeLatency } from './telemetry/metricUtils.js';
|
|
18
18
|
import { McpServer, StdioServerTransport, SetLevelRequestSchema, } from './third_party/index.js';
|
|
19
19
|
import { ToolCategory } from './tools/categories.js';
|
|
20
20
|
import { tools } from './tools/tools.js';
|
|
21
21
|
// If moved update release-please config
|
|
22
22
|
// x-release-please-start-version
|
|
23
|
-
const VERSION = '0.
|
|
23
|
+
const VERSION = '0.17.1';
|
|
24
24
|
// x-release-please-end
|
|
25
25
|
export const args = parseArguments(VERSION);
|
|
26
26
|
const logFile = args.logFile ? saveLogsToFile(args.logFile) : undefined;
|
|
@@ -87,6 +87,7 @@ async function getContext() {
|
|
|
87
87
|
context = await McpContext.from(browser, logger, {
|
|
88
88
|
experimentalDevToolsDebugging: devtools,
|
|
89
89
|
experimentalIncludeAllPages: args.experimentalIncludeAllPages,
|
|
90
|
+
performanceCrux: args.performanceCrux,
|
|
90
91
|
});
|
|
91
92
|
}
|
|
92
93
|
return context;
|
|
@@ -95,6 +96,9 @@ const logDisclaimers = () => {
|
|
|
95
96
|
console.error(`chrome-devtools-mcp exposes content of the browser instance to the MCP clients allowing them to inspect,
|
|
96
97
|
debug, and modify any data in the browser or DevTools.
|
|
97
98
|
Avoid sharing sensitive or personal information that you do not want to share with MCP clients.`);
|
|
99
|
+
if (args.performanceCrux) {
|
|
100
|
+
console.error(`Performance tools may send trace URLs to the Google CrUX API to fetch real-user experience data. To disable, run with --no-performance-crux.`);
|
|
101
|
+
}
|
|
98
102
|
if (args.usageStatistics) {
|
|
99
103
|
console.error(`
|
|
100
104
|
Google collects usage statistics to improve Chrome DevTools MCP. To opt-out, run with --no-usage-statistics.
|
|
@@ -7,7 +7,7 @@ import process from 'node:process';
|
|
|
7
7
|
import { logger } from '../logger.js';
|
|
8
8
|
import { FilePersistence } from './persistence.js';
|
|
9
9
|
import { WatchdogMessageType, OsType } from './types.js';
|
|
10
|
-
import { WatchdogClient } from './
|
|
10
|
+
import { WatchdogClient } from './WatchdogClient.js';
|
|
11
11
|
const MS_PER_DAY = 24 * 60 * 60 * 1000;
|
|
12
12
|
function detectOsType() {
|
|
13
13
|
switch (process.platform) {
|
|
@@ -8,7 +8,7 @@ import readline from 'node:readline';
|
|
|
8
8
|
import { parseArgs } from 'node:util';
|
|
9
9
|
import { logger, flushLogs, saveLogsToFile } from '../../logger.js';
|
|
10
10
|
import { WatchdogMessageType } from '../types.js';
|
|
11
|
-
import { ClearcutSender } from './
|
|
11
|
+
import { ClearcutSender } from './ClearcutSender.js';
|
|
12
12
|
function parseWatchdogArgs() {
|
|
13
13
|
const { values } = parseArgs({
|
|
14
14
|
options: {
|