diff --git a/scripts/generate-docs.ts b/scripts/generate-docs.ts index 31f2d0997..de67fcab5 100644 --- a/scripts/generate-docs.ts +++ b/scripts/generate-docs.ts @@ -12,9 +12,9 @@ import type {Tool} from '@modelcontextprotocol/sdk/types.js'; import {get_encoding} from 'tiktoken'; import {cliOptions} from '../build/src/cli.js'; +import type {ParsedArguments} from '../build/src/cli.js'; import {ToolCategory, labels} from '../build/src/tools/categories.js'; -import {tools as slimTools} from '../build/src/tools/slim/tools.js'; -import {tools} from '../build/src/tools/tools.js'; +import {createTools} from '../build/src/tools/tools.js'; const OUTPUT_PATH = './docs/tool-reference.md'; const SLIM_OUTPUT_PATH = './docs/slim-tool-reference.md'; @@ -504,7 +504,7 @@ async function generateToolDocumentation(): Promise { { const {toolsWithAnnotations, categories, sortedCategories} = - getToolsAndCategories(tools); + getToolsAndCategories(createTools({slim: false} as ParsedArguments)); await generateReference( 'Chrome DevTools MCP Tool Reference', OUTPUT_PATH, @@ -521,7 +521,7 @@ async function generateToolDocumentation(): Promise { { const {toolsWithAnnotations, categories, sortedCategories} = - getToolsAndCategories(slimTools); + getToolsAndCategories(createTools({slim: true} as ParsedArguments)); await generateReference( 'Chrome DevTools MCP Slim Tool Reference', SLIM_OUTPUT_PATH, diff --git a/src/cli.ts b/src/cli.ts index 6019d816f..c585ea4c4 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -243,6 +243,8 @@ export const cliOptions = { }, } satisfies Record; +export type ParsedArguments = ReturnType; + export function parseArguments(version: string, argv = process.argv) { const yargsInstance = yargs(hideBin(argv)) .scriptName('npx chrome-devtools-mcp@latest') diff --git a/src/main.ts b/src/main.ts index 250363daa..a8f9b5f2f 100644 --- a/src/main.ts +++ b/src/main.ts @@ -27,9 +27,8 @@ import { SetLevelRequestSchema, } from './third_party/index.js'; import {ToolCategory} from './tools/categories.js'; -import {tools as slimTools} from './tools/slim/tools.js'; import type {ToolDefinition} from './tools/ToolDefinition.js'; -import {tools} from './tools/tools.js'; +import {createTools} from './tools/tools.js'; import {VERSION} from './version.js'; export const args = parseArguments(VERSION); @@ -256,7 +255,8 @@ function registerTool(tool: ToolDefinition): void { ); } -for (const tool of args.slim ? slimTools : tools) { +const tools = createTools(args); +for (const tool of tools) { registerTool(tool); } diff --git a/src/tools/ToolDefinition.ts b/src/tools/ToolDefinition.ts index eccbe7037..70bfbfcbe 100644 --- a/src/tools/ToolDefinition.ts +++ b/src/tools/ToolDefinition.ts @@ -4,6 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ +import type {ParsedArguments} from '../cli.js'; import type {TextSnapshotNode, GeolocationOptions} from '../McpContext.js'; import {zod} from '../third_party/index.js'; import type { @@ -157,6 +158,20 @@ export type Context = Readonly<{ export function defineTool( definition: ToolDefinition, +): ToolDefinition; + +export function defineTool< + Schema extends zod.ZodRawShape, + Args extends ParsedArguments = ParsedArguments, +>( + definition: (args: Args) => ToolDefinition, +): (args: Args) => ToolDefinition; + +export function defineTool< + Schema extends zod.ZodRawShape, + Args extends ParsedArguments = ParsedArguments, +>( + definition: ToolDefinition | ((args: Args) => ToolDefinition), ) { return definition; } diff --git a/src/tools/slim/tools.ts b/src/tools/slim/tools.ts index c3465def6..55c5d3aa8 100644 --- a/src/tools/slim/tools.ts +++ b/src/tools/slim/tools.ts @@ -7,7 +7,6 @@ import type {Dialog} from '../../third_party/index.js'; import {zod} from '../../third_party/index.js'; import {ToolCategory} from '../categories.js'; -import type {ToolDefinition} from '../ToolDefinition.js'; import {defineTool} from '../ToolDefinition.js'; export const screenshot = defineTool({ @@ -86,5 +85,3 @@ export const evaluate = defineTool({ } }, }); - -export const tools = [screenshot, evaluate, navigate] as ToolDefinition[]; diff --git a/src/tools/tools.ts b/src/tools/tools.ts index 96bb033a3..8fb8659d9 100644 --- a/src/tools/tools.ts +++ b/src/tools/tools.ts @@ -4,6 +4,8 @@ * SPDX-License-Identifier: Apache-2.0 */ +import type {ParsedArguments} from '../cli.js'; + import * as consoleTools from './console.js'; import * as emulationTools from './emulation.js'; import * as extensionTools from './extensions.js'; @@ -15,26 +17,41 @@ import * as performanceTools from './performance.js'; import * as screencastTools from './screencast.js'; import * as screenshotTools from './screenshot.js'; import * as scriptTools from './script.js'; +import * as slimTools from './slim/tools.js'; import * as snapshotTools from './snapshot.js'; import type {ToolDefinition} from './ToolDefinition.js'; -const tools = [ - ...Object.values(consoleTools), - ...Object.values(emulationTools), - ...Object.values(extensionTools), - ...Object.values(inputTools), - ...Object.values(memoryTools), - ...Object.values(networkTools), - ...Object.values(pagesTools), - ...Object.values(performanceTools), - ...Object.values(screencastTools), - ...Object.values(screenshotTools), - ...Object.values(scriptTools), - ...Object.values(snapshotTools), -] as ToolDefinition[]; +export const createTools = (args: ParsedArguments) => { + const rawTools = args.slim + ? Object.values(slimTools) + : [ + ...Object.values(consoleTools), + ...Object.values(emulationTools), + ...Object.values(extensionTools), + ...Object.values(inputTools), + ...Object.values(memoryTools), + ...Object.values(networkTools), + ...Object.values(pagesTools), + ...Object.values(performanceTools), + ...Object.values(screencastTools), + ...Object.values(screenshotTools), + ...Object.values(scriptTools), + ...Object.values(snapshotTools), + ]; + + const tools: ToolDefinition[] = []; + for (const tool of rawTools) { + if (typeof tool === 'function') { + // @ts-expect-error none of the tools for now implement the function type tool has type "never" + tools.push(tool(args) as ToolDefinition); + } else { + tools.push(tool as ToolDefinition); + } + } -tools.sort((a, b) => { - return a.name.localeCompare(b.name); -}); + tools.sort((a, b) => { + return a.name.localeCompare(b.name); + }); -export {tools}; + return tools; +}; diff --git a/tests/index.test.ts b/tests/index.test.ts index dc653cd84..ed1d75955 100644 --- a/tests/index.test.ts +++ b/tests/index.test.ts @@ -92,16 +92,35 @@ describe('e2e', () => { const files = fs.readdirSync('build/src/tools'); const definedNames = []; for (const file of files) { - if (file === 'ToolDefinition.js' || file === 'slim') { + if ( + file === 'ToolDefinition.js' || + file === 'tools.js' || + file === 'slim' + ) { continue; } const fileTools = await import(`../src/tools/${file}`); - for (const maybeTool of Object.values(fileTools)) { - if ('name' in maybeTool) { - if (maybeTool.annotations?.conditions) { + for (const maybeTool of Object.values(fileTools)) { + if (typeof maybeTool === 'function') { + const tool = (maybeTool as (val: boolean) => ToolDefinition)(false); + if (tool && typeof tool === 'object' && 'name' in tool) { + if (tool.annotations?.conditions) { + continue; + } + definedNames.push(tool.name); + } + continue; + } + if ( + typeof maybeTool === 'object' && + maybeTool !== null && + 'name' in maybeTool + ) { + const tool = maybeTool as ToolDefinition; + if (tool.annotations?.conditions) { continue; } - definedNames.push(maybeTool.name); + definedNames.push(tool.name); } } }