@mcp-b/chrome-devtools-mcp 0.12.0-beta.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.
Files changed (41) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +554 -0
  3. package/build/src/DevToolsConnectionAdapter.js +69 -0
  4. package/build/src/DevtoolsUtils.js +206 -0
  5. package/build/src/McpContext.js +499 -0
  6. package/build/src/McpResponse.js +396 -0
  7. package/build/src/Mutex.js +37 -0
  8. package/build/src/PageCollector.js +283 -0
  9. package/build/src/WaitForHelper.js +139 -0
  10. package/build/src/browser.js +134 -0
  11. package/build/src/cli.js +213 -0
  12. package/build/src/formatters/consoleFormatter.js +121 -0
  13. package/build/src/formatters/networkFormatter.js +77 -0
  14. package/build/src/formatters/snapshotFormatter.js +73 -0
  15. package/build/src/index.js +21 -0
  16. package/build/src/issue-descriptions.js +39 -0
  17. package/build/src/logger.js +27 -0
  18. package/build/src/main.js +130 -0
  19. package/build/src/polyfill.js +7 -0
  20. package/build/src/third_party/index.js +16 -0
  21. package/build/src/tools/ToolDefinition.js +20 -0
  22. package/build/src/tools/categories.js +24 -0
  23. package/build/src/tools/console.js +85 -0
  24. package/build/src/tools/emulation.js +87 -0
  25. package/build/src/tools/input.js +268 -0
  26. package/build/src/tools/network.js +106 -0
  27. package/build/src/tools/pages.js +237 -0
  28. package/build/src/tools/performance.js +147 -0
  29. package/build/src/tools/screenshot.js +84 -0
  30. package/build/src/tools/script.js +71 -0
  31. package/build/src/tools/snapshot.js +52 -0
  32. package/build/src/tools/tools.js +31 -0
  33. package/build/src/tools/webmcp.js +233 -0
  34. package/build/src/trace-processing/parse.js +84 -0
  35. package/build/src/transports/WebMCPBridgeScript.js +196 -0
  36. package/build/src/transports/WebMCPClientTransport.js +276 -0
  37. package/build/src/transports/index.js +7 -0
  38. package/build/src/utils/keyboard.js +296 -0
  39. package/build/src/utils/pagination.js +49 -0
  40. package/build/src/utils/types.js +6 -0
  41. package/package.json +87 -0
