chrome-devtools-mcp 0.21.0 → 0.22.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 (49) hide show
  1. package/README.md +83 -21
  2. package/build/src/HeapSnapshotManager.js +94 -0
  3. package/build/src/McpContext.js +26 -56
  4. package/build/src/McpPage.js +16 -0
  5. package/build/src/McpResponse.js +145 -11
  6. package/build/src/PageCollector.js +10 -24
  7. package/build/src/WaitForHelper.js +31 -0
  8. package/build/src/bin/check-latest-version.js +25 -0
  9. package/build/src/bin/chrome-devtools-mcp-cli-options.js +24 -10
  10. package/build/src/bin/chrome-devtools-mcp-main.js +2 -0
  11. package/build/src/bin/chrome-devtools.js +3 -0
  12. package/build/src/bin/cliDefinitions.js +14 -8
  13. package/build/src/daemon/client.js +1 -1
  14. package/build/src/daemon/daemon.js +0 -4
  15. package/build/src/formatters/HeapSnapshotFormatter.js +38 -0
  16. package/build/src/formatters/NetworkFormatter.js +24 -7
  17. package/build/src/index.js +12 -1
  18. package/build/src/telemetry/ClearcutLogger.js +34 -12
  19. package/build/src/telemetry/flagUtils.js +46 -4
  20. package/build/src/telemetry/toolMetricsUtils.js +88 -0
  21. package/build/src/telemetry/watchdog/ClearcutSender.js +4 -3
  22. package/build/src/third_party/THIRD_PARTY_NOTICES +32 -32
  23. package/build/src/third_party/bundled-packages.json +5 -4
  24. package/build/src/third_party/devtools-formatter-worker.js +61 -64
  25. package/build/src/third_party/devtools-heap-snapshot-worker.js +9690 -0
  26. package/build/src/third_party/index.js +61443 -59378
  27. package/build/src/third_party/lighthouse-devtools-mcp-bundle.js +3501 -2658
  28. package/build/src/tools/categories.js +3 -0
  29. package/build/src/tools/console.js +42 -39
  30. package/build/src/tools/emulation.js +1 -1
  31. package/build/src/tools/extensions.js +5 -11
  32. package/build/src/tools/inPage.js +27 -6
  33. package/build/src/tools/input.js +15 -16
  34. package/build/src/tools/lighthouse.js +2 -2
  35. package/build/src/tools/memory.js +48 -3
  36. package/build/src/tools/network.js +2 -2
  37. package/build/src/tools/pages.js +8 -5
  38. package/build/src/tools/performance.js +1 -1
  39. package/build/src/tools/screencast.js +2 -1
  40. package/build/src/tools/screenshot.js +3 -3
  41. package/build/src/tools/script.js +22 -16
  42. package/build/src/tools/tools.js +2 -0
  43. package/build/src/tools/webmcp.js +63 -0
  44. package/build/src/utils/check-for-updates.js +73 -0
  45. package/build/src/utils/files.js +4 -0
  46. package/build/src/utils/id.js +15 -0
  47. package/build/src/version.js +1 -1
  48. package/package.json +12 -8
  49. package/build/src/utils/ExtensionRegistry.js +0 -35
@@ -13,6 +13,7 @@ export var ToolCategory;
13
13
  ToolCategory["DEBUGGING"] = "debugging";
14
14
  ToolCategory["EXTENSIONS"] = "extensions";
15
15
  ToolCategory["IN_PAGE"] = "in-page";
16
+ ToolCategory["MEMORY"] = "memory";
16
17
  })(ToolCategory || (ToolCategory = {}));
