chrome-devtools-mcp 0.17.1 → 0.17.3

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
@@ -124,6 +124,9 @@ claude mcp add chrome-devtools --scope user npx chrome-devtools-mcp@latest
124
124
 
125
125
  **Install as a Plugin (MCP + Skills)**
126
126
 
127
+ > [!NOTE]
128
+ > If you already had Chrome DevTools MCP installed previously for Claude Code, make sure to remove it first from your installation and configuration files.
129
+
127
130
  To install Chrome DevTools MCP with skills, add the marketplace registry in Claude Code:
128
131
 
129
132
  ```sh
@@ -147,7 +150,7 @@ Restart Claude Code to have the MCP server and skills load (check with `/skills`
147
150
 
148
151
  <details>
149
152
  <summary>Codex</summary>
150
- Follow the <a href="https://github.com/openai/codex/blob/main/docs/advanced.md#model-context-protocol-mcp">configure MCP guide</a>
153
+ Follow the <a href="https://developers.openai.com/codex/mcp/#configure-with-the-cli">configure MCP guide</a>
151
154
  using the standard config from above. You can also install the Chrome DevTools MCP server using the Codex CLI:
152
155
 
153
156
  ```bash
@@ -398,16 +398,8 @@ Call ${handleDialog.name} to handle it before continuing.`);
398
398
  response.push(extensionsMessage);
399
399
  }
400
400
  }
401
- if (this.#networkRequestsOptions?.include) {
402
- let requests = context.getNetworkRequests(this.#networkRequestsOptions?.includePreservedRequests);
403
- // Apply resource type filtering if specified
404
- if (this.#networkRequestsOptions.resourceTypes?.length) {
405
- const normalizedTypes = new Set(this.#networkRequestsOptions.resourceTypes);
406
- requests = requests.filter(request => {
407
- const type = request.resourceType();
408
- return normalizedTypes.has(type);
409
- });
410
- }
401
+ if (this.#networkRequestsOptions?.include && data.networkRequests) {
402
+ const requests = data.networkRequests;
411
403
  response.push('## Network requests');
412
404
  if (requests.length) {
413
405
  const paginationData = this.#dataWithPagination(requests, this.#networkRequestsOptions.pagination);
@@ -415,7 +407,7 @@ Call ${handleDialog.name} to handle it before continuing.`);
415
407
  response.push(...paginationData.info);
