Skip to content

Commit c96ea45

Browse files
committed
build: rewrite docs generator
1 parent a29163d commit c96ea45

File tree

4 files changed

+136
-57
lines changed

4 files changed

+136
-57
lines changed

docs/tool-reference.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -280,15 +280,16 @@ so returned values have to JSON-serializable.
280280

281281
- **args** (array) _(optional)_: An optional list of arguments to pass to the function.
282282
- **function** (string) **(required)**: A JavaScript function declaration to be executed by the tool in the currently selected page.
283-
Example without arguments: `() => {
283+
Example without arguments: `() => {
284284
return document.title
285285
}` or `async () => {
286286
return await fetch("example.com")
287287
}`.
288-
Example with arguments: `(el) => {
288+
Example with arguments: `(el) => {
289289
return el.innerText;
290290
}`
291291

292+
292293
---
293294

294295
### `get_console_message`

scripts/generate-docs.ts

Lines changed: 98 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,12 @@
66

77
import fs from 'node:fs';
88

9-
import {Client} from '@modelcontextprotocol/sdk/client/index.js';
10-
import {StdioClientTransport} from '@modelcontextprotocol/sdk/client/stdio.js';
119
import type {Tool} from '@modelcontextprotocol/sdk/types.js';
1210

1311
import {cliOptions} from '../build/src/cli.js';
1412
import {ToolCategory, labels} from '../build/src/tools/categories.js';
13+
import {tools} from '../build/src/tools/tools.js';
1514

16-
const MCP_SERVER_PATH = 'build/src/index.js';
1715
const OUTPUT_PATH = './docs/tool-reference.md';
1816
const README_PATH = './README.md';
1917

@@ -162,34 +160,106 @@ function updateReadmeWithOptionsMarkdown(optionsMarkdown: string): void {
162160
console.log('Updated README.md with options markdown');
163161
}
164162

165-
async function generateToolDocumentation(): Promise<void> {
166-
console.log('Starting MCP server to query tool definitions...');
167-
168-
// Create MCP client with stdio transport pointing to the built server
169-
const transport = new StdioClientTransport({
170-
command: 'node',
171-
args: [MCP_SERVER_PATH, '--channel', 'canary'],
172-
});
173-
174-
const client = new Client(
175-
{
176-
name: 'docs-generator',
177-
version: '1.0.0',
178-
},
179-
{
180-
capabilities: {},
181-
},
182-
);
163+
// Helper to convert Zod schema to JSON schema-like object for docs
164+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
165+
function getZodTypeInfo(schema: any): {
166+
type: string;
167+
enum?: string[];
168+
items?: any;
169+
description?: string;
170+
default?: any;
171+
} {
172+
let description = schema.description;
173+
let def = schema._def;
174+
let defaultValue = undefined;
175+
176+
// Unwrap optional/default/effects
177+
while (
178+
def.typeName === 'ZodOptional' ||
179+
def.typeName === 'ZodDefault' ||
180+
def.typeName === 'ZodEffects'
181+
) {
182+
if (def.typeName === 'ZodDefault') {
183+
defaultValue = def.defaultValue();
184+
}
185+
schema = def.innerType || def.schema;
186+
def = schema._def;
187+
if (!description && schema.description) description = schema.description;
188+
}
189+
190+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
191+
const result: any = {};
192+
if (description) result.description = description;
193+
if (defaultValue !== undefined) result.default = defaultValue;
194+
195+
switch (def.typeName) {
196+
case 'ZodString':
197+
result.type = 'string';
198+
break;
199+
case 'ZodNumber':
200+
result.type = def.checks?.some((c: any) => c.kind === 'int')
201+
? 'integer'
202+
: 'number';
203+
break;
204+
case 'ZodBoolean':
205+
result.type = 'boolean';
206+
break;
207+
case 'ZodEnum':
208+
result.type = 'string';
209+
result.enum = def.values;
210+
break;
211+
case 'ZodArray':
212+
result.type = 'array';
213+
result.items = getZodTypeInfo(def.type);
214+
break;
215+
default:
216+
result.type = 'unknown';
217+
}
218+
return result;
219+
}
183220

221+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
222+
function isRequired(schema: any): boolean {
223+
let def = schema._def;
224+
while (def.typeName === 'ZodEffects') {
225+
schema = def.schema;
226+
def = schema._def;
227+
}
228+
return def.typeName !== 'ZodOptional' && def.typeName !== 'ZodDefault';
229+
}
230+
231+
async function generateToolDocumentation(): Promise<void> {
184232
try {
185-
// Connect to the server
186-
await client.connect(transport);
187-
console.log('Connected to MCP server');
233+
console.log('Generating tool documentation from definitions...');
234+
235+
// Convert ToolDefinitions to ToolWithAnnotations
236+
const toolsWithAnnotations: ToolWithAnnotations[] = tools.map(tool => {
237+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
238+
const properties: any = {};
239+
const required: string[] = [];
240+
241+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
242+
for (const [key, schema] of Object.entries(tool.schema as any)) {
243+
const info = getZodTypeInfo(schema);
244+
properties[key] = info;
245+
if (isRequired(schema)) {
246+
required.push(key);
247+
}
248+
}
249+
250+
return {
251+
name: tool.name,
252+
description: tool.description,
253+
inputSchema: {
254+
type: 'object',
255+
properties,
256+
required,
257+
},
258+
annotations: tool.annotations,
259+
};
260+
});
188261

189-
// List all available tools
190-
const {tools} = await client.listTools();
191-
const toolsWithAnnotations = tools as ToolWithAnnotations[];
192-
console.log(`Found ${tools.length} tools`);
262+
console.log(`Found ${toolsWithAnnotations.length} tools`);
193263

194264
// Generate markdown documentation
195265
let markdown = `<!-- AUTO GENERATED DO NOT EDIT - run 'npm run docs' to update-->
@@ -322,8 +392,6 @@ async function generateToolDocumentation(): Promise<void> {
322392
// Generate and update configuration options
323393
const optionsMarkdown = generateConfigOptionsMarkdown();
324394
updateReadmeWithOptionsMarkdown(optionsMarkdown);
325-
// Clean up
326-
await client.close();
327395
process.exit(0);
328396
} catch (error) {
329397
console.error('Error generating documentation:', error);

src/main.ts

Lines changed: 1 addition & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -21,16 +21,8 @@ import {
2121
SetLevelRequestSchema,
2222
} from './third_party/index.js';
2323
import {ToolCategory} from './tools/categories.js';
24-
import * as consoleTools from './tools/console.js';
25-
import * as emulationTools from './tools/emulation.js';
26-
import * as inputTools from './tools/input.js';
27-
import * as networkTools from './tools/network.js';
28-
import * as pagesTools from './tools/pages.js';
29-
import * as performanceTools from './tools/performance.js';
30-
import * as screenshotTools from './tools/screenshot.js';
31-
import * as scriptTools from './tools/script.js';
32-
import * as snapshotTools from './tools/snapshot.js';
3324
import type {ToolDefinition} from './tools/ToolDefinition.js';
25+
import {tools} from './tools/tools.js';
3426

3527
// If moved update release-please config
3628
// x-release-please-start-version
@@ -165,22 +157,6 @@ function registerTool(tool: ToolDefinition): void {
165157
);
166158
}
167159

168-
const tools = [
169-
...Object.values(consoleTools),
170-
...Object.values(emulationTools),
171-
...Object.values(inputTools),
172-
...Object.values(networkTools),
173-
...Object.values(pagesTools),
174-
...Object.values(performanceTools),
175-
...Object.values(screenshotTools),
176-
...Object.values(scriptTools),
177-
...Object.values(snapshotTools),
178-
] as ToolDefinition[];
179-
180-
tools.sort((a, b) => {
181-
return a.name.localeCompare(b.name);
182-
});
183-
184160
for (const tool of tools) {
185161
registerTool(tool);
186162
}

src/tools/tools.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
2+
/**
3+
* @license
4+
* Copyright 2025 Google LLC
5+
* SPDX-License-Identifier: Apache-2.0
6+
*/
7+
import * as consoleTools from './console.js';
8+
import * as emulationTools from './emulation.js';
9+
import * as inputTools from './input.js';
10+
import * as networkTools from './network.js';
11+
import * as pagesTools from './pages.js';
12+
import * as performanceTools from './performance.js';
13+
import * as screenshotTools from './screenshot.js';
14+
import * as scriptTools from './script.js';
15+
import * as snapshotTools from './snapshot.js';
16+
import type {ToolDefinition} from './ToolDefinition.js';
17+
18+
const tools = [
19+
...Object.values(consoleTools),
20+
...Object.values(emulationTools),
21+
...Object.values(inputTools),
22+
...Object.values(networkTools),
23+
...Object.values(pagesTools),
24+
...Object.values(performanceTools),
25+
...Object.values(screenshotTools),
26+
...Object.values(scriptTools),
27+
...Object.values(snapshotTools),
28+
] as ToolDefinition[];
29+
30+
tools.sort((a, b) => {
31+
return a.name.localeCompare(b.name);
32+
});
33+
34+
export {tools};

0 commit comments

Comments
 (0)