@mcp-b/chrome-devtools-mcp 2.3.0 → 2.3.1-beta.20260528050333

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 (67) hide show
  1. package/package.json +1 -1
  2. package/build/src/DevToolsConnectionAdapter.js +0 -70
  3. package/build/src/DevtoolsUtils.js +0 -290
  4. package/build/src/McpContext.js +0 -687
  5. package/build/src/McpPage.js +0 -95
  6. package/build/src/McpResponse.js +0 -588
  7. package/build/src/Mutex.js +0 -37
  8. package/build/src/PageCollector.js +0 -308
  9. package/build/src/SlimMcpResponse.js +0 -18
  10. package/build/src/WaitForHelper.js +0 -135
  11. package/build/src/bin/chrome-devtools-cli-options.js +0 -651
  12. package/build/src/bin/chrome-devtools-mcp-cli-options.js +0 -317
  13. package/build/src/bin/chrome-devtools-mcp-main.js +0 -35
  14. package/build/src/bin/chrome-devtools-mcp.js +0 -21
  15. package/build/src/bin/chrome-devtools.js +0 -185
  16. package/build/src/bin/cliDefinitions.js +0 -615
  17. package/build/src/browser.js +0 -198
  18. package/build/src/daemon/client.js +0 -152
  19. package/build/src/daemon/daemon.js +0 -206
  20. package/build/src/daemon/types.js +0 -6
  21. package/build/src/daemon/utils.js +0 -108
  22. package/build/src/formatters/ConsoleFormatter.js +0 -234
  23. package/build/src/formatters/IssueFormatter.js +0 -192
  24. package/build/src/formatters/NetworkFormatter.js +0 -215
  25. package/build/src/formatters/SnapshotFormatter.js +0 -131
  26. package/build/src/index.js +0 -202
  27. package/build/src/issue-descriptions.js +0 -39
  28. package/build/src/logger.js +0 -36
  29. package/build/src/polyfill.js +0 -7
  30. package/build/src/telemetry/ClearcutLogger.js +0 -102
  31. package/build/src/telemetry/WatchdogClient.js +0 -60
  32. package/build/src/telemetry/flagUtils.js +0 -45
  33. package/build/src/telemetry/metricUtils.js +0 -14
  34. package/build/src/telemetry/persistence.js +0 -53
  35. package/build/src/telemetry/types.js +0 -33
  36. package/build/src/telemetry/watchdog/ClearcutSender.js +0 -203
  37. package/build/src/telemetry/watchdog/main.js +0 -127
  38. package/build/src/third_party/devtools-formatter-worker.js +0 -7
  39. package/build/src/third_party/index.js +0 -26
  40. package/build/src/third_party/lighthouse-devtools-mcp-bundle.js +0 -54183
  41. package/build/src/tools/ToolDefinition.js +0 -72
  42. package/build/src/tools/categories.js +0 -24
  43. package/build/src/tools/console.js +0 -85
  44. package/build/src/tools/emulation.js +0 -55
  45. package/build/src/tools/extensions.js +0 -96
  46. package/build/src/tools/input.js +0 -368
  47. package/build/src/tools/lighthouse.js +0 -123
  48. package/build/src/tools/memory.js +0 -28
  49. package/build/src/tools/network.js +0 -120
  50. package/build/src/tools/pages.js +0 -319
  51. package/build/src/tools/performance.js +0 -190
  52. package/build/src/tools/screencast.js +0 -79
  53. package/build/src/tools/screenshot.js +0 -84
  54. package/build/src/tools/script.js +0 -119
  55. package/build/src/tools/slim/tools.js +0 -81
  56. package/build/src/tools/snapshot.js +0 -56
  57. package/build/src/tools/tools.js +0 -52
  58. package/build/src/tools/webmcp.js +0 -416
  59. package/build/src/trace-processing/parse.js +0 -84
  60. package/build/src/types.js +0 -6
  61. package/build/src/utils/ExtensionRegistry.js +0 -35
  62. package/build/src/utils/files.js +0 -19
  63. package/build/src/utils/keyboard.js +0 -296
  64. package/build/src/utils/pagination.js +0 -49
  65. package/build/src/utils/string.js +0 -36
  66. package/build/src/utils/types.js +0 -6
  67. package/build/src/version.js +0 -9