416
408
  if (data.networkRequests) {
417
409
  structuredContent.networkRequests = [];
418
- for (const formatter of data.networkRequests) {
410
+ for (const formatter of paginationData.items) {
419
411
  response.push(formatter.toString());
420
412
  structuredContent.networkRequests.push(formatter.toJSON());
421
413
  }
@@ -82,63 +82,10 @@ export class NetworkFormatter {
82
82
  }
83
83
  }
84
84
  toString() {
85
- // TODO truncate the URL
86
- return `reqid=${this.#options.requestId} ${this.#request.method()} ${this.#request.url()} ${this.#getStatusFromRequest(this.#request)}${this.#options.selectedInDevToolsUI ? ` [selected in the DevTools Network panel]` : ''}`;
85
+ return convertNetworkRequestConciseToString(this.toJSON());
87
86
  }
88
87
  toStringDetailed() {
89
- const response = [];
90
- response.push(`## Request ${this.#request.url()}`);
91
- response.push(`Status: ${this.#getStatusFromRequest(this.#request)}`);
92
- response.push(`### Request Headers`);
93
- for (const line of this.#getFormattedHeaderValue(this.#request.headers())) {
94
- response.push(line);
95
- }
96
- if (this.#requestBody) {
97
- response.push(`### Request Body`);
98
- response.push(this.#requestBody);
99
- }
100
- else if (this.#requestBodyFilePath) {
101
- response.push(`### Request Body`);
102
- response.push(`Saved to ${this.#requestBodyFilePath}.`);
103
- }
104
- const httpResponse = this.#request.response();
105
- if (httpResponse) {
106
- response.push(`### Response Headers`);
107
- for (const line of this.#getFormattedHeaderValue(httpResponse.headers())) {
108
- response.push(line);
109
- }
110
- }
111
- if (this.#responseBody) {
112
- response.push(`### Response Body`);
113
- response.push(this.#responseBody);
114
- }
115
- else if (this.#responseBodyFilePath) {
116
- response.push(`### Response Body`);
117
- response.push(`Saved to ${this.#responseBodyFilePath}.`);
118
- }
119
- const httpFailure = this.#request.failure();
120
- if (httpFailure) {
121
- response.push(`### Request failed with`);
122
- response.push(httpFailure.errorText);
123
- }
124
- const redirectChain = this.#request.redirectChain();
125
- if (redirectChain.length) {
126
- response.push(`### Redirect chain`);
127
- let indent = 0;
128
- for (const request of redirectChain.reverse()) {
129
- const id = this.#options.requestIdResolver
130
- ? this.#options.requestIdResolver(request)
131
- : undefined;
132
- // We create a temporary synchronous instance just for toString
133
- const formatter = new NetworkFormatter(request, {
134
- requestId: id,
135
- saveFile: this.#options.saveFile,
136
- });
137
- response.push(`${' '.repeat(indent)}${formatter.toString()}`);
138
- indent++;
139
- }
140
- }
141
- return response.join('\n');
88
+ return converNetworkRequestDetailedToStringDetailed(this.toJSONDetailed());
142
89
  }
143
90
  toJSON() {
144
91
  return {
@@ -180,27 +127,16 @@ export class NetworkFormatter {
180
127
  const failure = request.failure();
181
128
  let status;
182
129
  if (httpResponse) {
183
- const responseStatus = httpResponse.status();
184
- status =
185
- responseStatus >= 200 && responseStatus <= 299
186
- ? `[success - ${responseStatus}]`
187
- : `[failed - ${responseStatus}]`;
130
+ status = httpResponse.status().toString();
188
131
  }
189
132
  else if (failure) {
190
- status = `[failed - ${failure.errorText}]`;
133
+ status = failure.errorText;
191
134
  }
192
135
  else {
193
- status = '[pending]';
136
+ status = 'pending';
194
137
  }
195
138
  return status;
196
139
  }
197
- #getFormattedHeaderValue(headers) {
198
- const response = [];
199
- for (const [name, value] of Object.entries(headers)) {
200
- response.push(`- ${name}:${value}`);
201
- }
202
- return response;
203
- }
204
140
  async #getFormattedResponseBody(httpResponse, sizeLimit = BODY_CONTEXT_SIZE_LIMIT) {
205
141
  try {
206
142
  const responseBuffer = await httpResponse.buffer();
@@ -224,3 +160,59 @@ function getSizeLimitedString(text, sizeLimit) {
224
160
  }
225
161
  return text;
226
162
  }
163
+ function convertNetworkRequestConciseToString(data) {
164
+ // TODO truncate the URL
165
+ return `reqid=${data.requestId} ${data.method} ${data.url} [${data.status}]${data.selectedInDevToolsUI ? ` [selected in the DevTools Network panel]` : ''}`;
166
+ }
167
+ function formatHeadlers(headers) {
168
+ const response = [];
169
+ for (const [name, value] of Object.entries(headers)) {
170
+ response.push(`- ${name}:${value}`);
171
+ }
172
+ return response;
173
+ }
174
+ function converNetworkRequestDetailedToStringDetailed(data) {
175
+ const response = [];
176
+ response.push(`## Request ${data.url}`);
177
+ response.push(`Status: ${data.status}`);
178
+ response.push(`### Request Headers`);
179
+ for (const line of formatHeadlers(data.requestHeaders)) {
180
+ response.push(line);
181
+ }
182
+ if (data.requestBody) {
183
+ response.push(`### Request Body`);
184
+ response.push(data.requestBody);
185
+ }
186
+ else if (data.requestBodyFilePath) {
187
+ response.push(`### Request Body`);
188
+ response.push(`Saved to ${data.requestBodyFilePath}.`);
189
+ }
190
+ if (data.responseHeaders) {
191
+ response.push(`### Response Headers`);
192
+ for (const line of formatHeadlers(data.responseHeaders)) {
193
+ response.push(line);
194
+ }
195
+ }
196
+ if (data.responseBody) {
197
+ response.push(`### Response Body`);
198
+ response.push(data.responseBody);
199
+ }
200
+ else if (data.responseBodyFilePath) {
201
+ response.push(`### Response Body`);
202
+ response.push(`Saved to ${data.responseBodyFilePath}.`);
203
+ }
204
+ if (data.failure) {
205
+ response.push(`### Request failed with`);
206
+ response.push(data.failure);
207
+ }
208
+ const redirectChain = data.redirectChain;
209
+ if (redirectChain?.length) {
210
+ response.push(`### Redirect chain`);
211
+ let indent = 0;
212
+ for (const request of redirectChain.reverse()) {
213
+ response.push(`${' '.repeat(indent)}${convertNetworkRequestConciseToString(request)})}`);
214
+ indent++;
215
+ }
216
+ }
217
+ return response.join('\n');
218
+ }
package/build/src/main.js CHANGED
@@ -20,7 +20,7 @@ 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.17.1';
23
+ const VERSION = '0.17.3';
24
24
  // x-release-please-end
25
25
  export const args = parseArguments(VERSION);
26
26
  const logFile = args.logFile ? saveLogsToFile(args.logFile) : undefined;
@@ -636,14 +636,14 @@ SOFTWARE.
636
636
 
637
637
  Name: puppeteer-core
638
638
  URL: https://github.com/puppeteer/puppeteer/tree/main/packages/puppeteer-core
639
- Version: 24.37.3
639
+ Version: 24.37.4
640
640
  License: Apache-2.0
641
641
 
642
642
  -------------------- DEPENDENCY DIVIDER --------------------
643
643
 
644
644
  Name: @puppeteer/browsers
645
645
  URL: https://github.com/puppeteer/puppeteer/tree/main/packages/browsers
646
- Version: 2.12.1
646
+ Version: 2.13.0
647
647
  License: Apache-2.0
648
648
 
649
649
  -------------------- DEPENDENCY DIVIDER --------------------
@@ -4,5 +4,5 @@
4
4
  "core-js": "3.48.0",
5
5
  "debug": "4.4.3",
6
6
  "yargs": "18.0.0",
7
- "puppeteer-core": "24.37.3"
7
+ "puppeteer-core": "24.37.4"
8
8
  }
@@ -40599,7 +40599,7 @@ function mergeUint8Arrays(items) {
40599
40599
  * Copyright 2025 Google Inc.
40600
40600
  * SPDX-License-Identifier: Apache-2.0
40601
40601
  */
40602
- const packageVersion = '24.37.3';
40602
+ const packageVersion = '24.37.4';
40603
40603
 
40604
40604
  /**
40605
40605
  * @license
@@ -40865,15 +40865,15 @@ async function getReadableFromProtocolStream(client, handle) {
40865
40865
  },
40866
40866
  });
40867
40867
  }
40868
+ const VALID_DIALOG_TYPES = new Set([
40869
+ 'alert',
40870
+ 'confirm',
40871
+ 'prompt',
40872
+ 'beforeunload',
40873
+ ]);
40868
40874
  function validateDialogType(type) {
40869
40875
  let dialogType = null;
40870
- const validDialogTypes = new Set([
40871
- 'alert',
40872
- 'confirm',
40873
- 'prompt',
40874
- 'beforeunload',
40875
- ]);
40876
- if (validDialogTypes.has(type)) {
40876
+ if (VALID_DIALOG_TYPES.has(type)) {
40877
40877
  dialogType = type;
40878
40878
  }
40879
40879
  assert(dialogType, `Unknown javascript dialog type: ${type}`);
@@ -50610,7 +50610,7 @@ let FrameManager$1 = class FrameManager extends EventEmitter {
50610
50610
  frame.updateClient(target._session());
50611
50611
  }
50612
50612
  this.setupEventListeners(target._session());
50613
- void this.initialize(target._session(), frame);
50613
+ void this.initialize(target._session(), frame).catch(debugError);
50614
50614
  }
50615
50615
  _deviceRequestPromptManager(client) {
50616
50616
  let manager = this.#deviceRequestPromptManagerMap.get(client);
@@ -55541,9 +55541,9 @@ class Puppeteer {
55541
55541
  * SPDX-License-Identifier: Apache-2.0
55542
55542
  */
55543
55543
  const PUPPETEER_REVISIONS = Object.freeze({
55544
- chrome: '145.0.7632.67',
55545
- 'chrome-headless-shell': '145.0.7632.67',
55546
- firefox: 'stable_147.0.3',
55544
+ chrome: '145.0.7632.76',
55545
+ 'chrome-headless-shell': '145.0.7632.76',
55546
+ firefox: 'stable_147.0.4',
55547
55547
  });
55548
55548
 
55549
55549
  /**
@@ -156,11 +156,16 @@ async function selectOption(handle, aXNode, value) {
156
156
  throw new Error(`Could not find option with text "${value}"`);
157
157
  }
158
158
  }
159
+ function hasOptionChildren(aXNode) {
160
+ return aXNode.children.some(child => child.role === 'option');
161
+ }
159
162
  async function fillFormElement(uid, value, context) {
160
163
  const handle = await context.getElementByUid(uid);
161
164
  try {
162
165
  const aXNode = context.getAXNodeByUid(uid);
163
- if (aXNode && aXNode.role === 'combobox') {
166
+ // We assume that combobox needs to be handled as select if it has
167
+ // role='combobox' and option children.
168
+ if (aXNode && aXNode.role === 'combobox' && hasOptionChildren(aXNode)) {
164
169
  await selectOption(handle, aXNode, value);
165
170
  }
166
171
  else {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chrome-devtools-mcp",
3
- "version": "0.17.1",
3
+ "version": "0.17.3",
4
4
  "description": "MCP server for Chrome DevTools",
5
5
  "type": "module",
6
6
  "bin": "./build/src/index.js",
@@ -62,7 +62,7 @@
62
62
  "eslint-plugin-import": "^2.32.0",
63
63
  "globals": "^17.0.0",
64
64
  "prettier": "^3.6.2",
65
- "puppeteer": "24.37.3",
65
+ "puppeteer": "24.37.4",
66
66
  "rollup": "4.57.1",
67
67
  "rollup-plugin-cleanup": "^3.2.1",
68
68
  "rollup-plugin-license": "^3.6.0",