Skip to content

Commit b2b3e99

Browse files
authored
feat(cli): generate commands for conditional tools (#1962)
This PR adds all tools in the CLI interface. When a tool is not enabled the server responds with an error guiding the user on how to enable the category or the experiment. Closes: #1933
1 parent 7ee5e86 commit b2b3e99

19 files changed

Lines changed: 484 additions & 117 deletions

docs/tool-reference.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -398,7 +398,7 @@ in the DevTools Elements panel (if any).
398398

399399
## Extensions
400400

401-
> NOTE: Extensions are not active by default. Use the '--category-extensions' flag
401+
> NOTE: Extensions are not active by default. Use the '--categoryExtensions' flag
402402
403403
### `install_extension`
404404

scripts/generate-cli.ts

Lines changed: 49 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,12 @@ import {Client} from '@modelcontextprotocol/sdk/client/index.js';
1111
import {StdioClientTransport} from '@modelcontextprotocol/sdk/client/stdio.js';
1212

1313
import {parseArguments} from '../build/src/bin/chrome-devtools-mcp-cli-options.js';
14-
import {labels} from '../build/src/tools/categories.js';
14+
import {buildFlag} from '../build/src/index.js';
15+
import {
16+
labels,
17+
ToolCategory,
18+
OFF_BY_DEFAULT_CATEGORIES,
19+
} from '../build/src/tools/categories.js';
1520
import {createTools} from '../build/src/tools/tools.js';
1621

1722
const OUTPUT_PATH = path.join(
@@ -29,7 +34,7 @@ async function fetchTools() {
2934

3035
const transport = new StdioClientTransport({
3136
command: 'node',
32-
args: [serverPath],
37+
args: [serverPath, '--viaCli'],
3338
env: {...process.env, CHROME_DEVTOOLS_MCP_NO_USAGE_STATISTICS: 'true'},
3439
});
3540

@@ -103,6 +108,15 @@ function schemaToCLIOptions(schema: JsonSchema): CliOption[] {
103108
async function generateCli() {
104109
const tools = await fetchTools();
105110

111+
const staticTools = createTools(parseArguments());
112+
const toolNameToCategoryEnum = new Map<string, string>();
113+
const toolNameToConditions = new Map<string, string[]>();
114+
115+
for (const tool of staticTools) {
116+
toolNameToCategoryEnum.set(tool.name, tool.annotations.category);
117+
toolNameToConditions.set(tool.name, tool.annotations.conditions || []);
118+
}
119+
106120
// Sort tools by name
107121
const sortedTools = tools
108122
.sort((a, b) => a.name.localeCompare(b.name))
@@ -117,18 +131,17 @@ async function generateCli() {
117131
if (tool.name === 'wait_for') {
118132
return false;
119133
}
134+
// Skipping get_tab_id as it is for internal integrations
135+
if (tool.name === 'get_tab_id') {
136+
return false;
137+
}
138+
// Skipping in_page tools as they are not launched yet
139+
if (toolNameToCategoryEnum.get(tool.name) === ToolCategory.IN_PAGE) {
140+
return false;
141+
}
120142
return true;
121143
});
122144

123-
const staticTools = createTools(parseArguments());
124-
const toolNameToCategory = new Map<string, string>();
125-
for (const tool of staticTools) {
126-
toolNameToCategory.set(
127-
tool.name,
128-
labels[tool.annotations.category as keyof typeof labels],
129-
);
130-
}
131-
132145
const commands: Record<
133146
string,
134147
{description: string; category: string; args: Record<string, CliOption>}
@@ -140,15 +153,36 @@ async function generateCli() {
140153
for (const opt of options) {
141154
args[opt.name] = opt;
142155
}
143-
const category = toolNameToCategory.get(tool.name);
144-
if (!category) {
156+
157+
const categoryEnum = toolNameToCategoryEnum.get(tool.name);
158+
if (!categoryEnum) {
145159
throw new Error(`Tool ${tool.name} has no category.`);
146160
}
161+
const category = labels[categoryEnum as unknown as keyof typeof labels];
147162
if (!tool.description) {
148-
throw new Error(`Tool ${tool.name} is missing descripttion`);
163+
throw new Error(`Tool ${tool.name} is missing description`);
164+
}
165+
166+
let description = tool.description;
167+
const requiredFlags: string[] = [];
168+
169+
const isOffByDefault = OFF_BY_DEFAULT_CATEGORIES.includes(categoryEnum);
170+
if (isOffByDefault) {
171+
const categoryFlag = buildFlag(categoryEnum);
172+
requiredFlags.push(`--${categoryFlag}=true`);
149173
}
174+
175+
const conditions = toolNameToConditions.get(tool.name) || [];
176+
for (const condition of conditions) {
177+
requiredFlags.push(`--${condition}=true`);
178+
}
179+
180+
if (requiredFlags.length > 0) {
181+
description += ` (requires flag: ${requiredFlags.join(', ')})`;
182+
}
183+
150184
commands[tool.name] = {
151-
description: tool.description,
185+
description,
152186
category,
153187
args,
154188
};

scripts/generate-docs.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {get_encoding} from 'tiktoken';
1313

1414
import {cliOptions} from '../build/src/bin/chrome-devtools-mcp-cli-options.js';
1515
import type {ParsedArguments} from '../build/src/bin/chrome-devtools-mcp-cli-options.js';
16+
import {buildFlag} from '../build/src/index.js';
1617
import {
1718
ToolCategory,
1819
OFF_BY_DEFAULT_CATEGORIES,
@@ -356,7 +357,7 @@ async function generateReference(
356357
markdown += `## ${categoryName}\n\n`;
357358

358359
if (OFF_BY_DEFAULT_CATEGORIES.includes(category)) {
359-
const flagName = `--category-${category}`;
360+
const flagName = `--${buildFlag(category)}`;
360361

361362
markdown += `> NOTE: ${categoryName} are not active by default. Use the '${flagName}' flag\n\n`;
362363
}
@@ -446,6 +447,11 @@ function getToolsAndCategories(tools: any) {
446447
// Convert ToolDefinitions to ToolWithAnnotations
447448
const toolsWithAnnotations: ToolWithAnnotations[] = tools
448449
.filter(tool => {
450+
// Skipping in_page tools as they are not launched yet
451+
if (tool.annotations.category.includes('experimental')) {
452+
return false;
453+
}
454+
449455
if (!tool.annotations.conditions) {
450456
return true;
451457
}

skills/chrome-devtools-cli/SKILL.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,27 @@ chrome-devtools take_snapshot # Take a text snapshot of the page from the a11y t
123123
chrome-devtools take_snapshot --verbose true --filePath "s.txt" # Take a verbose snapshot and save to file
124124
```
125125

126+
## Extensions
127+
128+
```bash
129+
chrome-devtools list_extensions # Lists all the Chrome extensions installed in the browser
130+
chrome-devtools install_extension "/path/to/extension" # Installs a Chrome extension from the given path
131+
chrome-devtools uninstall_extension "extension_id" # Uninstalls a Chrome extension by its ID
132+
chrome-devtools reload_extension "extension_id" # Reloads an unpacked Chrome extension by its ID
133+
chrome-devtools trigger_extension_action "extension_id" # Triggers the default action of an extension by its ID
134+
```
135+
136+
## Experimental Features
137+
138+
Experimental tools are disabled by default. Enable them with the corresponding flag during `start`.
139+
140+
```bash
141+
chrome-devtools click_at 100 200 # Clicks at the provided coordinates (requires --experimentalVision=true)
142+
chrome-devtools screencast_start # Starts a screencast recording (requires --experimentalScreencast=true and ffmpeg)
143+
chrome-devtools screencast_stop # Stops the active screencast
144+
chrome-devtools list_webmcp_tools # List all WebMCP tools (requires --experimentalWebmcp=true)
145+
```
146+
126147
## Service Management
127148

128149
```bash

src/McpResponse.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -245,7 +245,7 @@ export class McpResponse implements Response {
245245
}
246246

247247
setListInPageTools(): void {
248-
if (this.#args.categoryInPageTools) {
248+
if (this.#args.categoryExperimentalInPage) {
249249
this.#listInPageTools = true;
250250
}
251251
}

0 commit comments

Comments
 (0)