@@ -1,416 +0,0 @@
1
- /**
2
- * @license
3
- * Copyright 2026 Google LLC
4
- * SPDX-License-Identifier: Apache-2.0
5
- */
6
- import { zod } from '../third_party/index.js';
7
- import { ToolCategory } from './categories.js';
8
- import { defineTool } from './ToolDefinition.js';
9
- const DEFAULT_INPUT_SCHEMA = {
10
- type: 'object',
11
- properties: {},
12
- };
13
- function globToRegex(pattern) {
14
- const escaped = pattern
15
- .replace(/[.+^${}()|[\]\\]/g, '\\$&')
16
- .replace(/\*/g, '.*')
17
- .replace(/\?/g, '.');
18
- return new RegExp(`^${escaped}$`, 'i');
19
- }
20
- function isJsonObject(value) {
21
- return typeof value === 'object' && value !== null && !Array.isArray(value);
22
- }
23
- function jsonStringify(value) {
24
- return JSON.stringify(value, null, 2) ?? 'null';
25
- }
26
- function normalizeToolMetadata(pageId, tools) {
27
- return tools.map(tool => ({
28
- ...tool,
29
- pageId,
30
- }));
31
- }
32
- function summarizeDescription(description) {
33
- const firstLine = description.split('\n', 1)[0] ?? '';
34
- const firstSentence = firstLine.split('. ', 1)[0] ?? '';
35
- const summary = firstSentence || firstLine || description;
36
- return summary.length > 120 ? `${summary.slice(0, 117)}...` : summary;
37
- }
38
- function resolvePage(context, pageId) {
39
- if (pageId !== undefined) {
40
- return {
41
- id: pageId,
42
- pptrPage: context.getPageById(pageId).pptrPage,
43
- };
44
- }
45
- return context.getSelectedMcpPage();
46
- }
47
- async function evaluateListTools(page) {
48
- return page.evaluate(async (defaultInputSchema) => {
49
- function isObject(value) {
50
- return typeof value === 'object' && value !== null && !Array.isArray(value);
51
- }
52
- function normalizeCoreSchema(value) {
53
- return isObject(value) ? value : defaultInputSchema;
54
- }
55
- function normalizeTestingSchema(value) {
56
- if (typeof value !== 'string' || value.length === 0) {
57
- return defaultInputSchema;
58
- }
59
- try {
60
- const parsed = JSON.parse(value);
61
- return isObject(parsed) ? parsed : defaultInputSchema;
62
- }
63
- catch {
64
- return defaultInputSchema;
65
- }
66
- }
67
- const nav = navigator;
68
- if (nav.modelContext && typeof nav.modelContext.listTools === 'function') {
69
- const tools = await nav.modelContext.listTools();
70
- return {
71
- kind: 'available',
72
- api: 'modelContext',
73
- tools: Array.isArray(tools)
74
- ? tools.map(tool => {
75
- const raw = isObject(tool) ? tool : {};
76
- return {
77
- name: typeof raw.name === 'string' ? raw.name : '',
78
- description: typeof raw.description === 'string' ? raw.description : '',
79
- inputSchema: normalizeCoreSchema(raw.inputSchema),
80
- };
81
- })
82
- : [],
83
- };
84
- }
85
- if (nav.modelContextTesting &&
86
- typeof nav.modelContextTesting.listTools === 'function') {
87
- const tools = await nav.modelContextTesting.listTools();
88
- return {
89
- kind: 'available',
90
- api: 'modelContextTesting',
91
- tools: Array.isArray(tools)
92
- ? tools.map(tool => {
93
- const raw = isObject(tool) ? tool : {};
94
- return {
95
- name: typeof raw.name === 'string' ? raw.name : '',
96
- description: typeof raw.description === 'string' ? raw.description : '',
97
- inputSchema: normalizeTestingSchema(raw.inputSchema),
98
- };
99
- })
100
- : [],
101
- };
102
- }
103
- return {
104
- kind: 'unavailable',
105
- message: 'Page does not expose navigator.modelContext.listTools() or navigator.modelContextTesting.listTools().',
106
- };
107
- }, DEFAULT_INPUT_SCHEMA);
108
- }
109
- async function evaluateCallTool(page, name, args) {
110
- return page.evaluate(async ({ toolName, toolArgs }) => {
111
- const nav = navigator;
112
- try {
113
- if (nav.modelContext &&
114
- typeof nav.modelContext.callTool === 'function') {
115
- return {
116
- kind: 'success',
117
- api: 'modelContext',
118
- value: await nav.modelContext.callTool({
119
- name: toolName,
120
- arguments: toolArgs,
121
- }),
122
- };
123
- }
124
- if (nav.modelContextTesting &&
125
- typeof nav.modelContextTesting.executeTool === 'function') {
126
- const serialized = await nav.modelContextTesting.executeTool(toolName, JSON.stringify(toolArgs));
127
- if (serialized === null) {
128
- return {
129
- kind: 'error',
130
- message: 'Tool execution was interrupted by navigation or the page became unavailable.',
131
- };
132
- }
133
- const trimmed = serialized.trim();
134
- try {
135
- return {
136
- kind: 'success',
137
- api: 'modelContextTesting',
138
- value: JSON.parse(serialized),
139
- };
140
- }
141
- catch {
142
- // Native modelContextTesting can return a plain text string for
143
- // simple tool results. Preserve invalid JSON errors for payloads
144
- // that look like structured data but fail to parse.
145
- if (trimmed.length > 0 &&
146
- !['{', '[', '"'].includes(trimmed[0]) &&
147
- !/^[-\d]/.test(trimmed[0])) {
148
- return {
149
- kind: 'success',
150
- api: 'modelContextTesting',
151
- value: serialized,
152
- };
153
- }
154
- return {
155
- kind: 'error',
156
- message: `Testing tool returned invalid JSON: ${serialized.slice(0, 200)}`,
157
- };
158
- }
159
- }
160
- return {
161
- kind: 'error',
162
- message: 'Page does not expose navigator.modelContext.callTool() or navigator.modelContextTesting.executeTool().',
163
- };
164
- }
165
- catch (error) {
166
- return {
167
- kind: 'error',
168
- message: error instanceof Error ? error.message : String(error),
169
- };
170
- }
171
- }, {
172
- toolName: name,
173
- toolArgs: args,
174
- });
175
- }
176
- function normalizeResource(value) {
177
- if (!isJsonObject(value) || typeof value.uri !== 'string') {
178
- return null;
179
- }
180
- if (typeof value.text === 'string') {
181
- return {
182
- uri: value.uri,
183
- text: value.text,
184
- mimeType: typeof value.mimeType === 'string' ? value.mimeType : undefined,
185
- _meta: isJsonObject(value._meta) ? value._meta : undefined,
186
- };
187
- }
188
- if (typeof value.blob === 'string') {
189
- return {
190
- uri: value.uri,
191
- blob: value.blob,
192
- mimeType: typeof value.mimeType === 'string' ? value.mimeType : undefined,
193
- _meta: isJsonObject(value._meta) ? value._meta : undefined,
194
- };
195
- }
196
- return null;
197
- }
198
- function normalizeContentBlock(value) {
199
- if (isJsonObject(value)) {
200
- if (value.type === 'text' && typeof value.text === 'string') {
201
- return {
202
- type: 'text',
203
- text: value.text,
204
- annotations: isJsonObject(value.annotations)
205
- ? value.annotations
206
- : undefined,
207
- _meta: isJsonObject(value._meta) ? value._meta : undefined,
208
- };
209
- }
210
- if (value.type === 'image' &&
211
- typeof value.data === 'string' &&
212
- typeof value.mimeType === 'string') {
213
- return {
214
- type: 'image',
215
- data: value.data,
216
- mimeType: value.mimeType,
217
- annotations: isJsonObject(value.annotations)
218
- ? value.annotations
219
- : undefined,
220
- _meta: isJsonObject(value._meta) ? value._meta : undefined,
221
- };
222
- }
223
- if (value.type === 'resource') {
224
- const resource = normalizeResource(value.resource);
225
- if (resource) {
226
- return {
227
- type: 'resource',
228
- resource,
229
- annotations: isJsonObject(value.annotations)
230
- ? value.annotations
231
- : undefined,
232
- _meta: isJsonObject(value._meta) ? value._meta : undefined,
233
- };
234
- }
235
- }
236
- }
237
- return {
238
- type: 'text',
239
- text: typeof value === 'string' ? value : jsonStringify(value),
240
- };
241
- }
242
- function normalizeToolResult(value) {
243
- if (!isJsonObject(value)) {
244
- return {
245
- content: [normalizeContentBlock(value)],
246
- isError: false,
247
- };
248
- }
249
- const content = Array.isArray(value.content)
250
- ? value.content.map(block => normalizeContentBlock(block))
251
- : [];
252
- if (!content.length) {
253
- if (value.structuredContent !== undefined) {
254
- content.push({
255
- type: 'text',
256
- text: jsonStringify(value.structuredContent),
257
- });
258
- }
259
- else {
260
- content.push({
261
- type: 'text',
262
- text: '',
263
- });
264
- }
265
- }
266
- return {
267
- content,
268
- isError: value.isError === true,
269
- };
270
- }
271
- function formatListedTools(tools, summary) {
272
- if (summary) {
273
- return tools.map(tool => ({
274
- name: tool.name,
275
- description: summarizeDescription(tool.description),
276
- pageId: tool.pageId,
277
- }));
278
- }
279
- return tools;
280
- }
281
- export const listWebMCPTools = defineTool({
282
- name: 'list_webmcp_tools',
283
- description: 'List WebMCP tools exposed by the selected page, a specific pageId, or across all open pages. Supports compact summaries and glob filtering.',
284
- annotations: {
285
- category: ToolCategory.DEBUGGING,
286
- readOnlyHint: true,
287
- },
288
- schema: {
289
- pageId: zod
290
- .number()
291
- .optional()
292
- .describe('Targets a specific page by ID. Defaults to the selected page.'),
293
- allPages: zod
294
- .boolean()
295
- .optional()
296
- .describe('If true, search across all open pages instead of only the selected page.'),
297
- pattern: zod
298
- .string()
299
- .optional()
300
- .describe('Optional glob pattern to filter tool names, for example "list_*" or "*_entity".'),
301
- summary: zod
302
- .boolean()
303
- .optional()
304
- .describe('If true, omit input schemas and return compact tool summaries.'),
305
- },
306
- handler: async (request, response, context) => {
307
- const { allPages = false, pageId, pattern, summary = false } = request.params;
308
- if (allPages && pageId !== undefined) {
309
- throw new Error('Specify either pageId or allPages, not both.');
310
- }
311
- const regex = pattern ? globToRegex(pattern) : null;
312
- if (!allPages) {
313
- const page = resolvePage(context, pageId);
314
- const result = await evaluateListTools(page.pptrPage);
315
- if (result.kind === 'unavailable') {
316
- response.appendResponseLine(jsonStringify({
317
- pageId: page.id,
318
- count: 0,
319
- tools: [],
320
- message: result.message,
321
- }));
322
- return;
323
- }
324
- let tools = normalizeToolMetadata(page.id, result.tools);
325
- if (regex) {
326
- tools = tools.filter(tool => regex.test(tool.name));
327
- }
328
- if (!tools.length) {
329
- response.appendResponseLine(jsonStringify({
330
- pageId: page.id,
331
- api: result.api,
332
- count: 0,
333
- tools: [],
334
- message: pattern
335
- ? `No WebMCP tools matched pattern "${pattern}" on page ${page.id}.`
336
- : 'No WebMCP tools were exposed on the selected page.',
337
- }));
338
- return;
339
- }
340
- response.appendResponseLine(jsonStringify({
341
- pageId: page.id,
342
- api: result.api,
343
- count: tools.length,
344
- tools: formatListedTools(tools, summary),
345
- }));
346
- return;
347
- }
348
- const selectedPageId = context.getSelectedMcpPage().id;
349
- const pages = context
350
- .getPages()
351
- .map(pptrPage => {
352
- const id = context.getPageId(pptrPage);
353
- return id === undefined ? null : { id, pptrPage };
354
- })
355
- .filter(page => page !== null);
356
- const pageResults = await Promise.all(pages.map(async (page) => ({
357
- page,
358
- result: await evaluateListTools(page.pptrPage),
359
- })));
360
- const tools = pageResults.flatMap(({ page, result }) => {
361
- if (result.kind !== 'available') {
362
- return [];
363
- }
364
- return normalizeToolMetadata(page.id, result.tools);
365
- });
366
- const filteredTools = regex
367
- ? tools.filter(tool => regex.test(tool.name))
368
- : tools;
369
- const unavailablePageIds = pageResults
370
- .filter(entry => entry.result.kind === 'unavailable')
371
- .map(entry => entry.page.id);
372
- response.appendResponseLine(jsonStringify({
373
- selectedPageId,
374
- pagesScanned: pages.length,
375
- count: filteredTools.length,
376
- tools: formatListedTools(filteredTools, summary),
377
- message: filteredTools.length > 0
378
- ? undefined
379
- : pattern
380
- ? `No WebMCP tools matched pattern "${pattern}" across ${pages.length} page(s).`
381
- : 'No WebMCP tools were exposed across the scanned pages.',
382
- unavailablePageIds: unavailablePageIds.length > 0 ? unavailablePageIds : undefined,
383
- }));
384
- },
385
- });
386
- export const callWebMCPTool = defineTool({
387
- name: 'call_webmcp_tool',
388
- description: 'Call a WebMCP tool exposed by the selected page or a specific pageId.',
389
- annotations: {
390
- category: ToolCategory.DEBUGGING,
391
- readOnlyHint: false,
392
- },
393
- schema: {
394
- name: zod.string().describe('The WebMCP tool name to invoke.'),
395
- arguments: zod
396
- .record(zod.unknown())
397
- .optional()
398
- .describe('Tool arguments. Defaults to an empty object.'),
399
- pageId: zod
400
- .number()
401
- .optional()
402
- .describe('Targets a specific page by ID. Defaults to the selected page.'),
403
- },
404
- handler: async (request, response, context) => {
405
- const page = resolvePage(context, request.params.pageId);
406
- const result = await evaluateCallTool(page.pptrPage, request.params.name, request.params.arguments ?? {});
407
- if (result.kind === 'error') {
408
- throw new Error(result.message);
409
- }
410
- const normalized = normalizeToolResult(result.value);
411
- response.setToolResultError(normalized.isError);
412
- for (const block of normalized.content) {
413
- response.appendMcpContent(block);
414
- }
415
- },
416
- });
@@ -1,84 +0,0 @@
1
- /**
2
- * @license
3
- * Copyright 2025 Google LLC
4
- * SPDX-License-Identifier: Apache-2.0
5
- */
6
- import { logger } from '../logger.js';
7
- import { DevTools } from '../third_party/index.js';
8
- const engine = DevTools.TraceEngine.TraceModel.Model.createWithAllHandlers();
9
- export function traceResultIsSuccess(x) {
10
- return 'parsedTrace' in x;
11
- }
12
- export async function parseRawTraceBuffer(buffer) {
13
- engine.resetProcessor();
14
- if (!buffer) {
15
- return {
16
- error: 'No buffer was provided.',
17
- };
18
- }
19
- const asString = new TextDecoder().decode(buffer);
20
- if (!asString) {
21
- return {
22
- error: 'Decoding the trace buffer returned an empty string.',
23
- };
24
- }
25
- try {
26
- const data = JSON.parse(asString);
27
- const events = Array.isArray(data) ? data : data.traceEvents;
28
- await engine.parse(events);
29
- const parsedTrace = engine.parsedTrace();
30
- if (!parsedTrace) {
31
- return {
32
- error: 'No parsed trace was returned from the trace engine.',
33
- };
34
- }
35
- const insights = parsedTrace?.insights ?? null;
36
- return {
37
- parsedTrace,
38
- insights,
39
- };
40
- }
41
- catch (e) {
42
- const errorText = e instanceof Error ? e.message : JSON.stringify(e);
43
- logger(`Unexpected error parsing trace: ${errorText}`);
44
- return {
45
- error: errorText,
46
- };
47
- }
48
- }
49
- const extraFormatDescriptions = `Information on performance traces may contain main thread activity represented as call frames and network requests.
50
-
51
- ${DevTools.PerformanceTraceFormatter.callFrameDataFormatDescription}
52
-
53
- ${DevTools.PerformanceTraceFormatter.networkDataFormatDescription}`;
54
- export function getTraceSummary(result) {
55
- const focus = DevTools.AgentFocus.fromParsedTrace(result.parsedTrace);
56
- const formatter = new DevTools.PerformanceTraceFormatter(focus);
57
- const summaryText = formatter.formatTraceSummary();
58
- return `## Summary of Performance trace findings:
59
- ${summaryText}
60
-
61
- ## Details on call tree & network request formats:
62
- ${extraFormatDescriptions}`;
63
- }
64
- export function getInsightOutput(result, insightSetId, insightName) {
65
- if (!result.insights) {
66
- return {
67
- error: 'No Performance insights are available for this trace.',
68
- };
69
- }
70
- const insightSet = result.insights.get(insightSetId);
71
- if (!insightSet) {
72
- return {
73
- error: 'No Performance Insights for the given insight set id. Only use ids given in the "Available insight sets" list.',
74
- };
75
- }
76
- const matchingInsight = insightName in insightSet.model ? insightSet.model[insightName] : null;
77
- if (!matchingInsight) {
78
- return {
79
- error: `No Insight with the name ${insightName} found. Double check the name you provided is accurate and try again.`,
80
- };
81
- }
82
- const formatter = new DevTools.PerformanceInsightFormatter(DevTools.AgentFocus.fromParsedTrace(result.parsedTrace), matchingInsight);
83
- return { output: formatter.formatInsight() };
84
- }
@@ -1,6 +0,0 @@
1
- /**
2
- * @license
3
- * Copyright 2025 Google LLC
4
- * SPDX-License-Identifier: Apache-2.0
5
- */
6
- export {};
@@ -1,35 +0,0 @@
1
- /**
2
- * @license
3
- * Copyright 2026 Google LLC
4
- * SPDX-License-Identifier: Apache-2.0
5
- */
6
- import fs from 'node:fs/promises';
7
- import path from 'node:path';
8
- export class ExtensionRegistry {
9
- #extensions = new Map();
10
- async registerExtension(id, extensionPath) {
11
- const manifestPath = path.join(extensionPath, 'manifest.json');
12
- const manifestContent = await fs.readFile(manifestPath, 'utf-8');
13
- const manifest = JSON.parse(manifestContent);
14
- const name = manifest.name ?? 'Unknown';
15
- const version = manifest.version ?? 'Unknown';
16
- const extension = {
17
- id,
18
- name,
19
- version,
20
- isEnabled: true,
21
- path: extensionPath,
22
- };
23
- this.#extensions.set(extension.id, extension);
24
- return extension;
25
- }
26
- remove(id) {
27
- this.#extensions.delete(id);
28
- }
29
- list() {
30
- return Array.from(this.#extensions.values());
31
- }
32
- getById(id) {
33
- return this.#extensions.get(id);
34
- }
35
- }
@@ -1,19 +0,0 @@
1
- /**
2
- * @license
3
- * Copyright 2025 Google LLC
4
- * SPDX-License-Identifier: Apache-2.0
5
- */
6
- import fs from 'node:fs/promises';
7
- import os from 'node:os';
8
- import path from 'node:path';
9
- export async function saveTemporaryFile(data, filename) {
10
- try {
11
- const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'chrome-devtools-mcp-'));
12
- const filepath = path.join(dir, filename);
13
- await fs.writeFile(filepath, data);
14
- return { filepath };
15
- }
16
- catch (err) {
17
- throw new Error('Could not save a file', { cause: err });
18
- }
19
- }