refactor: simplify our gen scritps (#2793)

This commit is contained in:
Nikolay Vitkov
2026-09-21 14:39:51 +00:00
committed by GitHub
parent da3c40609a
commit 5068584894
2 changed files with 28 additions and 211 deletions
+10 -54
View File
@@ -7,12 +7,12 @@
import fs from 'node:fs';
import path from 'node:path';
import {Client, StdioClientTransport} from '../build/src/third_party/index.js';
import {mcpOptions, parseArguments} from '../build/src/config/mcp-options.js';
import {
isCategoryOffByDefault,
categoryToFlagName,
} from '../build/src/config/category-options.js';
import {mcpOptions, parseArguments} from '../build/src/config/mcp-options.js';
import {zod} from '../build/src/third_party/index.js';
import {labels, ToolCategory} from '../build/src/tools/categories.js';
import {createTools} from '../build/src/tools/tools.js';
@@ -21,44 +21,6 @@ const OUTPUT_PATH = path.join(
'../src/config/cli-options.ts',
);
async function fetchTools() {
console.log('Connecting to chrome-devtools-mcp to fetch tools...');
// Use the local build of the server
const serverPath = path.join(
import.meta.dirname,
'../build/src/bin/chrome-devtools-mcp.js',
);
const transport = new StdioClientTransport({
command: 'node',
args: [serverPath, '--viaCli'],
env: {...process.env, CHROME_DEVTOOLS_MCP_NO_USAGE_STATISTICS: 'true'},
});
const client = new Client(
{
name: 'chrome-devtools-cli-generator',
version: '0.1.0',
},
{
capabilities: {},
},
);
await client.connect(transport);
try {
const toolsResponse = await client.listTools();
if (!toolsResponse.tools?.length) {
throw new Error(`No tools were fetched`);
}
const tools = toolsResponse.tools || [];
console.log(`Fetched ${tools.length} tools`);
return tools;
} finally {
await client.close();
}
}
interface CliOption {
name: string;
type: string;
@@ -103,16 +65,7 @@ function schemaToCLIOptions(schema: JsonSchema): CliOption[] {
}
async function generateCli() {
const tools = await fetchTools();
const staticTools = createTools(parseArguments('0.0.0', [], {}));
const toolNameToCategoryEnum = new Map<string, string>();
const toolNameToConditions = new Map<string, string[]>();
for (const tool of staticTools) {
toolNameToCategoryEnum.set(tool.name, tool.annotations.category);
toolNameToConditions.set(tool.name, tool.annotations.conditions || []);
}
const tools = createTools(parseArguments('0.0.0', ['', '', '--viaCli']));
// Sort tools by name
const sortedTools = tools
@@ -133,7 +86,7 @@ async function generateCli() {
return false;
}
// Skipping in_page tools as they are not launched yet
if (toolNameToCategoryEnum.get(tool.name) === ToolCategory.IN_PAGE) {
if (tool.annotations.category === ToolCategory.IN_PAGE) {
return false;
}
return true;
@@ -145,13 +98,16 @@ async function generateCli() {
> = {};
for (const tool of sortedTools) {
const options = schemaToCLIOptions(tool.inputSchema);
const inputSchema = zod.toJSONSchema(zod.object(tool.schema), {
io: 'input',
}) as JsonSchema;
const options = schemaToCLIOptions(inputSchema);
const args: Record<string, CliOption> = {};
for (const opt of options) {
args[opt.name] = opt;
}
const categoryEnum = toolNameToCategoryEnum.get(tool.name);
const categoryEnum = tool.annotations.category;
if (!categoryEnum) {
throw new Error(`Tool ${tool.name} has no category.`);
}
@@ -169,7 +125,7 @@ async function generateCli() {
requiredFlags.push(`--${categoryFlag}=true`);
}
const conditions = toolNameToConditions.get(tool.name) || [];
const conditions = tool.annotations.conditions || [];
for (const condition of conditions) {
const option = mcpOptions[condition as keyof typeof mcpOptions];
if (!option || !('default' in option) || option.default !== true) {
+18 -157
View File
@@ -16,7 +16,7 @@ import {
isCategoryOffByDefault,
categoryToFlagName,
} from '../build/src/config/category-options.js';
import type {Tool, zod} from '../src/third_party/index.js';
import {zod} from '../build/src/third_party/index.js';
import {ToolCategory, labels} from '../build/src/tools/categories.js';
import type {
DefinedPageTool,
@@ -27,50 +27,29 @@ import {createTools} from '../build/src/tools/tools.js';
const OUTPUT_PATH = './docs/tool-reference.md';
const SLIM_OUTPUT_PATH = './docs/slim-tool-reference.md';
// Extend the MCP Tool type to include our annotations
interface ToolWithAnnotations extends Omit<Tool, 'inputSchema'> {
interface TypeInfo {
type?: string;
enum?: string[];
items?: TypeInfo;
description?: string;
default?: unknown;
}
interface ToolWithAnnotations {
name: string;
description: string;
inputSchema: {
type: 'object';
type?: string;
properties?: Record<string, TypeInfo>;
required?: string[];
};
annotations?: {
title?: string;
category?: typeof ToolCategory;
category?: ToolCategory;
conditions?: string[];
};
}
interface ZodCheck {
kind: string;
}
interface ZodDef {
typeName?: string;
checks?: ZodCheck[];
values?: string[];
entries?: Record<string, string>;
type?: string | ZodSchema;
innerType?: ZodSchema;
schema?: ZodSchema;
in?: ZodSchema;
defaultValue?: (() => unknown) | unknown;
}
interface ZodSchema {
_def: ZodDef;
description?: string;
isInt?: boolean;
}
interface TypeInfo {
type: string;
enum?: string[];
items?: TypeInfo;
description?: string;
default?: unknown;
}
function escapeHtmlTags(text: string): string {
return text
.replace(/&(?![a-zA-Z]+;)/g, '&amp;')
@@ -206,111 +185,6 @@ function updateConfigurationWithOptionsMarkdown(optionsMarkdown: string): void {
console.log('Updated configuration.md with options markdown');
}
// Helper to convert Zod schema to JSON schema-like object for docs
function getZodTypeInfo(schema: ZodSchema): TypeInfo {
let description = schema.description;
let def = schema._def;
let defaultValue: unknown;
let typeName = def.typeName;
if (!typeName && typeof def.type === 'string') {
typeName = 'Zod' + def.type.charAt(0).toUpperCase() + def.type.slice(1);
}
// Unwrap optional/default/effects
while (
typeName === 'ZodOptional' ||
typeName === 'ZodDefault' ||
typeName === 'ZodEffects' ||
typeName === 'ZodPipeline' ||
typeName === 'ZodPipe'
) {
if (typeName === 'ZodDefault' && def.defaultValue !== undefined) {
defaultValue =
typeof def.defaultValue === 'function'
? (def.defaultValue as () => unknown)()
: def.defaultValue;
}
const next = def.innerType || def.schema || def.in;
if (!next) {
break;
}
schema = next;
def = schema._def;
typeName = def.typeName;
if (!typeName && typeof def.type === 'string') {
typeName = 'Zod' + def.type.charAt(0).toUpperCase() + def.type.slice(1);
}
if (!description && schema.description) {
description = schema.description;
}
}
const result: TypeInfo = {type: 'unknown'};
if (description) {
result.description = description;
}
if (defaultValue !== undefined) {
result.default = defaultValue;
}
switch (typeName) {
case 'ZodString':
result.type = 'string';
break;
case 'ZodNumber':
result.type =
schema.isInt || def.checks?.some((c: ZodCheck) => c.kind === 'int')
? 'integer'
: 'number';
break;
case 'ZodBoolean':
result.type = 'boolean';
break;
case 'ZodEnum':
result.type = 'string';
result.enum = def.values;
if (!result.enum && def.entries) {
result.enum = Object.values(def.entries);
}
break;
case 'ZodArray':
result.type = 'array';
if (typeof def.type !== 'string' && def.type) {
result.items = getZodTypeInfo(def.type);
}
break;
default:
result.type = 'unknown';
}
return result;
}
function isRequired(schema: ZodSchema): boolean {
let def = schema._def;
let typeName = def.typeName;
if (!typeName && typeof def.type === 'string') {
typeName = 'Zod' + def.type.charAt(0).toUpperCase() + def.type.slice(1);
}
while (
typeName === 'ZodEffects' ||
typeName === 'ZodPipeline' ||
typeName === 'ZodPipe'
) {
const next = def.schema || def.in;
if (!next) {
break;
}
schema = next;
def = schema._def;
typeName = def.typeName;
if (!typeName && typeof def.type === 'string') {
typeName = 'Zod' + def.type.charAt(0).toUpperCase() + def.type.slice(1);
}
}
return typeName !== 'ZodOptional' && typeName !== 'ZodDefault';
}
async function generateReference(
title: string,
outputPath: string,
@@ -475,27 +349,14 @@ function getToolsAndCategories(
return true;
})
.map(tool => {
const properties: Record<string, TypeInfo> = {};
const required: string[] = [];
for (const [key, schema] of Object.entries(
tool.schema as unknown as Record<string, ZodSchema>,
)) {
const info = getZodTypeInfo(schema);
properties[key] = info;
if (isRequired(schema)) {
required.push(key);
}
}
const inputSchema = zod.toJSONSchema(zod.object(tool.schema), {
io: 'input',
}) as ToolWithAnnotations['inputSchema'];
return {
name: tool.name,
description: tool.description,
inputSchema: {
type: 'object',
properties,
required,
},
inputSchema,
annotations: tool.annotations,
};
});