17
18
  export const labels = {
18
19
  [ToolCategory.INPUT]: 'Input automation',
@@ -23,4 +24,6 @@ export const labels = {
23
24
  [ToolCategory.DEBUGGING]: 'Debugging',
24
25
  [ToolCategory.EXTENSIONS]: 'Extensions',
25
26
  [ToolCategory.IN_PAGE]: 'In-page tools',
27
+ [ToolCategory.MEMORY]: 'Memory',
26
28
  };
29
+ export const OFF_BY_DEFAULT_CATEGORIES = [ToolCategory.EXTENSIONS];
@@ -28,48 +28,51 @@ const FILTERABLE_MESSAGE_TYPES = [
28
28
  'verbose',
29
29
  'issue',
30
30
  ];
31
- export const listConsoleMessages = definePageTool({
32
- name: 'list_console_messages',
33
- description: 'List all console messages for the currently selected page since the last navigation.',
34
- annotations: {
35
- category: ToolCategory.DEBUGGING,
36
- readOnlyHint: true,
37
- },
38
- schema: {
39
- pageSize: zod
40
- .number()
41
- .int()
42
- .positive()
43
- .optional()
44
- .describe('Maximum number of messages to return. When omitted, returns all requests.'),
45
- pageIdx: zod
46
- .number()
47
- .int()
48
- .min(0)
49
- .optional()
50
- .describe('Page number to return (0-based). When omitted, returns the first page.'),
51
- types: zod
52
- .array(zod.enum(FILTERABLE_MESSAGE_TYPES))
53
- .optional()
54
- .describe('Filter messages to only return messages of the specified resource types. When omitted or empty, returns all messages.'),
55
- includePreservedMessages: zod
56
- .boolean()
57
- .default(false)
58
- .optional()
59
- .describe('Set to true to return the preserved messages over the last 3 navigations.'),
60
- },
61
- handler: async (request, response) => {
62
- response.setIncludeConsoleData(true, {
63
- pageSize: request.params.pageSize,
64
- pageIdx: request.params.pageIdx,
65
- types: request.params.types,
66
- includePreservedMessages: request.params.includePreservedMessages,
67
- });
68
- },
31
+ const LIST_CONSOLE_MESSAGES_TOOL_NAME = 'list_console_messages';
32
+ export const listConsoleMessages = definePageTool(cliArgs => {
33
+ return {
34
+ name: LIST_CONSOLE_MESSAGES_TOOL_NAME,
35
+ description: `List all console messages for the currently selected page since the last navigation.${cliArgs?.categoryExtensions ? ' This includes console messages originating from extensions content scripts.' : ''}`,
36
+ annotations: {
37
+ category: ToolCategory.DEBUGGING,
38
+ readOnlyHint: true,
39
+ },
40
+ schema: {
41
+ pageSize: zod
42
+ .number()
43
+ .int()
44
+ .positive()
45
+ .optional()
46
+ .describe('Maximum number of messages to return. When omitted, returns all messages.'),
47
+ pageIdx: zod
48
+ .number()
49
+ .int()
50
+ .min(0)
51
+ .optional()
52
+ .describe('Page number to return (0-based). When omitted, returns the first page.'),
53
+ types: zod
54
+ .array(zod.enum(FILTERABLE_MESSAGE_TYPES))
55
+ .optional()
56
+ .describe('Filter messages to only return messages of the specified resource types. When omitted or empty, returns all messages.'),
57
+ includePreservedMessages: zod
58
+ .boolean()
59
+ .default(false)
60
+ .optional()
61
+ .describe('Set to true to return the preserved messages over the last 3 navigations.'),
62
+ },
63
+ handler: async (request, response) => {
64
+ response.setIncludeConsoleData(true, {
65
+ pageSize: request.params.pageSize,
66
+ pageIdx: request.params.pageIdx,
67
+ types: request.params.types,
68
+ includePreservedMessages: request.params.includePreservedMessages,
69
+ });
70
+ },
71
+ };
69
72
  });
70
73
  export const getConsoleMessage = definePageTool({
71
74
  name: 'get_console_message',
72
- description: `Gets a console message by its ID. You can get all messages by calling ${listConsoleMessages.name}.`,
75
+ description: `Gets a console message by its ID. You can get all messages by calling ${LIST_CONSOLE_MESSAGES_TOOL_NAME}.`,
73
76
  annotations: {
74
77
  category: ToolCategory.DEBUGGING,
75
78
  readOnlyHint: true,
@@ -33,7 +33,7 @@ export const emulate = definePageTool({
33
33
  .string()
34
34
  .optional()
35
35
  .transform(geolocationTransform)
36
- .describe('Geolocation (`<latitude>x<longitude>`) to emulate. Latitude between -90 and 90. Longitude between -180 and 180. Omit clear the geolocation override.'),
36
+ .describe('Geolocation (`<latitude>x<longitude>`) to emulate. Latitude between -90 and 90. Longitude between -180 and 180. Omit to clear the geolocation override.'),
37
37
  userAgent: zod
38
38
  .string()
39
39
  .optional()
@@ -6,14 +6,12 @@
6
6
  import { zod } from '../third_party/index.js';
7
7
  import { ToolCategory } from './categories.js';
8
8
  import { defineTool } from './ToolDefinition.js';
9
- const EXTENSIONS_CONDITION = 'experimentalExtensionSupport';
10
9
  export const installExtension = defineTool({
11
10
  name: 'install_extension',
12
11
  description: 'Installs a Chrome extension from the given path.',
13
12
  annotations: {
14
13
  category: ToolCategory.EXTENSIONS,
15
14
  readOnlyHint: false,
16
- conditions: [EXTENSIONS_CONDITION],
17
15
  },
18
16
  schema: {
19
17
  path: zod
@@ -32,7 +30,6 @@ export const uninstallExtension = defineTool({
32
30
  annotations: {
33
31
  category: ToolCategory.EXTENSIONS,
34
32
  readOnlyHint: false,
35
- conditions: [EXTENSIONS_CONDITION],
36
33
  },
37
34
  schema: {
38
35
  id: zod.string().describe('ID of the extension to uninstall.'),
@@ -45,11 +42,10 @@ export const uninstallExtension = defineTool({
45
42
  });
46
43
  export const listExtensions = defineTool({
47
44
  name: 'list_extensions',
48
- description: 'Lists all extensions via this server, including their name, ID, version, and enabled status.',
45
+ description: 'Lists all the Chrome extensions installed in the browser. This includes their name, ID, version, and enabled status.',
49
46
  annotations: {
50
47
  category: ToolCategory.EXTENSIONS,
51
48
  readOnlyHint: true,
52
- conditions: [EXTENSIONS_CONDITION],
53
49
  },
54
50
  schema: {},
55
51
  handler: async (_request, response, _context) => {
@@ -62,14 +58,13 @@ export const reloadExtension = defineTool({
62
58
  annotations: {
63
59
  category: ToolCategory.EXTENSIONS,
64
60
  readOnlyHint: false,
65
- conditions: [EXTENSIONS_CONDITION],
66
61
  },
67
62
  schema: {
68
63
  id: zod.string().describe('ID of the extension to reload.'),
69
64
  },
70
65
  handler: async (request, response, context) => {
71
66
  const { id } = request.params;
72
- const extension = context.getExtension(id);
67
+ const extension = await context.getExtension(id);
73
68
  if (!extension) {
74
69
  throw new Error(`Extension with ID ${id} not found.`);
75
70
  }
@@ -79,18 +74,17 @@ export const reloadExtension = defineTool({
79
74
  });
80
75
  export const triggerExtensionAction = defineTool({
81
76
  name: 'trigger_extension_action',
82
- description: 'Triggers an action in a Chrome extension.',
77
+ description: 'Triggers the default action of an extension by its ID.',
83
78
  annotations: {
84
79
  category: ToolCategory.EXTENSIONS,
85
80
  readOnlyHint: false,
86
- conditions: [EXTENSIONS_CONDITION],
87
81
  },
88
82
  schema: {
89
- id: zod.string().describe('ID of the extension.'),
83
+ id: zod.string().describe('ID of the extension to trigger the action for.'),
90
84
  },
91
85
  handler: async (request, response, context) => {
92
86
  const { id } = request.params;
93
87
  await context.triggerExtensionAction(id);
94
- response.appendResponseLine(`Extension action triggered. Id: ${id}`);
88
+ response.appendResponseLine(`Extension action triggered for ID ${id}`);
95
89
  },
96
90
  });
@@ -3,7 +3,7 @@
3
3
  * Copyright 2026 Google LLC
4
4
  * SPDX-License-Identifier: Apache-2.0
5
5
  */
6
- import { zod, ajv } from '../third_party/index.js';
6
+ import { zod, ajv, } from '../third_party/index.js';
7
7
  import { ToolCategory } from './categories.js';
8
8
  import { definePageTool } from './ToolDefinition.js';
9
9
  export const listInPageTools = definePageTool({
@@ -40,8 +40,7 @@ export const executeInPageTool = definePageTool({
40
40
  .optional()
41
41
  .describe('The JSON-stringified parameters to pass to the tool'),
42
42
  },
43
- handler: async (request, response, context) => {
44
- const page = context.getSelectedMcpPage();
43
+ handler: async (request, response) => {
45
44
  const toolName = request.params.toolName;
46
45
  let params = {};
47
46
  if (request.params.params) {
@@ -59,7 +58,20 @@ export const executeInPageTool = definePageTool({
59
58
  throw new Error(`Failed to parse params as JSON: ${errorMessage}`);
60
59
  }
61
60
  }
62
- const toolGroup = context.getInPageTools();
61
+ // Creates array of ElementHandles from the UIDs in the params.
62
+ // We do not replace the uids with the ElementsHandles yet, because
63
+ // the `evaluate` function only turns them into DOM elements if they
64
+ // are passed as non-nested arguments.
65
+ const handles = [];
66
+ for (const value of Object.values(params)) {
67
+ if (value instanceof Object &&
68
+ 'uid' in value &&
69
+ typeof value.uid === 'string' &&
70
+ Object.keys(value).length === 1) {
71
+ handles.push(await request.page.getElementByUid(value.uid));
72
+ }
73
+ }
74
+ const toolGroup = request.page.getInPageTools();
63
75
  const tool = toolGroup?.tools.find(t => t.name === toolName);
64
76
  if (!tool) {
65
77
  throw new Error(`Tool ${toolName} not found`);
@@ -70,7 +82,16 @@ export const executeInPageTool = definePageTool({
70
82
  if (!valid) {
71
83
  throw new Error(`Invalid parameters for tool ${toolName}: ${ajvInstance.errorsText(validate.errors)}`);
72
84
  }
73
- const result = await page.pptrPage.evaluate(async (name, args) => {
85
+ const result = await request.page.pptrPage.evaluate(async (name, args, ...elements) => {
86
+ // Replace the UIDs with DOM elements.
87
+ for (const [key, value] of Object.entries(args)) {
88
+ if (value instanceof Object &&
89
+ 'uid' in value &&
90
+ typeof value.uid === 'string' &&
91
+ Object.keys(value).length === 1) {
92
+ args[key] = elements.shift();
93
+ }
94
+ }
74
95
  if (!window.__dtmcp?.executeTool) {
75
96
  throw new Error('No tools found on the page');
76
97
  }
@@ -78,7 +99,7 @@ export const executeInPageTool = definePageTool({
78
99
  return {
79
100
  result: toolResult,
80
101
  };
81
- }, toolName, params);
102
+ }, toolName, params, ...handles);
82
103
  response.appendResponseLine(JSON.stringify(result, null, 2));
83
104
  },
84
105
  });
@@ -40,11 +40,11 @@ export const click = definePageTool({
40
40
  dblClick: dblClickSchema,
41
41
  includeSnapshot: includeSnapshotSchema,
42
42
  },
43
- handler: async (request, response, context) => {
43
+ handler: async (request, response) => {
44
44
  const uid = request.params.uid;
45
45
  const handle = await request.page.getElementByUid(uid);
46
46
  try {
47
- await context.waitForEventsAfterAction(async () => {
47
+ await request.page.waitForEventsAfterAction(async () => {
48
48
  await handle.asLocator().click({
49
49
  count: request.params.dblClick ? 2 : 1,
50
50
  });
@@ -78,9 +78,9 @@ export const clickAt = definePageTool({
78
78
  dblClick: dblClickSchema,
79
79
  includeSnapshot: includeSnapshotSchema,
80
80
  },
81
- handler: async (request, response, context) => {
81
+ handler: async (request, response) => {
82
82
  const page = request.page;
83
- await context.waitForEventsAfterAction(async () => {
83
+ await page.waitForEventsAfterAction(async () => {
84
84
  await page.pptrPage.mouse.click(request.params.x, request.params.y, {
85
85
  clickCount: request.params.dblClick ? 2 : 1,
86
86
  });
@@ -106,11 +106,11 @@ export const hover = definePageTool({
106
106
  .describe('The uid of an element on the page from the page content snapshot'),
107
107
  includeSnapshot: includeSnapshotSchema,
108
108
  },
109
- handler: async (request, response, context) => {
109
+ handler: async (request, response) => {
110
110
  const uid = request.params.uid;
111
111
  const handle = await request.page.getElementByUid(uid);
112
112
  try {
113
- await context.waitForEventsAfterAction(async () => {
113
+ await request.page.waitForEventsAfterAction(async () => {
114
114
  await handle.asLocator().hover();
115
115
  });
116
116
  response.appendResponseLine(`Successfully hovered over the element`);
@@ -186,10 +186,9 @@ async function fillFormElement(uid, value, context, page) {
186
186
  void handle.dispose();
187
187
  }
188
188
  }
189
- // here
190
189
  export const fill = definePageTool({
191
190
  name: 'fill',
192
- description: `Type text into a input, text area or select an option from a <select> element.`,
191
+ description: `Type text into an input, text area or select an option from a <select> element.`,
193
192
  annotations: {
194
193
  category: ToolCategory.INPUT,
195
194
  readOnlyHint: false,
@@ -203,7 +202,7 @@ export const fill = definePageTool({
203
202
  },
204
203
  handler: async (request, response, context) => {
205
204
  const page = request.page;
206
- await context.waitForEventsAfterAction(async () => {
205
+ await page.waitForEventsAfterAction(async () => {
207
206
  await fillFormElement(request.params.uid, request.params.value, context, page);
208
207
  });
209
208
  response.appendResponseLine(`Successfully filled out the element`);
@@ -223,9 +222,9 @@ export const typeText = definePageTool({
223
222
  text: zod.string().describe('The text to type'),
224
223
  submitKey: submitKeySchema,
225
224
  },
226
- handler: async (request, response, context) => {
225
+ handler: async (request, response) => {
227
226
  const page = request.page;
228
- await context.waitForEventsAfterAction(async () => {
227
+ await page.waitForEventsAfterAction(async () => {
229
228
  await page.pptrPage.keyboard.type(request.params.text);
230
229
  if (request.params.submitKey) {
231
230
  await page.pptrPage.keyboard.press(request.params.submitKey);
@@ -246,11 +245,11 @@ export const drag = definePageTool({
246
245
  to_uid: zod.string().describe('The uid of the element to drop into'),
247
246
  includeSnapshot: includeSnapshotSchema,
248
247
  },
249
- handler: async (request, response, context) => {
248
+ handler: async (request, response) => {
250
249
  const fromHandle = await request.page.getElementByUid(request.params.from_uid);
251
250
  const toHandle = await request.page.getElementByUid(request.params.to_uid);
252
251
  try {
253
- await context.waitForEventsAfterAction(async () => {
252
+ await request.page.waitForEventsAfterAction(async () => {
254
253
  await fromHandle.drag(toHandle);
255
254
  await new Promise(resolve => setTimeout(resolve, 50));
256
255
  await toHandle.drop(fromHandle);
@@ -287,7 +286,7 @@ export const fillForm = definePageTool({
287
286
  handler: async (request, response, context) => {
288
287
  const page = request.page;
289
288
  for (const element of request.params.elements) {
290
- await context.waitForEventsAfterAction(async () => {
289
+ await page.waitForEventsAfterAction(async () => {
291
290
  await fillFormElement(element.uid, element.value, context, page);
292
291
  });
293
292
  }
@@ -356,11 +355,11 @@ export const pressKey = definePageTool({
356
355
  .describe('A key or a combination (e.g., "Enter", "Control+A", "Control++", "Control+Shift+R"). Modifiers: Control, Shift, Alt, Meta'),
357
356
  includeSnapshot: includeSnapshotSchema,
358
357
  },
359
- handler: async (request, response, context) => {
358
+ handler: async (request, response) => {
360
359
  const page = request.page;
361
360
  const tokens = parseKey(request.params.key);
362
361
  const [key, ...modifiers] = tokens;
363
- await context.waitForEventsAfterAction(async () => {
362
+ await page.waitForEventsAfterAction(async () => {
364
363
  for (const modifier of modifiers) {
365
364
  await page.pptrPage.keyboard.down(modifier);
366
365
  }
@@ -86,8 +86,8 @@ export const lighthouseAudit = definePageTool({
86
86
  const report = generateReport(lhr, format);
87
87
  const data = encoder.encode(report);
88
88
  if (outputDirPath) {
89
- const reportPath = path.join(outputDirPath, `report.${format}`);
90
- const { filename } = await context.saveFile(data, reportPath);
89
+ const reportPath = path.join(outputDirPath, `report`);
90
+ const { filename } = await context.saveFile(data, reportPath, `.${format}`);
91
91
  reportPaths.push(filename);
92
92
  }
93
93
  else {
@@ -4,13 +4,14 @@
4
4
  * SPDX-License-Identifier: Apache-2.0
5
5
  */
6
6
  import { zod } from '../third_party/index.js';
7
+ import { ensureExtension } from '../utils/files.js';
7
8
  import { ToolCategory } from './categories.js';
8
- import { definePageTool } from './ToolDefinition.js';
9
+ import { definePageTool, defineTool } from './ToolDefinition.js';
9
10
  export const takeMemorySnapshot = definePageTool({
10
11
  name: 'take_memory_snapshot',
11
12
  description: `Capture a heap snapshot of the currently selected page. Use to analyze the memory distribution of JavaScript objects and debug memory leaks.`,
12
13
  annotations: {
13
- category: ToolCategory.PERFORMANCE,
14
+ category: ToolCategory.MEMORY,
14
15
  readOnlyHint: false,
15
16
  },
16
17
  schema: {
@@ -21,8 +22,52 @@ export const takeMemorySnapshot = definePageTool({
21
22
  handler: async (request, response, _context) => {
22
23
  const page = request.page;
23
24
  await page.pptrPage.captureHeapSnapshot({
24
- path: request.params.filePath,
25
+ path: ensureExtension(request.params.filePath, '.heapsnapshot'),
25
26
  });
26
27
  response.appendResponseLine(`Heap snapshot saved to ${request.params.filePath}`);
27
28
  },
28
29
  });
30
+ export const exploreMemorySnapshot = defineTool({
31
+ name: 'load_memory_snapshot',
32
+ description: 'Loads a memory heapsnapshot and returns snapshot summary stats.',
33
+ annotations: {
34
+ category: ToolCategory.MEMORY,
35
+ readOnlyHint: true,
36
+ conditions: ['experimentalMemory'],
37
+ },
38
+ schema: {
39
+ filePath: zod.string().describe('A path to a .heapsnapshot file to read.'),
40
+ },
41
+ handler: async (request, response, context) => {
42
+ const stats = await context.getHeapSnapshotStats(request.params.filePath);
43
+ const staticData = await context.getHeapSnapshotStaticData(request.params.filePath);
44
+ response.setHeapSnapshotStats(stats, staticData);
45
+ },
46
+ });
47
+ export const getMemorySnapshotDetails = defineTool({
48
+ name: 'get_memory_snapshot_details',
49
+ description: 'Loads a memory heapsnapshot and returns all available information including statistics, static data, and aggregated node information. Supports pagination for aggregates.',
50
+ annotations: {
51
+ category: ToolCategory.MEMORY,
52
+ readOnlyHint: true,
53
+ conditions: ['experimentalMemory'],
54
+ },
55
+ schema: {
56
+ filePath: zod.string().describe('A path to a .heapsnapshot file to read.'),
57
+ pageIdx: zod
58
+ .number()
59
+ .optional()
60
+ .describe('The page index for pagination of aggregates.'),
61
+ pageSize: zod
62
+ .number()
63
+ .optional()
64
+ .describe('The page size for pagination of aggregates.'),
65
+ },
66
+ handler: async (request, response, context) => {
67
+ const aggregates = await context.getHeapSnapshotAggregates(request.params.filePath);
68
+ response.setHeapSnapshotAggregates(aggregates, {
69
+ pageIdx: request.params.pageIdx,
70
+ pageSize: request.params.pageSize,
71
+ });
72
+ },
73
+ });
@@ -87,11 +87,11 @@ export const getNetworkRequest = definePageTool({
87
87
  requestFilePath: zod
88
88
  .string()
89
89
  .optional()
90
- .describe('The absolute or relative path to save the request body to. If omitted, the body is returned inline.'),
90
+ .describe('The absolute or relative path to a .network-request file to save the request body to. If omitted, the body is returned inline.'),
91
91
  responseFilePath: zod
92
92
  .string()
93
93
  .optional()
94
- .describe('The absolute or relative path to save the response body to. If omitted, the body is returned inline.'),
94
+ .describe('The absolute or relative path to a .network-response file to save the response body to. If omitted, the body is returned inline.'),
95
95
  },
96
96
  handler: async (request, response, context) => {
97
97
  if (request.params.reqid) {
@@ -10,7 +10,7 @@ import { CLOSE_PAGE_ERROR, definePageTool, defineTool, timeoutSchema, } from './
10
10
  export const listPages = defineTool(args => {
11
11
  return {
12
12
  name: 'list_pages',
13
- description: `Get a list of pages ${args?.categoryExtensions ? 'including extension service workers' : ''} open in the browser.`,
13
+ description: `Get a list of pages${args?.categoryExtensions ? ' including extension service workers' : ''} open in the browser.`,
14
14
  annotations: {
15
15
  category: ToolCategory.NAVIGATION,
16
16
  readOnlyHint: true,
@@ -19,6 +19,7 @@ export const listPages = defineTool(args => {
19
19
  handler: async (_request, response) => {
20
20
  response.setIncludePages(true);
21
21
  response.setListInPageTools();
22
+ response.setListWebMcpTools();
22
23
  },
23
24
  };
24
25
  });
@@ -43,6 +44,7 @@ export const selectPage = defineTool({
43
44
  context.selectPage(page);
44
45
  response.setIncludePages(true);
45
46
  response.setListInPageTools();
47
+ response.setListWebMcpTools();
46
48
  if (request.params.bringToFront) {
47
49
  await page.pptrPage.bringToFront();
48
50
  }
@@ -99,7 +101,7 @@ export const newPage = defineTool({
99
101
  },
100
102
  handler: async (request, response, context) => {
101
103
  const page = await context.newPage(request.params.background, request.params.isolatedContext);
102
- await context.waitForEventsAfterAction(async () => {
104
+ await page.waitForEventsAfterAction(async () => {
103
105
  await page.pptrPage.goto(request.params.url, {
104
106
  timeout: request.params.timeout,
105
107
  });
@@ -135,7 +137,7 @@ export const navigatePage = definePageTool({
135
137
  .describe('A JavaScript script to be executed on each new document before any other scripts for the next navigation.'),
136
138
  ...timeoutSchema,
137
139
  },
138
- handler: async (request, response, context) => {
140
+ handler: async (request, response) => {
139
141
  const page = request.page;
140
142
  const options = {
141
143
  timeout: request.params.timeout,
@@ -168,7 +170,7 @@ export const navigatePage = definePageTool({
168
170
  }
169
171
  page.pptrPage.on('dialog', dialogHandler);
170
172
  try {
171
- await context.waitForEventsAfterAction(async () => {
173
+ await page.waitForEventsAfterAction(async () => {
172
174
  switch (request.params.type) {
173
175
  case 'url':
174
176
  if (!request.params.url) {
@@ -179,7 +181,7 @@ export const navigatePage = definePageTool({
179
181
  response.appendResponseLine(`Successfully navigated to ${request.params.url}.`);
180
182
  }
181
183
  catch (error) {
182
- response.appendResponseLine(`Unable to navigate in the selected page: ${error.message}.`);
184
+ response.appendResponseLine(`Unable to navigate in the selected page: ${error.message}.`);
183
185
  }
184
186
  break;
185
187
  case 'back':
@@ -227,6 +229,7 @@ export const navigatePage = definePageTool({
227
229
  }
228
230
  response.setIncludePages(true);
229
231
  response.setListInPageTools();
232
+ response.setListWebMcpTools();
230
233
  },
231
234
  });
232
235
  export const resizePage = definePageTool({
@@ -142,7 +142,7 @@ async function stopTracingAndAppendOutput(page, response, context, filePath) {
142
142
  });
143
143
  });
144
144
  }
145
- const file = await context.saveFile(dataToWrite, filePath);
145
+ const file = await context.saveFile(dataToWrite, filePath, filePath.endsWith('.gz') ? '.json.gz' : '.json');
146
146
  response.appendResponseLine(`The raw trace data was saved to ${file.filename}.`);
147
147
  }
148
148
  const result = await parseRawTraceBuffer(traceEventsBuffer);
@@ -7,6 +7,7 @@ import fs from 'node:fs/promises';
7
7
  import os from 'node:os';
8
8
  import path from 'node:path';
9
9
  import { zod } from '../third_party/index.js';
10
+ import { ensureExtension } from '../utils/files.js';
10
11
  import { ToolCategory } from './categories.js';
11
12
  import { definePageTool } from './ToolDefinition.js';
12
13
  async function generateTempFilePath() {
@@ -33,7 +34,7 @@ export const startScreencast = definePageTool({
33
34
  return;
34
35
  }
35
36
  const filePath = request.params.path ?? (await generateTempFilePath());
36
- const resolvedPath = path.resolve(filePath);
37
+ const resolvedPath = ensureExtension(path.resolve(filePath), '.mp4');
37
38
  const page = request.page;
38
39
  let recorder;
39
40
  try {
@@ -28,7 +28,7 @@ export const screenshot = definePageTool({
28
28
  uid: zod
29
29
  .string()
30
30
  .optional()
31
- .describe('The uid of an element on the page from the page content snapshot. If omitted takes a pages screenshot.'),
31
+ .describe('The uid of an element on the page from the page content snapshot. If omitted, takes a page screenshot.'),
32
32
  fullPage: zod
33
33
  .boolean()
34
34
  .optional()
@@ -67,8 +67,8 @@ export const screenshot = definePageTool({
67
67
  response.appendResponseLine("Took a screenshot of the current page's viewport.");
68
68
  }
69
69
  if (request.params.filePath) {
70
- const file = await context.saveFile(screenshot, request.params.filePath);
71
- response.appendResponseLine(`Saved screenshot to ${file.filename}.`);
70
+ const result = await context.saveFile(screenshot, request.params.filePath, `.${format}`);
71
+ response.appendResponseLine(`Saved screenshot to ${result.filename}.`);
72
72
  }
73
73
  else if (screenshot.length >= 2_000_000) {
74
74
  const { filepath } = await context.saveTemporaryFile(screenshot, `screenshot.${request.params.format}`);