chrome-devtools-mcp 0.6.1 → 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/README.md +2 -2
- package/build/src/McpResponse.js +25 -5
- package/build/src/browser.js +0 -2
- package/build/src/formatters/networkFormatter.js +44 -0
- package/build/src/tools/emulation.js +13 -2
- package/build/src/tools/performance.js +0 -1
- package/build/src/trace-processing/parse.js +6 -5
- package/package.json +5 -4
package/README.md
CHANGED
|
@@ -111,7 +111,7 @@ Start the dialog to add a new MCP server by running:
|
|
|
111
111
|
/mcp add
|
|
112
112
|
```
|
|
113
113
|
|
|
114
|
-
Configure the following fields and press `
|
|
114
|
+
Configure the following fields and press `CTRL+S` to save the configuration:
|
|
115
115
|
|
|
116
116
|
- **Server name:** `chrome-devtools`
|
|
117
117
|
- **Server Type:** `[1] Local`
|
|
@@ -336,5 +336,5 @@ Some MCP clients allow sandboxing the MCP server using macOS Seatbelt or Linux
|
|
|
336
336
|
containers. If sandboxes are enabled, `chrome-devtools-mcp` is not able to start
|
|
337
337
|
Chrome that requires permissions to create its own sandboxes. As a workaround,
|
|
338
338
|
either disable sandboxing for `chrome-devtools-mcp` in your MCP client or use
|
|
339
|
-
`--
|
|
339
|
+
`--browser-url` to connect to a Chrome instance that you start manually outside
|
|
340
340
|
of the MCP client sandbox.
|
package/build/src/McpResponse.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { formatConsoleEvent } from './formatters/consoleFormatter.js';
|
|
2
|
-
import { getFormattedHeaderValue, getShortDescriptionForRequest, getStatusFromRequest, } from './formatters/networkFormatter.js';
|
|
2
|
+
import { getFormattedHeaderValue, getFormattedResponseBody, getFormattedRequestBody, getShortDescriptionForRequest, getStatusFromRequest, } from './formatters/networkFormatter.js';
|
|
3
3
|
import { formatA11ySnapshot } from './formatters/snapshotFormatter.js';
|
|
4
4
|
import { handleDialog } from './tools/pages.js';
|
|
5
5
|
import { paginate } from './utils/pagination.js';
|
|
6
6
|
export class McpResponse {
|
|
7
7
|
#includePages = false;
|
|
8
8
|
#includeSnapshot = false;
|
|
9
|
-
#
|
|
9
|
+
#attachedNetworkRequestData;
|
|
10
10
|
#includeConsoleData = false;
|
|
11
11
|
#textResponseLines = [];
|
|
12
12
|
#formattedConsoleData;
|
|
@@ -38,7 +38,9 @@ export class McpResponse {
|
|
|
38
38
|
this.#includeConsoleData = value;
|
|
39
39
|
}
|
|
40
40
|
attachNetworkRequest(url) {
|
|
41
|
-
this.#
|
|
41
|
+
this.#attachedNetworkRequestData = {
|
|
42
|
+
networkRequestUrl: url,
|
|
43
|
+
};
|
|
42
44
|
}
|
|
43
45
|
get includePages() {
|
|
44
46
|
return this.#includePages;
|
|
@@ -50,7 +52,7 @@ export class McpResponse {
|
|
|
50
52
|
return this.#includeConsoleData;
|
|
51
53
|
}
|
|
52
54
|
get attachedNetworkRequestUrl() {
|
|
53
|
-
return this.#
|
|
55
|
+
return this.#attachedNetworkRequestData?.networkRequestUrl;
|
|
54
56
|
}
|
|
55
57
|
get networkRequestsPageIdx() {
|
|
56
58
|
return this.#networkRequestsOptions?.pagination?.pageIdx;
|
|
@@ -78,6 +80,16 @@ export class McpResponse {
|
|
|
78
80
|
await context.createTextSnapshot();
|
|
79
81
|
}
|
|
80
82
|
let formattedConsoleMessages;
|
|
83
|
+
if (this.#attachedNetworkRequestData?.networkRequestUrl) {
|
|
84
|
+
const request = context.getNetworkRequestByUrl(this.#attachedNetworkRequestData.networkRequestUrl);
|
|
85
|
+
this.#attachedNetworkRequestData.requestBody =
|
|
86
|
+
await getFormattedRequestBody(request);
|
|
87
|
+
const response = request.response();
|
|
88
|
+
if (response) {
|
|
89
|
+
this.#attachedNetworkRequestData.responseBody =
|
|
90
|
+
await getFormattedResponseBody(response);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
81
93
|
if (this.#includeConsoleData) {
|
|
82
94
|
const consoleMessages = context.getConsoleData();
|
|
83
95
|
if (consoleMessages) {
|
|
@@ -193,7 +205,7 @@ Call ${handleDialog.name} to handle it before continuing.`);
|
|
|
193
205
|
}
|
|
194
206
|
#getIncludeNetworkRequestsData(context) {
|
|
195
207
|
const response = [];
|
|
196
|
-
const url = this.#
|
|
208
|
+
const url = this.#attachedNetworkRequestData?.networkRequestUrl;
|
|
197
209
|
if (!url) {
|
|
198
210
|
return response;
|
|
199
211
|
}
|
|
@@ -204,6 +216,10 @@ Call ${handleDialog.name} to handle it before continuing.`);
|
|
|
204
216
|
for (const line of getFormattedHeaderValue(httpRequest.headers())) {
|
|
205
217
|
response.push(line);
|
|
206
218
|
}
|
|
219
|
+
if (this.#attachedNetworkRequestData?.requestBody) {
|
|
220
|
+
response.push(`### Request Body`);
|
|
221
|
+
response.push(this.#attachedNetworkRequestData.requestBody);
|
|
222
|
+
}
|
|
207
223
|
const httpResponse = httpRequest.response();
|
|
208
224
|
if (httpResponse) {
|
|
209
225
|
response.push(`### Response Headers`);
|
|
@@ -211,6 +227,10 @@ Call ${handleDialog.name} to handle it before continuing.`);
|
|
|
211
227
|
response.push(line);
|
|
212
228
|
}
|
|
213
229
|
}
|
|
230
|
+
if (this.#attachedNetworkRequestData?.responseBody) {
|
|
231
|
+
response.push(`### Response Body`);
|
|
232
|
+
response.push(this.#attachedNetworkRequestData.responseBody);
|
|
233
|
+
}
|
|
214
234
|
const httpFailure = httpRequest.failure();
|
|
215
235
|
if (httpFailure) {
|
|
216
236
|
response.push(`### Request failed with`);
|
package/build/src/browser.js
CHANGED
|
@@ -27,8 +27,6 @@ function targetFilter(target) {
|
|
|
27
27
|
}
|
|
28
28
|
const connectOptions = {
|
|
29
29
|
targetFilter,
|
|
30
|
-
// We do not expect any single CDP command to take more than 10sec.
|
|
31
|
-
protocolTimeout: 10_000,
|
|
32
30
|
};
|
|
33
31
|
export async function ensureBrowserConnected(browserURL) {
|
|
34
32
|
if (browser?.connected) {
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
* Copyright 2025 Google LLC
|
|
4
4
|
* SPDX-License-Identifier: Apache-2.0
|
|
5
5
|
*/
|
|
6
|
+
import { isUtf8 } from 'node:buffer';
|
|
7
|
+
const BODY_CONTEXT_SIZE_LIMIT = 10000;
|
|
6
8
|
export function getShortDescriptionForRequest(request) {
|
|
7
9
|
return `${request.url()} ${request.method()} ${getStatusFromRequest(request)}`;
|
|
8
10
|
}
|
|
@@ -32,3 +34,45 @@ export function getFormattedHeaderValue(headers) {
|
|
|
32
34
|
}
|
|
33
35
|
return response;
|
|
34
36
|
}
|
|
37
|
+
export async function getFormattedResponseBody(httpResponse, sizeLimit = BODY_CONTEXT_SIZE_LIMIT) {
|
|
38
|
+
try {
|
|
39
|
+
const responseBuffer = await httpResponse.buffer();
|
|
40
|
+
if (isUtf8(responseBuffer)) {
|
|
41
|
+
const responseAsTest = responseBuffer.toString('utf-8');
|
|
42
|
+
if (responseAsTest.length === 0) {
|
|
43
|
+
return `<empty response>`;
|
|
44
|
+
}
|
|
45
|
+
return `${getSizeLimitedString(responseAsTest, sizeLimit)}`;
|
|
46
|
+
}
|
|
47
|
+
return `<binary data>`;
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
// buffer() call might fail with CDP exception, in this case we don't print anything in the context
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
export async function getFormattedRequestBody(httpRequest, sizeLimit = BODY_CONTEXT_SIZE_LIMIT) {
|
|
55
|
+
if (httpRequest.hasPostData()) {
|
|
56
|
+
const data = httpRequest.postData();
|
|
57
|
+
if (data) {
|
|
58
|
+
return `${getSizeLimitedString(data, sizeLimit)}`;
|
|
59
|
+
}
|
|
60
|
+
try {
|
|
61
|
+
const fetchData = await httpRequest.fetchPostData();
|
|
62
|
+
if (fetchData) {
|
|
63
|
+
return `${getSizeLimitedString(fetchData, sizeLimit)}`;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
// fetchPostData() call might fail with CDP exception, in this case we don't print anything in the context
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
function getSizeLimitedString(text, sizeLimit) {
|
|
74
|
+
if (text.length > sizeLimit) {
|
|
75
|
+
return `${text.substring(0, sizeLimit) + '... <truncated>'}`;
|
|
76
|
+
}
|
|
77
|
+
return `${text}`;
|
|
78
|
+
}
|
|
@@ -9,11 +9,12 @@ import { ToolCategories } from './categories.js';
|
|
|
9
9
|
import { defineTool } from './ToolDefinition.js';
|
|
10
10
|
const throttlingOptions = [
|
|
11
11
|
'No emulation',
|
|
12
|
+
'Offline',
|
|
12
13
|
...Object.keys(PredefinedNetworkConditions),
|
|
13
14
|
];
|
|
14
15
|
export const emulateNetwork = defineTool({
|
|
15
16
|
name: 'emulate_network',
|
|
16
|
-
description: `Emulates network conditions such as throttling on the selected page.`,
|
|
17
|
+
description: `Emulates network conditions such as throttling or offline mode on the selected page.`,
|
|
17
18
|
annotations: {
|
|
18
19
|
category: ToolCategories.EMULATION,
|
|
19
20
|
readOnlyHint: false,
|
|
@@ -21,7 +22,7 @@ export const emulateNetwork = defineTool({
|
|
|
21
22
|
schema: {
|
|
22
23
|
throttlingOption: z
|
|
23
24
|
.enum(throttlingOptions)
|
|
24
|
-
.describe(`The network throttling option to emulate. Available throttling options are: ${throttlingOptions.join(', ')}. Set to "No emulation" to disable.`),
|
|
25
|
+
.describe(`The network throttling option to emulate. Available throttling options are: ${throttlingOptions.join(', ')}. Set to "No emulation" to disable. Set to "Offline" to simulate offline network conditions.`),
|
|
25
26
|
},
|
|
26
27
|
handler: async (request, _response, context) => {
|
|
27
28
|
const page = context.getSelectedPage();
|
|
@@ -31,6 +32,16 @@ export const emulateNetwork = defineTool({
|
|
|
31
32
|
context.setNetworkConditions(null);
|
|
32
33
|
return;
|
|
33
34
|
}
|
|
35
|
+
if (conditions === 'Offline') {
|
|
36
|
+
await page.emulateNetworkConditions({
|
|
37
|
+
offline: true,
|
|
38
|
+
download: 0,
|
|
39
|
+
upload: 0,
|
|
40
|
+
latency: 0,
|
|
41
|
+
});
|
|
42
|
+
context.setNetworkConditions('Offline');
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
34
45
|
if (conditions in PredefinedNetworkConditions) {
|
|
35
46
|
const networkCondition = PredefinedNetworkConditions[conditions];
|
|
36
47
|
await page.emulateNetworkConditions(networkCondition);
|
|
@@ -124,7 +124,6 @@ async function stopTracingAndAppendOutput(page, response, context) {
|
|
|
124
124
|
response.appendResponseLine('The performance trace has been stopped.');
|
|
125
125
|
if (traceResultIsSuccess(result)) {
|
|
126
126
|
context.storeTraceRecording(result);
|
|
127
|
-
response.appendResponseLine('Here is a high level summary of the trace and the Insights that were found:');
|
|
128
127
|
const traceSummaryText = getTraceSummary(result);
|
|
129
128
|
response.appendResponseLine(traceSummaryText);
|
|
130
129
|
}
|
|
@@ -53,15 +53,16 @@ const extraFormatDescriptions = `Information on performance traces may contain m
|
|
|
53
53
|
|
|
54
54
|
${PerformanceTraceFormatter.callFrameDataFormatDescription}
|
|
55
55
|
|
|
56
|
-
${PerformanceTraceFormatter.networkDataFormatDescription}
|
|
57
|
-
`;
|
|
56
|
+
${PerformanceTraceFormatter.networkDataFormatDescription}`;
|
|
58
57
|
export function getTraceSummary(result) {
|
|
59
58
|
const focus = AgentFocus.fromParsedTrace(result.parsedTrace);
|
|
60
59
|
const formatter = new PerformanceTraceFormatter(focus);
|
|
61
|
-
const
|
|
62
|
-
return
|
|
60
|
+
const summaryText = formatter.formatTraceSummary();
|
|
61
|
+
return `## Summary of Performance trace findings:
|
|
62
|
+
${summaryText}
|
|
63
63
|
|
|
64
|
-
|
|
64
|
+
## Details on call tree & network request formats:
|
|
65
|
+
${extraFormatDescriptions}`;
|
|
65
66
|
}
|
|
66
67
|
export function getInsightOutput(result, insightName) {
|
|
67
68
|
if (!result.insights) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "chrome-devtools-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "MCP server for Chrome DevTools",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": "./build/src/index.js",
|
|
@@ -40,8 +40,9 @@
|
|
|
40
40
|
"@modelcontextprotocol/sdk": "1.19.1",
|
|
41
41
|
"core-js": "3.45.1",
|
|
42
42
|
"debug": "4.4.3",
|
|
43
|
-
"puppeteer-core": "24.23.
|
|
44
|
-
"yargs": "18.0.0"
|
|
43
|
+
"puppeteer-core": "^24.23.1",
|
|
44
|
+
"yargs": "18.0.0",
|
|
45
|
+
"zod": "^3.25.76"
|
|
45
46
|
},
|
|
46
47
|
"devDependencies": {
|
|
47
48
|
"@eslint/js": "^9.35.0",
|
|
@@ -59,7 +60,7 @@
|
|
|
59
60
|
"eslint-plugin-import": "^2.32.0",
|
|
60
61
|
"globals": "^16.4.0",
|
|
61
62
|
"prettier": "^3.6.2",
|
|
62
|
-
"puppeteer": "24.23.
|
|
63
|
+
"puppeteer": "24.23.1",
|
|
63
64
|
"sinon": "^21.0.0",
|
|
64
65
|
"typescript": "^5.9.2",
|
|
65
66
|
"typescript-eslint": "^8.43.0"
|