@@ -0,0 +1,396 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2025 Google LLC
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+ import { AggregatedIssue } from '../node_modules/chrome-devtools-frontend/mcp/mcp.js';
7
+ import { mapIssueToMessageObject } from './DevtoolsUtils.js';
8
+ import { formatConsoleEventShort, formatConsoleEventVerbose, } from './formatters/consoleFormatter.js';
9
+ import { getFormattedHeaderValue, getFormattedResponseBody, getFormattedRequestBody, getShortDescriptionForRequest, getStatusFromRequest, } from './formatters/networkFormatter.js';
10
+ import { formatSnapshotNode } from './formatters/snapshotFormatter.js';
11
+ import { handleDialog } from './tools/pages.js';
12
+ import { paginate } from './utils/pagination.js';
13
+ export class McpResponse {
14
+ #includePages = false;
15
+ #snapshotParams;
16
+ #attachedNetworkRequestId;
17
+ #attachedConsoleMessageId;
18
+ #textResponseLines = [];
19
+ #images = [];
20
+ #networkRequestsOptions;
21
+ #consoleDataOptions;
22
+ #devToolsData;
23
+ attachDevToolsData(data) {
24
+ this.#devToolsData = data;
25
+ }
26
+ setIncludePages(value) {
27
+ this.#includePages = value;
28
+ }
29
+ includeSnapshot(params) {
30
+ this.#snapshotParams = params ?? {
31
+ verbose: false,
32
+ };
33
+ }
34
+ setIncludeNetworkRequests(value, options) {
35
+ if (!value) {
36
+ this.#networkRequestsOptions = undefined;
37
+ return;
38
+ }
39
+ this.#networkRequestsOptions = {
40
+ include: value,
41
+ pagination: options?.pageSize || options?.pageIdx
42
+ ? {
43
+ pageSize: options.pageSize,
44
+ pageIdx: options.pageIdx,
45
+ }
46
+ : undefined,
47
+ resourceTypes: options?.resourceTypes,
48
+ includePreservedRequests: options?.includePreservedRequests,
49
+ networkRequestIdInDevToolsUI: options?.networkRequestIdInDevToolsUI,
50
+ };
51
+ }
52
+ setIncludeConsoleData(value, options) {
53
+ if (!value) {
54
+ this.#consoleDataOptions = undefined;
55
+ return;
56
+ }
57
+ this.#consoleDataOptions = {
58
+ include: value,
59
+ pagination: options?.pageSize || options?.pageIdx
60
+ ? {
61
+ pageSize: options.pageSize,
62
+ pageIdx: options.pageIdx,
63
+ }
64
+ : undefined,
65
+ types: options?.types,
66
+ includePreservedMessages: options?.includePreservedMessages,
67
+ };
68
+ }
69
+ attachNetworkRequest(reqid) {
70
+ this.#attachedNetworkRequestId = reqid;
71
+ }
72
+ attachConsoleMessage(msgid) {
73
+ this.#attachedConsoleMessageId = msgid;
74
+ }
75
+ get includePages() {
76
+ return this.#includePages;
77
+ }
78
+ get includeNetworkRequests() {
79
+ return this.#networkRequestsOptions?.include ?? false;
80
+ }
81
+ get includeConsoleData() {
82
+ return this.#consoleDataOptions?.include ?? false;
83
+ }
84
+ get attachedNetworkRequestId() {
85
+ return this.#attachedNetworkRequestId;
86
+ }
87
+ get networkRequestsPageIdx() {
88
+ return this.#networkRequestsOptions?.pagination?.pageIdx;
89
+ }
90
+ get consoleMessagesPageIdx() {
91
+ return this.#consoleDataOptions?.pagination?.pageIdx;
92
+ }
93
+ get consoleMessagesTypes() {
94
+ return this.#consoleDataOptions?.types;
95
+ }
96
+ appendResponseLine(value) {
97
+ this.#textResponseLines.push(value);
98
+ }
99
+ attachImage(value) {
100
+ this.#images.push(value);
101
+ }
102
+ get responseLines() {
103
+ return this.#textResponseLines;
104
+ }
105
+ get images() {
106
+ return this.#images;
107
+ }
108
+ get snapshotParams() {
109
+ return this.#snapshotParams;
110
+ }
111
+ async handle(toolName, context) {
112
+ if (this.#includePages) {
113
+ await context.createPagesSnapshot();
114
+ }
115
+ let formattedSnapshot;
116
+ if (this.#snapshotParams) {
117
+ await context.createTextSnapshot(this.#snapshotParams.verbose, this.#devToolsData);
118
+ const snapshot = context.getTextSnapshot();
119
+ if (snapshot) {
120
+ if (this.#snapshotParams.filePath) {
121
+ await context.saveFile(new TextEncoder().encode(formatSnapshotNode(snapshot.root, snapshot)), this.#snapshotParams.filePath);
122
+ formattedSnapshot = `Saved snapshot to ${this.#snapshotParams.filePath}.`;
123
+ }
124
+ else {
125
+ formattedSnapshot = formatSnapshotNode(snapshot.root, snapshot);
126
+ }
127
+ }
128
+ }
129
+ const bodies = {};
130
+ if (this.#attachedNetworkRequestId) {
131
+ const request = context.getNetworkRequestById(this.#attachedNetworkRequestId);
132
+ bodies.requestBody = await getFormattedRequestBody(request);
133
+ const response = request.response();
134
+ if (response) {
135
+ bodies.responseBody = await getFormattedResponseBody(response);
136
+ }
137
+ }
138
+ let consoleData;
139
+ if (this.#attachedConsoleMessageId) {
140
+ const message = context.getConsoleMessageById(this.#attachedConsoleMessageId);
141
+ const consoleMessageStableId = this.#attachedConsoleMessageId;
142
+ if ('args' in message) {
143
+ const consoleMessage = message;
144
+ consoleData = {
145
+ consoleMessageStableId,
146
+ type: consoleMessage.type(),
147
+ message: consoleMessage.text(),
148
+ args: await Promise.all(consoleMessage.args().map(async (arg) => {
149
+ const stringArg = await arg.jsonValue().catch(() => {
150
+ // Ignore errors.
151
+ });
152
+ return typeof stringArg === 'object'
153
+ ? JSON.stringify(stringArg)
154
+ : String(stringArg);
155
+ })),
156
+ };
157
+ }
158
+ else if (message instanceof AggregatedIssue) {
159
+ const mappedIssueMessage = mapIssueToMessageObject(message);
160
+ if (!mappedIssueMessage)
161
+ throw new Error("Can't provide detals for the msgid " + consoleMessageStableId);
162
+ consoleData = {
163
+ consoleMessageStableId,
164
+ ...mappedIssueMessage,
165
+ };
166
+ }
167
+ else {
168
+ consoleData = {
169
+ consoleMessageStableId,
170
+ type: 'error',
171
+ message: message.message,
172
+ args: [],
173
+ };
174
+ }
175
+ }
176
+ let consoleListData;
177
+ if (this.#consoleDataOptions?.include) {
178
+ let messages = context.getConsoleData(this.#consoleDataOptions.includePreservedMessages);
179
+ if (this.#consoleDataOptions.types?.length) {
180
+ const normalizedTypes = new Set(this.#consoleDataOptions.types);
181
+ messages = messages.filter(message => {
182
+ if ('type' in message) {
183
+ return normalizedTypes.has(message.type());
184
+ }
185
+ if (message instanceof AggregatedIssue) {
186
+ return normalizedTypes.has('issue');
187
+ }
188
+ return normalizedTypes.has('error');
189
+ });
190
+ }
191
+ consoleListData = (await Promise.all(messages.map(async (item) => {
192
+ const consoleMessageStableId = context.getConsoleMessageStableId(item);
193
+ if ('args' in item) {
194
+ const consoleMessage = item;
195
+ return {
196
+ consoleMessageStableId,
197
+ type: consoleMessage.type(),
198
+ message: consoleMessage.text(),
199
+ args: await Promise.all(consoleMessage.args().map(async (arg) => {
200
+ const stringArg = await arg.jsonValue().catch(() => {
201
+ // Ignore errors.
202
+ });
203
+ return typeof stringArg === 'object'
204
+ ? JSON.stringify(stringArg)
205
+ : String(stringArg);
206
+ })),
207
+ };
208
+ }
209
+ if (item instanceof AggregatedIssue) {
210
+ const mappedIssueMessage = mapIssueToMessageObject(item);
211
+ if (!mappedIssueMessage)
212
+ return null;
213
+ return {
214
+ consoleMessageStableId,
215
+ ...mappedIssueMessage,
216
+ };
217
+ }
218
+ return {
219
+ consoleMessageStableId,
220
+ type: 'error',
221
+ message: item.message,
222
+ args: [],
223
+ };
224
+ }))).filter(item => item !== null);
225
+ }
226
+ return this.format(toolName, context, {
227
+ bodies,
228
+ consoleData,
229
+ consoleListData,
230
+ formattedSnapshot,
231
+ });
232
+ }
233
+ format(toolName, context, data) {
234
+ const response = [`# ${toolName} response`];
235
+ for (const line of this.#textResponseLines) {
236
+ response.push(line);
237
+ }
238
+ const networkConditions = context.getNetworkConditions();
239
+ if (networkConditions) {
240
+ response.push(`## Network emulation`);
241
+ response.push(`Emulating: ${networkConditions}`);
242
+ response.push(`Default navigation timeout set to ${context.getNavigationTimeout()} ms`);
243
+ }
244
+ const cpuThrottlingRate = context.getCpuThrottlingRate();
245
+ if (cpuThrottlingRate > 1) {
246
+ response.push(`## CPU emulation`);
247
+ response.push(`Emulating: ${cpuThrottlingRate}x slowdown`);
248
+ }
249
+ const dialog = context.getDialog();
250
+ if (dialog) {
251
+ const defaultValueIfNeeded = dialog.type() === 'prompt'
252
+ ? ` (default value: "${dialog.defaultValue()}")`
253
+ : '';
254
+ response.push(`# Open dialog
255
+ ${dialog.type()}: ${dialog.message()}${defaultValueIfNeeded}.
256
+ Call ${handleDialog.name} to handle it before continuing.`);
257
+ }
258
+ if (this.#includePages) {
259
+ const parts = [`## Pages`];
260
+ let idx = 0;
261
+ for (const page of context.getPages()) {
262
+ parts.push(`${idx}: ${page.url()}${context.isPageSelected(page) ? ' [selected]' : ''}`);
263
+ idx++;
264
+ }
265
+ response.push(...parts);
266
+ }
267
+ if (data.formattedSnapshot) {
268
+ response.push('## Latest page snapshot');
269
+ response.push(data.formattedSnapshot);
270
+ }
271
+ response.push(...this.#formatNetworkRequestData(context, data.bodies));
272
+ response.push(...this.#formatConsoleData(context, data.consoleData));
273
+ if (this.#networkRequestsOptions?.include) {
274
+ let requests = context.getNetworkRequests(this.#networkRequestsOptions?.includePreservedRequests);
275
+ // Apply resource type filtering if specified
276
+ if (this.#networkRequestsOptions.resourceTypes?.length) {
277
+ const normalizedTypes = new Set(this.#networkRequestsOptions.resourceTypes);
278
+ requests = requests.filter(request => {
279
+ const type = request.resourceType();
280
+ return normalizedTypes.has(type);
281
+ });
282
+ }
283
+ response.push('## Network requests');
284
+ if (requests.length) {
285
+ const data = this.#dataWithPagination(requests, this.#networkRequestsOptions.pagination);
286
+ response.push(...data.info);
287
+ for (const request of data.items) {
288
+ response.push(getShortDescriptionForRequest(request, context.getNetworkRequestStableId(request), context.getNetworkRequestStableId(request) ===
289
+ this.#networkRequestsOptions?.networkRequestIdInDevToolsUI));
290
+ }
291
+ }
292
+ else {
293
+ response.push('No requests found.');
294
+ }
295
+ }
296
+ if (this.#consoleDataOptions?.include) {
297
+ const messages = data.consoleListData ?? [];
298
+ response.push('## Console messages');
299
+ if (messages.length) {
300
+ const data = this.#dataWithPagination(messages, this.#consoleDataOptions.pagination);
301
+ response.push(...data.info);
302
+ response.push(...data.items.map(message => formatConsoleEventShort(message)));
303
+ }
304
+ else {
305
+ response.push('<no console messages found>');
306
+ }
307
+ }
308
+ const text = {
309
+ type: 'text',
310
+ text: response.join('\n'),
311
+ };
312
+ const images = this.#images.map(imageData => {
313
+ return {
314
+ type: 'image',
315
+ ...imageData,
316
+ };
317
+ });
318
+ return [text, ...images];
319
+ }
320
+ #dataWithPagination(data, pagination) {
321
+ const response = [];
322
+ const paginationResult = paginate(data, pagination);
323
+ if (paginationResult.invalidPage) {
324
+ response.push('Invalid page number provided. Showing first page.');
325
+ }
326
+ const { startIndex, endIndex, currentPage, totalPages } = paginationResult;
327
+ response.push(`Showing ${startIndex + 1}-${endIndex} of ${data.length} (Page ${currentPage + 1} of ${totalPages}).`);
328
+ if (pagination) {
329
+ if (paginationResult.hasNextPage) {
330
+ response.push(`Next page: ${currentPage + 1}`);
331
+ }
332
+ if (paginationResult.hasPreviousPage) {
333
+ response.push(`Previous page: ${currentPage - 1}`);
334
+ }
335
+ }
336
+ return {
337
+ info: response,
338
+ items: paginationResult.items,
339
+ };
340
+ }
341
+ #formatConsoleData(context, data) {
342
+ const response = [];
343
+ if (!data) {
344
+ return response;
345
+ }
346
+ response.push(formatConsoleEventVerbose(data, context));
347
+ return response;
348
+ }
349
+ #formatNetworkRequestData(context, data) {
350
+ const response = [];
351
+ const id = this.#attachedNetworkRequestId;
352
+ if (!id) {
353
+ return response;
354
+ }
355
+ const httpRequest = context.getNetworkRequestById(id);
356
+ response.push(`## Request ${httpRequest.url()}`);
357
+ response.push(`Status: ${getStatusFromRequest(httpRequest)}`);
358
+ response.push(`### Request Headers`);
359
+ for (const line of getFormattedHeaderValue(httpRequest.headers())) {
360
+ response.push(line);
361
+ }
362
+ if (data.requestBody) {
363
+ response.push(`### Request Body`);
364
+ response.push(data.requestBody);
365
+ }
366
+ const httpResponse = httpRequest.response();
367
+ if (httpResponse) {
368
+ response.push(`### Response Headers`);
369
+ for (const line of getFormattedHeaderValue(httpResponse.headers())) {
370
+ response.push(line);
371
+ }
372
+ }
373
+ if (data.responseBody) {
374
+ response.push(`### Response Body`);
375
+ response.push(data.responseBody);
376
+ }
377
+ const httpFailure = httpRequest.failure();
378
+ if (httpFailure) {
379
+ response.push(`### Request failed with`);
380
+ response.push(httpFailure.errorText);
381
+ }
382
+ const redirectChain = httpRequest.redirectChain();
383
+ if (redirectChain.length) {
384
+ response.push(`### Redirect chain`);
385
+ let indent = 0;
386
+ for (const request of redirectChain.reverse()) {
387
+ response.push(`${' '.repeat(indent)}${getShortDescriptionForRequest(request, context.getNetworkRequestStableId(request))}`);
388
+ indent++;
389
+ }
390
+ }
391
+ return response;
392
+ }
393
+ resetResponseLineForTesting() {
394
+ this.#textResponseLines = [];
395
+ }
396
+ }
@@ -0,0 +1,37 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2025 Google Inc.
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+ export class Mutex {
7
+ static Guard = class Guard {
8
+ #mutex;
9
+ constructor(mutex) {
10
+ this.#mutex = mutex;
11
+ }
12
+ dispose() {
13
+ return this.#mutex.release();
14
+ }
15
+ };
16
+ #locked = false;
17
+ #acquirers = [];
18
+ // This is FIFO.
19
+ async acquire() {
20
+ if (!this.#locked) {
21
+ this.#locked = true;
22
+ return new Mutex.Guard(this);
23
+ }
24
+ const { resolve, promise } = Promise.withResolvers();
25
+ this.#acquirers.push(resolve);
26
+ await promise;
27
+ return new Mutex.Guard(this);
28
+ }
29
+ release() {
30
+ const resolve = this.#acquirers.shift();
31
+ if (!resolve) {
32
+ this.#locked = false;
33
+ return;
34
+ }
35
+ resolve();
36
+ }
37
+ }