diff --git a/docs/tool-reference.md b/docs/tool-reference.md index b7f20ab37..1c136b5a4 100644 --- a/docs/tool-reference.md +++ b/docs/tool-reference.md @@ -398,7 +398,7 @@ in the DevTools Elements panel (if any). ## Extensions -> NOTE: Extensions are not active by default. Use the '--category-extensions' flag +> NOTE: Extensions are not active by default. Use the '--categoryExtensions' flag ### `install_extension` diff --git a/scripts/generate-cli.ts b/scripts/generate-cli.ts index 6cb63647e..4eb9f1124 100644 --- a/scripts/generate-cli.ts +++ b/scripts/generate-cli.ts @@ -11,7 +11,12 @@ import {Client} from '@modelcontextprotocol/sdk/client/index.js'; import {StdioClientTransport} from '@modelcontextprotocol/sdk/client/stdio.js'; import {parseArguments} from '../build/src/bin/chrome-devtools-mcp-cli-options.js'; -import {labels} from '../build/src/tools/categories.js'; +import {buildFlag} from '../build/src/index.js'; +import { + labels, + ToolCategory, + OFF_BY_DEFAULT_CATEGORIES, +} from '../build/src/tools/categories.js'; import {createTools} from '../build/src/tools/tools.js'; const OUTPUT_PATH = path.join( @@ -29,7 +34,7 @@ async function fetchTools() { const transport = new StdioClientTransport({ command: 'node', - args: [serverPath], + args: [serverPath, '--viaCli'], env: {...process.env, CHROME_DEVTOOLS_MCP_NO_USAGE_STATISTICS: 'true'}, }); @@ -103,6 +108,15 @@ function schemaToCLIOptions(schema: JsonSchema): CliOption[] { async function generateCli() { const tools = await fetchTools(); + const staticTools = createTools(parseArguments()); + const toolNameToCategoryEnum = new Map(); + const toolNameToConditions = new Map(); + + for (const tool of staticTools) { + toolNameToCategoryEnum.set(tool.name, tool.annotations.category); + toolNameToConditions.set(tool.name, tool.annotations.conditions || []); + } + // Sort tools by name const sortedTools = tools .sort((a, b) => a.name.localeCompare(b.name)) @@ -117,18 +131,17 @@ async function generateCli() { if (tool.name === 'wait_for') { return false; } + // Skipping get_tab_id as it is for internal integrations + if (tool.name === 'get_tab_id') { + return false; + } + // Skipping in_page tools as they are not launched yet + if (toolNameToCategoryEnum.get(tool.name) === ToolCategory.IN_PAGE) { + return false; + } return true; }); - const staticTools = createTools(parseArguments()); - const toolNameToCategory = new Map(); - for (const tool of staticTools) { - toolNameToCategory.set( - tool.name, - labels[tool.annotations.category as keyof typeof labels], - ); - } - const commands: Record< string, {description: string; category: string; args: Record} @@ -140,15 +153,36 @@ async function generateCli() { for (const opt of options) { args[opt.name] = opt; } - const category = toolNameToCategory.get(tool.name); - if (!category) { + + const categoryEnum = toolNameToCategoryEnum.get(tool.name); + if (!categoryEnum) { throw new Error(`Tool ${tool.name} has no category.`); } + const category = labels[categoryEnum as unknown as keyof typeof labels]; if (!tool.description) { - throw new Error(`Tool ${tool.name} is missing descripttion`); + throw new Error(`Tool ${tool.name} is missing description`); + } + + let description = tool.description; + const requiredFlags: string[] = []; + + const isOffByDefault = OFF_BY_DEFAULT_CATEGORIES.includes(categoryEnum); + if (isOffByDefault) { + const categoryFlag = buildFlag(categoryEnum); + requiredFlags.push(`--${categoryFlag}=true`); } + + const conditions = toolNameToConditions.get(tool.name) || []; + for (const condition of conditions) { + requiredFlags.push(`--${condition}=true`); + } + + if (requiredFlags.length > 0) { + description += ` (requires flag: ${requiredFlags.join(', ')})`; + } + commands[tool.name] = { - description: tool.description, + description, category, args, }; diff --git a/scripts/generate-docs.ts b/scripts/generate-docs.ts index 4be8fcbd7..8b32e9974 100644 --- a/scripts/generate-docs.ts +++ b/scripts/generate-docs.ts @@ -13,6 +13,7 @@ import {get_encoding} from 'tiktoken'; import {cliOptions} from '../build/src/bin/chrome-devtools-mcp-cli-options.js'; import type {ParsedArguments} from '../build/src/bin/chrome-devtools-mcp-cli-options.js'; +import {buildFlag} from '../build/src/index.js'; import { ToolCategory, OFF_BY_DEFAULT_CATEGORIES, @@ -356,7 +357,7 @@ async function generateReference( markdown += `## ${categoryName}\n\n`; if (OFF_BY_DEFAULT_CATEGORIES.includes(category)) { - const flagName = `--category-${category}`; + const flagName = `--${buildFlag(category)}`; markdown += `> NOTE: ${categoryName} are not active by default. Use the '${flagName}' flag\n\n`; } @@ -446,6 +447,11 @@ function getToolsAndCategories(tools: any) { // Convert ToolDefinitions to ToolWithAnnotations const toolsWithAnnotations: ToolWithAnnotations[] = tools .filter(tool => { + // Skipping in_page tools as they are not launched yet + if (tool.annotations.category.includes('experimental')) { + return false; + } + if (!tool.annotations.conditions) { return true; } diff --git a/skills/chrome-devtools-cli/SKILL.md b/skills/chrome-devtools-cli/SKILL.md index 2b251c69b..fb80aa830 100644 --- a/skills/chrome-devtools-cli/SKILL.md +++ b/skills/chrome-devtools-cli/SKILL.md @@ -123,6 +123,27 @@ chrome-devtools take_snapshot # Take a text snapshot of the page from the a11y t chrome-devtools take_snapshot --verbose true --filePath "s.txt" # Take a verbose snapshot and save to file ``` +## Extensions + +```bash +chrome-devtools list_extensions # Lists all the Chrome extensions installed in the browser +chrome-devtools install_extension "/path/to/extension" # Installs a Chrome extension from the given path +chrome-devtools uninstall_extension "extension_id" # Uninstalls a Chrome extension by its ID +chrome-devtools reload_extension "extension_id" # Reloads an unpacked Chrome extension by its ID +chrome-devtools trigger_extension_action "extension_id" # Triggers the default action of an extension by its ID +``` + +## Experimental Features + +Experimental tools are disabled by default. Enable them with the corresponding flag during `start`. + +```bash +chrome-devtools click_at 100 200 # Clicks at the provided coordinates (requires --experimentalVision=true) +chrome-devtools screencast_start # Starts a screencast recording (requires --experimentalScreencast=true and ffmpeg) +chrome-devtools screencast_stop # Stops the active screencast +chrome-devtools list_webmcp_tools # List all WebMCP tools (requires --experimentalWebmcp=true) +``` + ## Service Management ```bash diff --git a/src/McpResponse.ts b/src/McpResponse.ts index 246536b77..9fc4ea80f 100644 --- a/src/McpResponse.ts +++ b/src/McpResponse.ts @@ -245,7 +245,7 @@ export class McpResponse implements Response { } setListInPageTools(): void { - if (this.#args.categoryInPageTools) { + if (this.#args.categoryExperimentalInPage) { this.#listInPageTools = true; } } diff --git a/src/bin/chrome-devtools-cli-options.ts b/src/bin/chrome-devtools-cli-options.ts index bf5a77420..e2da50adb 100644 --- a/src/bin/chrome-devtools-cli-options.ts +++ b/src/bin/chrome-devtools-cli-options.ts @@ -49,6 +49,38 @@ export const commands: Commands = { }, }, }, + click_at: { + description: + 'Clicks at the provided coordinates (requires flag: --experimentalVision=true)', + category: 'Input automation', + args: { + x: { + name: 'x', + type: 'number', + description: 'The x coordinate', + required: true, + }, + y: { + name: 'y', + type: 'number', + description: 'The y coordinate', + required: true, + }, + dblClick: { + name: 'dblClick', + type: 'boolean', + description: 'Set to true for double clicks. Default is false.', + required: false, + }, + includeSnapshot: { + name: 'includeSnapshot', + type: 'boolean', + description: + 'Whether to include a snapshot in the response. Default is false.', + required: false, + }, + }, + }, close_page: { description: 'Closes the page by its index. The last open page cannot be closed.', @@ -164,6 +196,26 @@ export const commands: Commands = { }, }, }, + execute_webmcp_tool: { + description: + 'Executes a WebMCP tool exposed by the page. (requires flag: --experimentalWebmcp=true)', + category: 'Debugging', + args: { + toolName: { + name: 'toolName', + type: 'string', + description: 'The name of the WebMCP tool to execute', + required: true, + }, + input: { + name: 'input', + type: 'string', + description: + 'The JSON-stringified parameters to pass to the WebMCP tool', + required: false, + }, + }, + }, fill: { description: 'Type text into an input, text area or select an option from a