豆豆友情提示:这是一个非官方 GitHub 代理镜像,主要用于网络测试或访问加速。请勿在此进行登录、注册或处理任何敏感信息。进行这些操作请务必访问官方网站 github.com。 Raw 内容也通过此代理提供。
Skip to content

Commit a453332

Browse files
authored
chore: add a script to generate CLI (#1077)
The script will be used to generate CLI for the MCP server at `src/bin/cliDefinitions.ts`. It uses the live server JSON schemas to make it more compatible with the actual MCP server at runtime. The code definitions are used to extend the CLI with tool categories.
1 parent e51ba47 commit a453332

File tree

2 files changed

+176
-0
lines changed

2 files changed

+176
-0
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
"bin": "./build/src/index.js",
77
"main": "./build/src/server.js",
88
"scripts": {
9+
"cli:generate": "node --experimental-strip-types scripts/generate-cli.ts",
910
"clean": "node -e \"require('fs').rmSync('build', {recursive: true, force: true})\"",
1011
"bundle": "npm run clean && npm run build && rollup -c rollup.config.mjs && node -e \"require('fs').rmSync('build/node_modules', {recursive: true, force: true})\" && node --experimental-strip-types scripts/append-lighthouse-notices.ts",
1112
"build": "tsc && node --experimental-strip-types --no-warnings=ExperimentalWarning scripts/post-build.ts",

scripts/generate-cli.ts

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
/**
2+
* @license
3+
* Copyright 2026 Google LLC
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
import fs from 'node:fs';
8+
import path from 'node:path';
9+
10+
import {Client} from '@modelcontextprotocol/sdk/client/index.js';
11+
import {StdioClientTransport} from '@modelcontextprotocol/sdk/client/stdio.js';
12+
13+
import {parseArguments} from '../build/src/cli.js';
14+
import {labels} from '../build/src/tools/categories.js';
15+
import {createTools} from '../build/src/tools/tools.js';
16+
17+
const OUTPUT_PATH = path.join(
18+
import.meta.dirname,
19+
'../src/bin/cliDefinitions.ts',
20+
);
21+
22+
async function fetchTools() {
23+
console.log('Connecting to chrome-devtools-mcp to fetch tools...');
24+
// Use the local build of the server
25+
const serverPath = path.join(import.meta.dirname, '../build/src/index.js');
26+
27+
const transport = new StdioClientTransport({
28+
command: 'node',
29+
args: [serverPath],
30+
env: {...process.env, CHROME_DEVTOOLS_MCP_NO_USAGE_STATISTICS: 'true'},
31+
});
32+
33+
const client = new Client(
34+
{
35+
name: 'chrome-devtools-cli-generator',
36+
version: '0.1.0',
37+
},
38+
{
39+
capabilities: {},
40+
},
41+
);
42+
43+
await client.connect(transport);
44+
try {
45+
const toolsResponse = await client.listTools();
46+
if (!toolsResponse.tools?.length) {
47+
throw new Error(`No tools were fetched`);
48+
}
49+
const tools = toolsResponse.tools || [];
50+
console.log(`Fetched ${tools.length} tools`);
51+
return tools;
52+
} finally {
53+
await client.close();
54+
}
55+
}
56+
57+
interface CliOption {
58+
name: string;
59+
type: string;
60+
description: string;
61+
required: boolean;
62+
default?: unknown;
63+
enum?: unknown[];
64+
}
65+
66+
interface JsonSchema {
67+
type?: string | string[];
68+
description?: string;
69+
properties?: Record<string, JsonSchema>;
70+
required?: string[];
71+
default?: unknown;
72+
enum?: unknown[];
73+
}
74+
75+
function schemaToCLIOptions(schema: JsonSchema): CliOption[] {
76+
if (!schema || !schema.properties) {
77+
return [];
78+
}
79+
const required = schema.required || [];
80+
const properties = schema.properties;
81+
return Object.entries(properties).map(([name, prop]) => {
82+
const isRequired = required.includes(name);
83+
const description = prop.description || '';
84+
if (typeof prop.type !== 'string') {
85+
throw new Error(
86+
`Property ${name} has a complex type not supported by CLI.`,
87+
);
88+
}
89+
return {
90+
name,
91+
type: prop.type,
92+
description,
93+
required: isRequired,
94+
default: prop.default,
95+
enum: prop.enum,
96+
};
97+
});
98+
}
99+
100+
async function generateCli() {
101+
const tools = await fetchTools();
102+
// Sort tools by name
103+
const sortedTools = tools.sort((a, b) => a.name.localeCompare(b.name));
104+
105+
const staticTools = createTools(parseArguments());
106+
const toolNameToCategory = new Map<string, string>();
107+
for (const tool of staticTools) {
108+
toolNameToCategory.set(
109+
tool.name,
110+
labels[tool.annotations.category as keyof typeof labels],
111+
);
112+
}
113+
114+
const commands: Record<
115+
string,
116+
{description: string; category: string; args: Record<string, CliOption>}
117+
> = {};
118+
119+
for (const tool of sortedTools) {
120+
const options = schemaToCLIOptions(tool.inputSchema);
121+
const args: Record<string, CliOption> = {};
122+
for (const opt of options) {
123+
args[opt.name] = opt;
124+
}
125+
const category = toolNameToCategory.get(tool.name);
126+
if (!category) {
127+
throw new Error(`Tool ${tool.name} has no category.`);
128+
}
129+
if (!tool.description) {
130+
throw new Error(`Tool ${tool.name} is missing descripttion`);
131+
}
132+
commands[tool.name] = {
133+
description: tool.description,
134+
category,
135+
args,
136+
};
137+
}
138+
139+
const lines: string[] = [];
140+
lines.push(`/**
141+
* @license
142+
* Copyright ${new Date().getFullYear()} Google LLC
143+
* SPDX-License-Identifier: Apache-2.0
144+
*/
145+
146+
// NOTE: do not edit manually. Auto-generated by 'npm run cli:generate'.
147+
148+
export interface ArgDef {
149+
name: string;
150+
type: string;
151+
description: string;
152+
required: boolean;
153+
default?: string | number | boolean;
154+
enum?: ReadonlyArray<string | number>;
155+
}
156+
export type Commands = Record<
157+
string,
158+
{
159+
description: string;
160+
category: string;
161+
args: Record<string, ArgDef>
162+
}
163+
>;
164+
export const commands: Commands = ${JSON.stringify(commands, null, 2)} as const;
165+
`);
166+
167+
fs.mkdirSync(path.dirname(OUTPUT_PATH), {recursive: true});
168+
fs.writeFileSync(OUTPUT_PATH, lines.join(''));
169+
console.log(`Generated CLI at ${OUTPUT_PATH}`);
170+
}
171+
172+
generateCli().catch(err => {
173+
console.error('Error during generation:', err);
174+
process.exit(1);
175+
});

0 commit comments

Comments
 (0)