chrome-devtools-mcp 0.24.0 → 0.26.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 (33) hide show
  1. package/README.md +23 -6
  2. package/build/src/McpPage.js +4 -4
  3. package/build/src/McpResponse.js +18 -16
  4. package/build/src/TextSnapshot.js +2 -2
  5. package/build/src/ToolHandler.js +188 -0
  6. package/build/src/bin/chrome-devtools-cli-options.js +28 -5
  7. package/build/src/bin/chrome-devtools-mcp-cli-options.js +8 -5
  8. package/build/src/bin/chrome-devtools-mcp-main.js +4 -8
  9. package/build/src/bin/chrome-devtools.js +0 -2
  10. package/build/src/index.js +9 -164
  11. package/build/src/telemetry/ClearcutLogger.js +27 -0
  12. package/build/src/telemetry/errors.js +14 -0
  13. package/build/src/telemetry/types.js +0 -8
  14. package/build/src/third_party/THIRD_PARTY_NOTICES +12 -14
  15. package/build/src/third_party/bundled-packages.json +3 -3
  16. package/build/src/third_party/devtools-formatter-worker.js +49 -33
  17. package/build/src/third_party/devtools-heap-snapshot-worker.js +60 -44
  18. package/build/src/third_party/index.js +2138 -1352
  19. package/build/src/third_party/issue-descriptions/genericFormModelContextMissingToolDescription.md +5 -0
  20. package/build/src/third_party/issue-descriptions/genericFormModelContextMissingToolName.md +5 -0
  21. package/build/src/third_party/issue-descriptions/genericFormModelContextParameterMissingName.md +5 -0
  22. package/build/src/third_party/issue-descriptions/genericFormModelContextParameterMissingTitleAndDescription.md +5 -0
  23. package/build/src/third_party/issue-descriptions/genericFormModelContextRequiredParameterMissingName.md +5 -0
  24. package/build/src/third_party/lighthouse-devtools-mcp-bundle.js +4236 -4219
  25. package/build/src/tools/categories.js +6 -3
  26. package/build/src/tools/input.js +72 -7
  27. package/build/src/tools/lighthouse.js +7 -7
  28. package/build/src/tools/pages.js +5 -5
  29. package/build/src/tools/{inPage.js → thirdPartyDeveloper.js} +15 -15
  30. package/build/src/tools/tools.js +2 -2
  31. package/build/src/tools/webmcp.js +2 -4
  32. package/build/src/version.js +1 -1
  33. package/package.json +7 -8
@@ -12,8 +12,9 @@ export var ToolCategory;
12
12
  ToolCategory["NETWORK"] = "network";
13
13
  ToolCategory["DEBUGGING"] = "debugging";
14
14
  ToolCategory["EXTENSIONS"] = "extensions";
15
- ToolCategory["IN_PAGE"] = "experimentalInPage";
15
+ ToolCategory["THIRD_PARTY"] = "experimentalThirdParty";
16
16
  ToolCategory["MEMORY"] = "memory";
17
+ ToolCategory["WEBMCP"] = "experimentalWebmcp";
17
18
  })(ToolCategory || (ToolCategory = {}));
18
19
  export const labels = {
19
20
  [ToolCategory.INPUT]: 'Input automation',
@@ -23,11 +24,13 @@ export const labels = {
23
24
  [ToolCategory.NETWORK]: 'Network',
24
25
  [ToolCategory.DEBUGGING]: 'Debugging',
25
26
  [ToolCategory.EXTENSIONS]: 'Extensions',
26
- [ToolCategory.IN_PAGE]: 'In-page tools',
27
+ [ToolCategory.THIRD_PARTY]: 'Third-party',
27
28
  [ToolCategory.MEMORY]: 'Memory',
29
+ [ToolCategory.WEBMCP]: 'WebMCP',
28
30
  };
29
31
  export const OFF_BY_DEFAULT_CATEGORIES = [
30
32
  ToolCategory.EXTENSIONS,
31
- ToolCategory.IN_PAGE,
33
+ ToolCategory.THIRD_PARTY,
34
+ ToolCategory.WEBMCP,
32
35
  ];
33
36
  //# sourceMappingURL=categories.js.map
@@ -26,6 +26,44 @@ function handleActionError(error, uid) {
26
26
  cause: error,
27
27
  });
28
28
  }
