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 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 `CTR-S` to save the configuration:
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
- `--connect-url` to connect to a Chrome instance that you start manually outside
339
+ `--browser-url` to connect to a Chrome instance that you start manually outside
340
340
  of the MCP client sandbox.
@@ -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
- #attachedNetworkRequestUrl;
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.#attachedNetworkRequestUrl = url;
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.#attachedNetworkRequestUrl;
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.#attachedNetworkRequestUrl;
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`);
@@ -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 output = formatter.formatTraceSummary();
62
- return `${extraFormatDescriptions}
60
+ const summaryText = formatter.formatTraceSummary();
61
+ return `## Summary of Performance trace findings:
62
+ ${summaryText}
63
63
 
64
- ${output}`;
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.6.1",
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.0",
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.0",
63
+ "puppeteer": "24.23.1",
63
64
  "sinon": "^21.0.0",
64
65
  "typescript": "^5.9.2",
65
66
  "typescript-eslint": "^8.43.0"