mirror of
https://github.com/ChromeDevTools/chrome-devtools-mcp.git
synced 2026-09-28 11:22:57 +08:00
refactor: simplify our gen scritps (#2793)
This commit is contained in:
+10
-54
@@ -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
@@ -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, '&')
|
||||
@@ -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,
|
||||
};
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user