29
+ async function selectNativeSelectOption(handle) {
30
+ const selectHandle = await handle.evaluateHandle(node => {
31
+ if (!(node instanceof HTMLOptionElement)) {
32
+ return null;
33
+ }
34
+ const select = node.closest('select');
35
+ if (!select || select.multiple || select.disabled || node.disabled) {
36
+ return null;
37
+ }
38
+ const parentElement = node.parentElement;
39
+ if (parentElement instanceof HTMLOptGroupElement &&
40
+ parentElement.disabled) {
41
+ return null;
42
+ }
43
+ return select;
44
+ });
45
+ try {
46
+ const select = selectHandle.asElement();
47
+ if (!select) {
48
+ return false;
49
+ }
50
+ const valueHandle = await handle.getProperty('value');
51
+ try {
52
+ const value = await valueHandle.jsonValue();
53
+ if (typeof value !== 'string') {
54
+ return false;
55
+ }
56
+ await select.asLocator().fill(value);
57
+ }
58
+ finally {
59
+ void valueHandle.dispose();
60
+ }
61
+ return true;
62
+ }
63
+ finally {
64
+ void selectHandle.dispose();
65
+ }
66
+ }
29
67
  export const click = definePageTool({
30
68
  name: 'click',
31
69
  description: `Clicks on the provided element`,
@@ -44,8 +82,14 @@ export const click = definePageTool({
44
82
  handler: async (request, response) => {
45
83
  const uid = request.params.uid;
46
84
  const handle = await request.page.getElementByUid(uid);
85
+ const aXNode = request.page.getAXNodeByUid(uid);
86
+ const shouldSelectNativeOption = !request.params.dblClick && aXNode?.role === 'option';
47
87
  try {
48
88
  await request.page.waitForEventsAfterAction(async () => {
89
+ if (shouldSelectNativeOption &&
90
+ (await selectNativeSelectOption(handle))) {
91
+ return;
92
+ }
49
93
  await handle.asLocator().click({
50
94
  count: request.params.dblClick ? 2 : 1,
51
95
  });
@@ -176,10 +220,27 @@ async function fillFormElement(uid, value, context, page) {
176
220
  await selectOption(handle, aXNode, value);
177
221
  }
178
222
  else {
179
- // Increase timeout for longer input values.
180
- const timeoutPerChar = 10; // ms
181
- const fillTimeout = page.pptrPage.getDefaultTimeout() + value.length * timeoutPerChar;
182
- await handle.asLocator().setTimeout(fillTimeout).fill(value);
223
+ const isToggle = await handle.evaluate(el => {
224
+ if (el instanceof HTMLInputElement) {
225
+ return el.type === 'checkbox' || el.type === 'radio';
226
+ }
227
+ const role = el.getAttribute('role');
228
+ return role === 'checkbox' || role === 'radio' || role === 'switch';
229
+ });
230
+ if (isToggle) {
231
+ if (['true', 'false'].includes(value)) {
232
+ await handle.asLocator().fill(value === 'true');
233
+ }
234
+ else {
235
+ throw new Error(`Checkboxes, radio boxes and toggles require "true" or "false" value, but ${value} was used`);
236
+ }
237
+ }
238
+ else {
239
+ // Increase timeout for longer input values.
240
+ const timeoutPerChar = 10; // ms
241
+ const fillTimeout = page.pptrPage.getDefaultTimeout() + value.length * timeoutPerChar;
242
+ await handle.asLocator().setTimeout(fillTimeout).fill(value);
243
+ }
183
244
  }
184
245
  }
185
246
  catch (error) {
@@ -200,7 +261,9 @@ export const fill = definePageTool({
200
261
  uid: zod
201
262
  .string()
202
263
  .describe('The uid of an element on the page from the page content snapshot'),
203
- value: zod.string().describe('The value to fill in'),
264
+ value: zod
265
+ .string()
266
+ .describe('The value to fill in. "true" or "false" for checkboxes and toggles, "true" for radio buttons.'),
204
267
  includeSnapshot: includeSnapshotSchema,
205
268
  },
206
269
  blockedByDialog: true,
@@ -273,7 +336,7 @@ export const drag = definePageTool({
273
336
  });
274
337
  export const fillForm = definePageTool({
275
338
  name: 'fill_form',
276
- description: `Fill out multiple form elements at once`,
339
+ description: `Fill out multiple form elements (inputs, selects, checkboxes, radios) at once. ALWAYS prefer this tool over multiple individual 'fill' or 'click' calls when interacting with forms. It is significantly faster, more reliable, and reduces turn count. Example: Fill username, password, and check "Remember Me" in one call.`,
277
340
  annotations: {
278
341
  category: ToolCategory.INPUT,
279
342
  readOnlyHint: false,
@@ -284,7 +347,9 @@ export const fillForm = definePageTool({
284
347
  // eslint-disable-next-line @local/enforce-zod-schema
285
348
  zod.object({
286
349
  uid: zod.string().describe('The uid of the element to fill out'),
287
- value: zod.string().describe('Value for the element'),
350
+ value: zod
351
+ .string()
352
+ .describe('Value for the element. "true" or "false" for checkboxes and toggles, "true" for radio buttons.'),
288
353
  }))
289
354
  .describe('Elements from snapshot to fill out.'),
290
355
  includeSnapshot: includeSnapshotSchema,
@@ -4,7 +4,7 @@
4
4
  * SPDX-License-Identifier: Apache-2.0
5
5
  */
6
6
  import path from 'node:path';
7
- import { snapshot, navigation, generateReport, zod, agenticBrowsingConfig, } from '../third_party/index.js';
7
+ import { snapshot, navigation, generateReport, zod, } from '../third_party/index.js';
8
8
  import { ToolCategory } from './categories.js';
9
9
  import { startTrace } from './performance.js';
10
10
  import { definePageTool } from './ToolDefinition.js';
@@ -67,17 +67,17 @@ export const lighthouseAudit = definePageTool({
67
67
  disabled: false,
68
68
  };
69
69
  }
70
- const options = {
71
- flags,
72
- config: agenticBrowsingConfig,
73
- };
74
70
  let result;
75
71
  try {
76
72
  if (mode === 'navigation') {
77
- result = await navigation(page.pptrPage, page.pptrPage.url(), options);
73
+ result = await navigation(page.pptrPage, page.pptrPage.url(), {
74
+ flags,
75
+ });
78
76
  }
79
77
  else {
80
- result = await snapshot(page.pptrPage, options);
78
+ result = await snapshot(page.pptrPage, {
79
+ flags,
80
+ });
81
81
  }
82
82
  if (!result) {
83
83
  throw new Error('Lighthouse audit failed.');
@@ -64,7 +64,7 @@ export const listPages = defineTool(args => {
64
64
  blockedByDialog: false,
65
65
  handler: async (_request, response) => {
66
66
  response.setIncludePages(true);
67
- response.setListInPageTools();
67
+ response.setListThirdPartyDeveloperTools();
68
68
  response.setListWebMcpTools();
69
69
  },
70
70
  };
@@ -90,7 +90,7 @@ export const selectPage = defineTool({
90
90
  const page = context.getPageById(request.params.pageId);
91
91
  context.selectPage(page);
92
92
  response.setIncludePages(true);
93
- response.setListInPageTools();
93
+ response.setListThirdPartyDeveloperTools();
94
94
  response.setListWebMcpTools();
95
95
  if (request.params.bringToFront) {
96
96
  await page.pptrPage.bringToFront();
@@ -123,7 +123,7 @@ export const closePage = defineTool({
123
123
  }
124
124
  }
125
125
  response.setIncludePages(true);
126
- response.setListInPageTools();
126
+ response.setListThirdPartyDeveloperTools();
127
127
  },
128
128
  });
129
129
  export const newPage = defineTool(args => {
@@ -163,7 +163,7 @@ export const newPage = defineTool(args => {
163
163
  timeout: request.params.timeout,
164
164
  }), request.params.allowList, request.params.timeout);
165
165
  response.setIncludePages(true);
166
- response.setListInPageTools();
166
+ response.setListThirdPartyDeveloperTools();
167
167
  },
168
168
  };
169
169
  });
@@ -295,7 +295,7 @@ export const navigatePage = definePageTool(args => {
295
295
  }
296
296
  }
297
297
  response.setIncludePages(true);
298
- response.setListInPageTools();
298
+ response.setListThirdPartyDeveloperTools();
299
299
  response.setListWebMcpTools();
300
300
  },
301
301
  };
@@ -6,30 +6,30 @@
6
6
  import { zod, ajv } from '../third_party/index.js';
7
7
  import { ToolCategory } from './categories.js';
8
8
  import { definePageTool } from './ToolDefinition.js';
9
- export const listInPageTools = definePageTool({
10
- name: 'list_in_page_tools',
11
- description: `Lists all in-page tools the page exposes for providing runtime information.
12
- In-page tools can be called via the 'execute_in_page_tool()' MCP tool.
13
- Alternatively, in-page tools can be executed by calling 'evaluate_script' and adding the
9
+ export const listThirdPartyDeveloperTools = definePageTool({
10
+ name: 'list_3p_developer_tools',
11
+ description: `Lists all third-party developer tools the page exposes for providing runtime information.
12
+ Third-party developer tools can be called via the 'execute_3p_developer_tool()' MCP tool.
13
+ Alternatively, third-party developer tools can be executed by calling 'evaluate_script' and adding the
14
14
  following command to the script:
15
15
  'window.__dtmcp.executeTool(toolName, params)'
16
- This might be helpful when the in-page-tools return non-serializable values or when composing
17
- the in-page-tools with additional functionality.`,
16
+ This might be helpful when the third-party developer tools return non-serializable values or when composing
17
+ third-party developer tools with additional functionality.`,
18
18
  annotations: {
19
- category: ToolCategory.IN_PAGE,
19
+ category: ToolCategory.THIRD_PARTY,
20
20
  readOnlyHint: true,
21
21
  },
22
22
  schema: {},
23
23
  blockedByDialog: false,
24
24
  handler: async (_request, response, _context) => {
25
- response.setListInPageTools();
25
+ response.setListThirdPartyDeveloperTools();
26
26
  },
27
27
  });
28
- export const executeInPageTool = definePageTool({
29
- name: 'execute_in_page_tool',
28
+ export const executeThirdPartyDeveloperTool = definePageTool({
29
+ name: 'execute_3p_developer_tool',
30
30
  description: `Executes a tool exposed by the page.`,
31
31
  annotations: {
32
- category: ToolCategory.IN_PAGE,
32
+ category: ToolCategory.THIRD_PARTY,
33
33
  readOnlyHint: false,
34
34
  },
35
35
  schema: {
@@ -58,7 +58,7 @@ export const executeInPageTool = definePageTool({
58
58
  throw new Error(`Failed to parse params as JSON: ${errorMessage}`);
59
59
  }
60
60
  }
61
- const toolGroup = request.page.getInPageTools();
61
+ const toolGroup = request.page.getThirdPartyDeveloperTools();
62
62
  const tool = toolGroup?.tools.find(t => t.name === toolName);
63
63
  if (!tool) {
64
64
  throw new Error(`Tool ${toolName} not found`);
@@ -69,7 +69,7 @@ export const executeInPageTool = definePageTool({
69
69
  if (!valid) {
70
70
  throw new Error(`Invalid parameters for tool ${toolName}: ${ajvInstance.errorsText(validate.errors)}`);
71
71
  }
72
- await request.page.executeInPageTool(toolName, params, response);
72
+ await request.page.executeThirdPartyDeveloperTool(toolName, params, response);
73
73
  },
74
74
  });
75
- //# sourceMappingURL=inPage.js.map
75
+ //# sourceMappingURL=thirdPartyDeveloper.js.map
@@ -6,7 +6,6 @@
6
6
  import * as consoleTools from './console.js';
7
7
  import * as emulationTools from './emulation.js';
8
8
  import * as extensionTools from './extensions.js';
9
- import * as inPageTools from './inPage.js';
10
9
  import * as inputTools from './input.js';
11
10
  import * as lighthouseTools from './lighthouse.js';
12
11
  import * as memoryTools from './memory.js';
@@ -18,6 +17,7 @@ import * as screenshotTools from './screenshot.js';
18
17
  import * as scriptTools from './script.js';
19
18
  import * as slimTools from './slim/tools.js';
20
19
  import * as snapshotTools from './snapshot.js';
20
+ import * as thirdPartyDeveloperTools from './thirdPartyDeveloper.js';
21
21
  import * as webmcpTools from './webmcp.js';
22
22
  export const createTools = (args) => {
23
23
  const rawTools = args.slim
@@ -26,7 +26,6 @@ export const createTools = (args) => {
26
26
  ...Object.values(consoleTools),
27
27
  ...Object.values(emulationTools),
28
28
  ...Object.values(extensionTools),
29
- ...Object.values(inPageTools),
30
29
  ...Object.values(inputTools),
31
30
  ...Object.values(lighthouseTools),
32
31
  ...Object.values(memoryTools),
@@ -37,6 +36,7 @@ export const createTools = (args) => {
37
36
  ...Object.values(screenshotTools),
38
37
  ...Object.values(scriptTools),
39
38
  ...Object.values(snapshotTools),
39
+ ...Object.values(thirdPartyDeveloperTools),
40
40
  ...Object.values(webmcpTools),
41
41
  ];
42
42
  const tools = [];
@@ -10,9 +10,8 @@ export const listWebMcpTools = definePageTool({
10
10
  name: 'list_webmcp_tools',
11
11
  description: `Lists all WebMCP tools the page exposes.`,
12
12
  annotations: {
13
- category: ToolCategory.DEBUGGING,
13
+ category: ToolCategory.WEBMCP,
14
14
  readOnlyHint: true,
15
- conditions: ['experimentalWebmcp'],
16
15
  },
17
16
  schema: {},
18
17
  blockedByDialog: false,
@@ -24,9 +23,8 @@ export const executeWebMcpTool = definePageTool({
24
23
  name: 'execute_webmcp_tool',
25
24
  description: `Executes a WebMCP tool exposed by the page.`,
26
25
  annotations: {
27
- category: ToolCategory.DEBUGGING,
26
+ category: ToolCategory.WEBMCP,
28
27
  readOnlyHint: false,
29
- conditions: ['experimentalWebmcp'],
30
28
  },
31
29
  schema: {
32
30
  toolName: zod.string().describe('The name of the WebMCP tool to execute'),
@@ -5,6 +5,6 @@
5
5
  */
6
6
  // If moved update release-please config
7
7
  // x-release-please-start-version
8
- export const VERSION = '0.24.0';
8
+ export const VERSION = '0.26.0';
9
9
  // x-release-please-end
10
10
  //# sourceMappingURL=version.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chrome-devtools-mcp",
3
- "version": "0.24.0",
3
+ "version": "0.26.0",
4
4
  "description": "MCP server for Chrome DevTools",
5
5
  "type": "module",
6
6
  "bin": {
@@ -48,7 +48,7 @@
48
48
  "mcpName": "io.github.ChromeDevTools/chrome-devtools-mcp",
49
49
  "devDependencies": {
50
50
  "@eslint/js": "^9.35.0",
51
- "@google/genai": "^1.37.0",
51
+ "@google/genai": "^2.0.1",
52
52
  "@modelcontextprotocol/sdk": "1.29.0",
53
53
  "@rollup/plugin-commonjs": "^29.0.0",
54
54
  "@rollup/plugin-json": "^6.1.0",
@@ -62,22 +62,21 @@
62
62
  "@types/yargs": "^17.0.33",
63
63
  "@typescript-eslint/eslint-plugin": "^8.43.0",
64
64
  "@typescript-eslint/parser": "^8.43.0",
65
- "chrome-devtools-frontend": "1.0.1618066",
65
+ "chrome-devtools-frontend": "1.0.1626840",
66
66
  "core-js": "3.49.0",
67
67
  "debug": "4.4.3",
68
68
  "eslint": "^9.35.0",
69
69
  "eslint-import-resolver-typescript": "^4.4.4",
70
70
  "eslint-plugin-import": "^2.32.0",
71
71
  "globals": "^17.0.0",
72
- "lighthouse": "13.2.0",
72
+ "lighthouse": "13.3.0",
73
73
  "prettier": "^3.6.2",
74
- "puppeteer": "24.42.0",
75
- "rollup": "4.60.2",
74
+ "puppeteer": "24.43.0",
75
+ "rollup": "4.60.3",
76
76
  "rollup-plugin-cleanup": "^3.2.1",
77
77
  "rollup-plugin-license": "^3.6.0",
78
78
  "semver": "^7.7.4",
79
- "sinon": "^21.0.0",
80
- "tiktoken": "^1.0.22",
79
+ "sinon": "^22.0.0",
81
80
  "typescript": "^6.0.2",
82
81
  "typescript-eslint": "^8.43.0",
83
82
  "urlpattern-polyfill": "^10.1.0",