diff --git a/README.md b/README.md index 7d496fe6..7ef39932 100644 --- a/README.md +++ b/README.md @@ -234,6 +234,9 @@ Both flags are required because the MCP uses both WebDriver Classic (`--marionet ## Tool overview +See [docs/tools.md](docs/tools.md) for the full list of tools by module, with +descriptions and parameters (generated from the source). + - Pages: list/new/navigate/select/close/get_page_text (get_page_text supports optional `saveTo`) - Snapshot/UID: take/resolve/clear (take supports optional `saveTo`) - Input: click/hover/fill/drag/upload/form fill diff --git a/docs/tools.md b/docs/tools.md new file mode 100644 index 00000000..83e178de --- /dev/null +++ b/docs/tools.md @@ -0,0 +1,620 @@ + + +# Tool reference + +The server exposes 51 tools grouped into 16 modules. Which modules are +enabled depends on `--tool-preset` or `--tools`; see +[Tool modules and presets](../README.md#tool-modules-and-presets) in the README. + +Presets are cumulative and `basic` is the default. Privileged modules require the +Mozilla-internal build and `MOZ_REMOTE_ALLOW_SYSTEM_ACCESS=1`; the public package drops them. + +## Modules and presets + +| Module | Tools | slim | basic | developer | mozilla | all | +| ------------------------- | ----- | ---- | ----- | --------- | ------- | --- | +| `pages` | 6 | yes | yes | yes | yes | yes | +| `snapshot` | 3 | yes | yes | yes | yes | yes | +| `input` | 6 | yes | yes | yes | yes | yes | +| `network` | 2 | - | - | yes | yes | yes | +| `console` | 2 | - | - | yes | yes | yes | +| `screenshot` | 2 | yes | yes | yes | yes | yes | +| `downloads` | 3 | - | yes | yes | yes | yes | +| `utilities` | 4 | - | yes | yes | yes | yes | +| `management` | 3 | - | yes | yes | yes | yes | +| `webextension` | 2 | - | yes | yes | yes | yes | +| `profiler` | 3 | - | - | yes | yes | yes | +| `screencast` | 2 | - | yes | yes | yes | yes | +| `script` | 1 | - | yes | yes | yes | yes | +| `debugging` | 6 | - | - | yes | yes | yes | +| `prefs` (privileged) | 2 | - | - | - | yes | yes | +| `privileged` (privileged) | 4 | - | - | - | yes | yes | + +## Contents + +- [pages](#pages) +- [snapshot](#snapshot) +- [input](#input) +- [network](#network) +- [console](#console) +- [screenshot](#screenshot) +- [downloads](#downloads) +- [utilities](#utilities) +- [management](#management) +- [webextension](#webextension) +- [profiler](#profiler) +- [screencast](#screencast) +- [script](#script) +- [debugging](#debugging) +- [prefs](#prefs) +- [privileged](#privileged) + +## pages + +Open, navigate, select, and close pages. + +### `list_pages` + +_Read-only._ + +List open tabs (index, title, URL). Selected tab is marked. + +No parameters. + +### `new_page` + +Open new tab at URL. Returns tab index. + +Parameters: + +- `url` (string, required) - Target URL + +### `navigate_page` + +Navigate selected tab to URL. + +Parameters: + +- `url` (string, required) - Target URL + +### `select_page` + +Select active tab by index, URL, or title. Index takes precedence. + +Parameters: + +- `pageIdx` (number, optional) - Tab index (0-based, most reliable) +- `url` (string, optional) - URL substring (case-insensitive) +- `title` (string, optional) - Title substring (case-insensitive) + +### `close_page` + +Close tab by index. + +Parameters: + +- `pageIdx` (number, required) - Tab index to close + +### `get_page_text` + +_Read-only._ + +Get the visible text of the page (document.body.innerText). Caps at maxLength (default 20000 chars); saveTo saves the full text to a file. + +Parameters: + +- `maxLength` (number, optional) - Max characters to return inline (default: 20000). Ignored when saveTo is used. +- `saveTo` (boolean | string, optional) - Save the full untruncated text to a file instead of returning it inline. Pass a file path, an existing directory (generated file inside), or true (generated file under ~/.firefox-devtools-mcp/output/). Relative paths resolve against the current working directory. +- `preview` (number, optional) - Number of characters of the saved text to return inline as a preview when saveTo is used. Omit for no preview. + +## snapshot + +Capture accessibility/DOM snapshots and resolve UIDs. + +### `take_snapshot` + +_Read-only._ + +Capture DOM snapshot with stable UIDs. A UID stays valid across snapshots until its element is removed or the page navigates. Output caps at maxLines (default 100); scope with selector or dump the full tree with saveTo. + +Parameters: + +- `maxLines` (number, optional) - Max lines (default: 100) +- `includeAttributes` (boolean, optional) - Include ARIA attributes (default: false) +- `includeText` (boolean, optional) - Include text (default: true) +- `maxDepth` (number, optional) - Max tree depth +- `includeAll` (boolean, optional) - Include all visible elements without relevance filtering. Useful for Vue/Livewire apps (default: false) +- `selector` (string, optional) - CSS selector to scope snapshot to specific element (e.g., "#app") +- `saveTo` (boolean | string, optional) - Save the complete snapshot text to a file (ignores maxLines) instead of returning it inline. Pass a file path, an existing directory (generated file inside), or true (generated file under ~/.firefox-devtools-mcp/output/). Relative paths resolve against the current working directory. +- `preview` (number, optional) - Number of characters of the saved output to return inline as a preview when saveTo is used. Omit for no preview. + +### `resolve_uid_to_selector` + +_Read-only._ + +Resolve UID to CSS selector. Fails if the element is gone. + +Parameters: + +- `uid` (string, required) - UID from snapshot + +### `clear_snapshot` + +Clear snapshot UIDs. Usually not needed. + +No parameters. + +## input + +Interact with the page via UID-based clicks, typing, drag, and uploads. + +### `click_by_uid` + +Click element by UID. Set dblClick for double-click. + +Parameters: + +- `uid` (string, required) - Element UID from snapshot +- `dblClick` (boolean, optional) - Double-click (default: false) + +### `hover_by_uid` + +Hover over element by UID. + +Parameters: + +- `uid` (string, required) - Element UID from snapshot + +### `fill_by_uid` + +Fill text input/textarea by UID. + +Parameters: + +- `uid` (string, required) - Input element UID from snapshot +- `value` (string, required) - Text to fill + +### `drag_by_uid_to_uid` + +Drag element to another (HTML5 drag events). + +Parameters: + +- `fromUid` (string, required) - Source element UID +- `toUid` (string, required) - Target element UID + +### `fill_form_by_uid` + +Fill multiple form fields at once. + +Parameters: + +- `elements` (array of object, required) - Array of {uid, value} pairs + +### `upload_file_by_uid` + +Upload file to file input by UID. + +Parameters: + +- `uid` (string, required) - File input UID from snapshot +- `filePath` (string, required) - Local file path + +## network + +List and inspect network requests. + +### `list_network_requests` + +_Read-only._ + +List network requests, returning IDs for get_network_request. Filter by url/method/status; caps at limit (default 50); saveTo saves all matches to a file. + +Parameters: + +- `limit` (number, optional) - Max requests (default: 50) +- `sinceMs` (number, optional) - Only last N ms +- `urlContains` (string, optional) - URL filter (case-insensitive) +- `method` (string, optional) - HTTP method filter +- `status` (number, optional) - Exact status code +- `statusMin` (number, optional) - Min status code +- `statusMax` (number, optional) - Max status code +- `isXHR` (boolean, optional) - XHR/fetch only +- `resourceType` (string, optional) - Resource type filter +- `sortBy` (`timestamp` | `duration` | `status`, optional) - Sort field (default: timestamp) +- `detail` (`summary` | `min` | `full`, optional) - Detail level (default: summary) +- `format` (`text` | `json`, optional) - Output format (default: text) +- `saveTo` (boolean | string, optional) - Save matching requests to a file as JSON instead of returning them inline. Saves full untruncated headers by default; pass detail=summary or min for a lean form, or an explicit limit to cap how many are saved (default: all matching). Pass a file path, an existing directory (generated file inside), or true (generated file under ~/.firefox-devtools-mcp/output/). Relative paths resolve against the current working directory. +- `preview` (number, optional) - Number of characters of the saved output to return inline as a preview when saveTo is used. Omit for no preview. + +### `get_network_request` + +_Read-only._ + +Get request details by ID, including the response body (and request body when present). Large text bodies are truncated inline; binary bodies are summarized. URL lookup as fallback. + +Parameters: + +- `id` (string, optional) - Request ID from list_network_requests +- `url` (string, optional) - URL fallback (may match multiple) +- `format` (`text` | `json`, optional) - Output format (default: text) +- `saveTo` (boolean | string, optional) - Save the request details with full untruncated headers and bodies to a file as JSON instead of returning them inline (binary bodies are stored base64-encoded). Pass a file path, an existing directory (generated file inside), or true (generated file under ~/.firefox-devtools-mcp/output/). Relative paths resolve against the current working directory. +- `preview` (number, optional) - Number of characters of the saved output to return inline as a preview when saveTo is used. Omit for no preview. + +## console + +Read and clear console messages. + +### `list_console_messages` + +_Read-only._ + +List console messages, filterable by level, time, text, source. Caps at limit (default 50); saveTo saves all matches to a file. + +Parameters: + +- `level` (`debug` | `info` | `warn` | `error`, optional) - Filter by level +- `limit` (number, optional) - Max messages (default: 50) +- `sinceMs` (number, optional) - Only last N ms +- `textContains` (string, optional) - Text filter (case-insensitive) +- `source` (string, optional) - Filter by source +- `format` (`text` | `json`, optional) - Output format (default: text) +- `saveTo` (boolean | string, optional) - Save matching messages to a file in full (untruncated) instead of returning them inline. Saves all matching messages by default; pass an explicit limit to cap how many are saved. Pass a file path, an existing directory (generated file inside), or true (generated file under ~/.firefox-devtools-mcp/output/). Relative paths resolve against the current working directory. +- `preview` (number, optional) - Number of characters of the saved output to return inline as a preview when saveTo is used. Omit for no preview. + +### `clear_console_messages` + +Clear collected console messages. + +No parameters. + +## screenshot + +Capture screenshots of the page or specific elements. + +### `screenshot_page` + +_Read-only._ + +Capture page screenshot as base64 PNG. + +Parameters: + +- `saveTo` (boolean | string, optional) - Save the screenshot to a file instead of returning it as image data in the response. Pass a file path, an existing directory (generated file inside), or true (generated file under ~/.firefox-devtools-mcp/output/). Relative paths resolve against the current working directory. + +### `screenshot_by_uid` + +_Read-only._ + +Capture element screenshot by UID as base64 PNG. + +Parameters: + +- `uid` (string, required) - Element UID from snapshot +- `saveTo` (boolean | string, optional) - Save the screenshot to a file instead of returning it as image data in the response. Pass a file path, an existing directory (generated file inside), or true (generated file under ~/.firefox-devtools-mcp/output/). Relative paths resolve against the current working directory. + +## downloads + +Monitor and manage file downloads. + +### `list_downloads` + +_Read-only._ + +List downloads tracked since startup, including status and saved file path. + +Parameters: + +- `status` (`in_progress` | `complete` | `canceled`, optional) - Filter by status +- `urlContains` (string, optional) - URL filter (case-insensitive) +- `limit` (number, optional) - Max downloads (default: 50) +- `format` (`text` | `json`, optional) - Output format (default: text) + +### `clear_downloads` + +Clear the tracked downloads buffer. + +No parameters. + +### `set_download_behavior` + +Control how downloads are handled: allow (save silently to the default download directory), deny (cancel), or reset to default. Avoids the native save-file dialog. Requires a recent Firefox. + +Parameters: + +- `behavior` (`allowed` | `denied` | `default`, required) - 'allowed' saves downloads automatically, 'denied' cancels them, 'default' resets to the browser default + +## utilities + +Handle dialogs, history navigation, and viewport sizing. + +### `accept_dialog` + +Accept browser dialog. Provide promptText for prompts. + +Parameters: + +- `promptText` (string, optional) - Text for prompt dialogs + +### `dismiss_dialog` + +Dismiss browser dialog. + +No parameters. + +### `navigate_history` + +Navigate history back/forward. UIDs become stale. + +Parameters: + +- `direction` (`back` | `forward`, required) - back or forward + +### `set_viewport_size` + +Set viewport dimensions in pixels. + +Parameters: + +- `width` (number, required) - Width in pixels +- `height` (number, required) - Height in pixels + +## management + +Inspect Firefox info/output and restart the browser. + +### `get_firefox_output` + +_Read-only._ + +Retrieve Firefox output (stdout/stderr including MOZ_LOG, warnings, crashes, stack traces). Returns recent output from the capture file. Use filters to focus on specific content. + +Parameters: + +- `lines` (number, optional) - Number of recent log lines to return (default: 100, max: 10000) +- `grep` (string, optional) - Filter log lines containing this string (case-insensitive) +- `since` (number, optional) - Only show logs written in the last N seconds + +### `get_firefox_info` + +_Read-only._ + +Get information about the current Firefox instance configuration, including binary path, environment variables, and output file location. + +No parameters. + +### `restart_firefox` + +Restart Firefox with different configuration. Allows changing binary path, environment variables, and other options. All current tabs will be closed. + +Parameters: + +- `firefoxPath` (string, optional) - New Firefox binary path (optional, keeps current if not specified) +- `profilePath` (string, optional) - Firefox profile path (optional, keeps current if not specified) +- `env` (array of string, optional) - New environment variables in KEY=VALUE format (optional, e.g., ["MOZ_LOG=HTMLMediaElement:5", "MOZ_LOG_FILE=/tmp/ff.log"]) +- `headless` (boolean, optional) - Run in headless mode (optional, keeps current if not specified) +- `startUrl` (string, optional) - URL to navigate to after restart (optional, uses about:blank if not specified) +- `prefs` (object, optional) - Firefox preferences to set at startup. Values are auto-typed: true/false become booleans, integers become numbers, everything else is a string. Requires MOZ_REMOTE_ALLOW_SYSTEM_ACCESS=1. + +## webextension + +Install and uninstall web extensions. + +### `install_extension` + +Install a Firefox extension using WebDriver BiDi webExtension.install command. Supports installing from archive (.xpi/.zip), base64-encoded data, or unpacked directory. + +Parameters: + +- `type` (`archivePath` | `base64` | `path`, required) - Extension data type: "archivePath" for .xpi/.zip, "base64" for encoded data, "path" for unpacked directory +- `path` (string, optional) - File path (for archivePath or path types) +- `value` (string, optional) - Base64-encoded extension data (for base64 type) +- `permanent` (boolean, optional) - Firefox-specific: Install permanently (requires signed extension). Default: false (temporary install) + +### `uninstall_extension` + +Uninstall a Firefox extension using WebDriver BiDi webExtension.uninstall command. Requires the extension ID returned by install_extension or obtained from list_extensions. + +Parameters: + +- `id` (string, required) - Extension ID (e.g., "addon@example.com") + +## profiler + +Start, stop, and query the performance profiler. + +### `profiler_is_active` + +_Read-only._ + +Check whether the Firefox profiler is currently recording. + +No parameters. + +### `profiler_start` + +Start the Firefox profiler. Provide either a preset name or explicit recording options (entries, interval, features, threads). Cannot combine both. Valid presets: web-developer, firefox-platform, graphics, media, ml, networking, power, debug. + +Parameters: + +- `preset` (`web-developer` | `firefox-platform` | `graphics` | `media` | `ml` | `networking` | `power` | `debug`, optional) - Profiler preset name. Cannot be combined with entries, interval, features, or threads. +- `entries` (integer, optional) - Number of entries to keep in the sampling buffer. Required when no preset is given. +- `interval` (number, optional) - Sampling interval in milliseconds. Required when no preset is given. +- `features` (array of string, optional) - Profiler features to enable. Required when no preset is given. +- `threads` (array of string, optional) - Thread names to profile. Required when no preset is given. +- `activeContext` (string, optional) - Id of the top-level navigable to mark as the active tab in the profile. Does not restrict profiling to that tab. + +### `profiler_stop` + +Stop the Firefox profiler and save the recorded profile to a file in the downloads directory. Returns the path to the saved file, or null when nothing was saved. + +Parameters: + +- `discard` (boolean, optional) - If true, stop the profiler and discard the recording instead of saving it to disk. Defaults to false. + +## screencast + +Record screencasts of the page viewport (Firefox 154+). + +### `screencast_start` + +Start recording a screencast (video) of the current page viewport, saving the output to a file in the downloads directory. Returns a screencast id to pass to screencast_stop. Multiple recordings can run at once. + +Parameters: + +- `context` (string, optional) - Id of the top-level browsing context to record. Defaults to the currently selected page. +- `frameRate` (integer, optional) - Target frame rate of the recording, in frames per second. +- `width` (integer, optional) - Width of the recorded video in pixels. Defaults to the viewport width. +- `height` (integer, optional) - Height of the recorded video in pixels. Defaults to the viewport height. +- `mimeType` (string, optional) - MIME type of the output file. Defaults to "video/webm". + +### `screencast_stop` + +Stop an in-progress screencast recording started with screencast_start and finalize the video file. Returns the path to the saved file. + +Parameters: + +- `screencast` (string, optional) - Id of the screencast to stop, as returned by screencast_start. Optional when exactly one recording is active. + +## script + +Evaluate arbitrary JavaScript in the page context. + +### `evaluate_script` + +Run a JS function in the page and return its result. Prefer this for targeted reads (a value, text, computed style, whether an element exists) instead of a full take_snapshot. Use the UID interaction tools for clicking, typing, and filling. + +Parameters: + +- `function` (string, required) - JS function string, e.g. () => document.title +- `args` (array of object, optional) - UIDs to pass as function arguments +- `timeout` (number, optional) - Timeout in ms (default: 5000) +- `sandbox` (string, optional) - Evaluate in an isolated sandbox realm with this name instead of the page realm. The sandbox shares the page DOM and keeps the native built-ins even where the page overrode them. Page-defined globals and expandos are invisible from the sandbox, and vice-versa. The same name reuses the same sandbox across calls; omit to evaluate in the page realm. +- `saveTo` (boolean | string, optional) - Save the result to a file as JSON instead of returning it inline. Pass a file path, an existing directory (generated file inside), or true (generated file under ~/.firefox-devtools-mcp/output/). Relative paths resolve against the current working directory. +- `preview` (number, optional) - Number of characters of the saved result to return inline as a preview when saveTo is used. Omit for no preview. + +## debugging + +Inspect scripts and set logpoints (Firefox 153+). + +### `enable_debugger` + +Enable the JS debugger for the current page. Required before set_logpoint works. Requires Firefox 153+. + +No parameters. + +### `list_scripts` + +_Read-only._ + +List all JavaScript files currently loaded in the page. Requires enable_debugger to have been called. + +No parameters. + +### `get_script_source` + +_Read-only._ + +Get the source code of a JavaScript file loaded in the page. Requires enable_debugger to have been called. + +Parameters: + +- `scriptUrl` (string, required) - URL of the script to retrieve. + +### `set_logpoint` + +Set a logpoint at a specific location. When execution reaches that line, the expression is evaluated and the result is stored without pausing. Use get_logpoint_results to retrieve collected values. Requires enable_debugger to have been called. + +Parameters: + +- `url` (string, required) - URL of the script. +- `line` (number, required) - Line number (1-based). +- `expression` (string, required) - JavaScript expression to evaluate each time the logpoint is hit. + +### `remove_logpoint` + +Remove a previously set logpoint. + +Parameters: + +- `logpoint` (string, required) - Logpoint id returned by set_logpoint. + +### `get_logpoint_results` + +_Read-only._ + +Get the results collected by a logpoint since it was set. + +Parameters: + +- `logpoint` (string, required) - Logpoint id returned by set_logpoint. + +## prefs + +Get and set Firefox preferences. + +Privileged module: requires the Mozilla-internal build. + +### `get_firefox_prefs` + +_Read-only._ + +Get Firefox preference values via a privileged API. Requires MOZ_REMOTE_ALLOW_SYSTEM_ACCESS=1 env var. + +Parameters: + +- `names` (array of string, required) - Array of preference names to read + +### `set_firefox_prefs` + +Set Firefox preferences at runtime a privileged API. Requires MOZ_REMOTE_ALLOW_SYSTEM_ACCESS=1 env var. + +Parameters: + +- `prefs` (object, required) - Object mapping preference names to values. Values are auto-typed: true/false become booleans, integers become numbers, everything else is a string. + +## privileged + +Access privileged ("chrome") contexts and list extensions. + +Privileged module: requires the Mozilla-internal build. + +### `list_privileged_contexts` + +_Read-only._ + +List privileged (privileged) browsing contexts. Requires MOZ_REMOTE_ALLOW_SYSTEM_ACCESS=1 env var. Use restart_firefox with env parameter to enable. + +No parameters. + +### `select_privileged_context` + +Select a privileged browsing context by ID and set WebDriver Classic context to "chrome" . Requires MOZ_REMOTE_ALLOW_SYSTEM_ACCESS=1 env var. + +Parameters: + +- `contextId` (string, required) - Privileged browsing context ID from list_privileged_contexts + +### `evaluate_privileged_script` + +Execute JS function in a privileged (chrome) browsing context. Requires MOZ_REMOTE_ALLOW_SYSTEM_ACCESS=1 env var. Get context ids from list_privileged_contexts. + +Parameters: + +- `function` (string, required) - JS function string, e.g. () => Services.prefs.getBoolPref("foo") +- `context` (string, required) - Privileged browsing context ID from list_privileged_contexts +- `saveTo` (boolean | string, optional) - Save the result to a file as JSON instead of returning it inline. Pass a file path, an existing directory (generated file inside), or true (generated file under ~/.firefox-devtools-mcp/output/). Relative paths resolve against the current working directory. +- `preview` (number, optional) - Number of characters of the saved result to return inline as a preview when saveTo is used. Omit for no preview. + +### `list_extensions` + +_Read-only._ + +List installed Firefox extensions with UUIDs and background scripts. Requires MOZ_REMOTE_ALLOW_SYSTEM_ACCESS=1 env var. + +Parameters: + +- `ids` (array of string, optional) - Optional: Filter by exact extension IDs (e.g., ["addon@example.com"]) +- `name` (string, optional) - Optional: Filter by partial name match (case-insensitive, e.g., "shopify") +- `isActive` (boolean, optional) - Optional: Filter by enabled (true) or disabled (false) status +- `isSystem` (boolean, optional) - Optional: Filter by system/built-in (true) or user-installed (false) extensions diff --git a/package.json b/package.json index c39b53bf..c7beff69 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "publish:moz": "node scripts/publish-moz-package.mjs", "start": "node dist/index.js", "setup": "node scripts/setup-mcp-config.js", + "docs:tools": "tsx scripts/generate-tools-doc.ts", "clean": "rm -rf dist", "typecheck": "tsc", "typecheck:tests": "tsc -p tests/tsconfig.json", diff --git a/scripts/generate-tools-doc.ts b/scripts/generate-tools-doc.ts new file mode 100644 index 00000000..e4b26234 --- /dev/null +++ b/scripts/generate-tools-doc.ts @@ -0,0 +1,146 @@ +/** + * Generates docs/tools.md from the tool module catalog in src/tools. + * + * The doc is fully derived from the ToolDefinition objects (names, descriptions, + * input schemas) and the preset table, so it cannot drift from the registry. + * Run `npm run docs:tools` after changing tool definitions; `--check` exits + * non-zero when the checked-in file is stale (used by tests/tools/doc.test.ts). + */ + +import { readFileSync, writeFileSync } from 'node:fs'; +import { fileURLToPath } from 'node:url'; +import { resolve } from 'node:path'; +import { MODULES, PRESETS, PRESET_NAMES, DEFAULT_PRESET } from '../src/tools/index.js'; +import type { ToolDefinition } from '../src/tools/module.js'; + +export const DOC_PATH = fileURLToPath(new URL('../docs/tools.md', import.meta.url)); + +interface PropertySchema { + type?: string | string[]; + description?: string; + enum?: unknown[]; + items?: PropertySchema; +} + +function inline(text: string): string { + return text.replace(/\s+/g, ' ').trim(); +} + +/** Renders a JSON Schema property as a short type label, e.g. `array of string`. */ +function formatType(schema: PropertySchema): string { + if (Array.isArray(schema.enum) && schema.enum.length > 0) { + return schema.enum.map((value) => `\`${String(value)}\``).join(' | '); + } + const type = Array.isArray(schema.type) ? schema.type.join(' | ') : schema.type; + if (type === 'array' && schema.items) { + return `array of ${formatType(schema.items)}`; + } + return type ?? 'any'; +} + +function renderTool(definition: ToolDefinition): string[] { + const lines: string[] = [`### \`${definition.name}\``, '']; + if (definition.annotations?.readOnlyHint === true) { + lines.push('_Read-only._', ''); + } + lines.push(inline(definition.description), ''); + + const schema = definition.inputSchema as { + properties?: Record; + required?: string[]; + }; + const properties = Object.entries(schema.properties ?? {}); + if (properties.length === 0) { + lines.push('No parameters.', ''); + return lines; + } + + const required = new Set(schema.required ?? []); + lines.push('Parameters:', ''); + for (const [name, property] of properties) { + const flag = required.has(name) ? 'required' : 'optional'; + const description = property.description ? ` - ${inline(property.description)}` : ''; + lines.push(`- \`${name}\` (${formatType(property)}, ${flag})${description}`); + } + lines.push(''); + return lines; +} + +/** Renders a markdown table with padded cells, matching prettier's output. */ +function renderTable(header: string[], rows: string[][]): string[] { + const widths = header.map((cell, index) => + Math.max(cell.length, ...rows.map((row) => row[index]?.length ?? 0)) + ); + const line = (cells: string[]) => + `| ${cells.map((cell, index) => cell.padEnd(widths[index] ?? 0)).join(' | ')} |`; + return [ + line(header), + `| ${widths.map((width) => '-'.repeat(Math.max(width, 3))).join(' | ')} |`, + ...rows.map(line), + ]; +} + +function renderPresetTable(): string[] { + const rows = MODULES.map((module) => [ + module.privileged ? `\`${module.name}\` (privileged)` : `\`${module.name}\``, + String(module.tools.length), + ...PRESET_NAMES.map((preset) => (PRESETS[preset]?.includes(module.name) ? 'yes' : '-')), + ]); + return renderTable(['Module', 'Tools', ...PRESET_NAMES], rows); +} + +export function renderToolsDoc(): string { + const toolCount = MODULES.reduce((total, module) => total + module.tools.length, 0); + const lines: string[] = [ + '', + '', + '# Tool reference', + '', + `The server exposes ${toolCount} tools grouped into ${MODULES.length} modules. Which modules are`, + 'enabled depends on `--tool-preset` or `--tools`; see', + '[Tool modules and presets](../README.md#tool-modules-and-presets) in the README.', + '', + `Presets are cumulative and \`${DEFAULT_PRESET}\` is the default. Privileged modules require the`, + 'Mozilla-internal build and `MOZ_REMOTE_ALLOW_SYSTEM_ACCESS=1`; the public package drops them.', + '', + '## Modules and presets', + '', + ...renderPresetTable(), + '', + '## Contents', + '', + ...MODULES.map((module) => `- [${module.name}](#${module.name})`), + '', + ]; + + for (const module of MODULES) { + lines.push(`## ${module.name}`, '', inline(module.description), ''); + if (module.privileged) { + lines.push('Privileged module: requires the Mozilla-internal build.', ''); + } + for (const { definition } of module.tools) { + lines.push(...renderTool(definition)); + } + } + + return `${lines.join('\n').trimEnd()}\n`; +} + +function main(): void { + const content = renderToolsDoc(); + if (process.argv.includes('--check')) { + const current = readFileSync(DOC_PATH, 'utf8'); + if (current !== content) { + console.error('docs/tools.md is out of date. Run: npm run docs:tools'); + process.exit(1); + } + console.log('docs/tools.md is up to date.'); + return; + } + writeFileSync(DOC_PATH, content); + console.log(`Wrote ${DOC_PATH}`); +} + +if (process.argv[1] && fileURLToPath(import.meta.url) === resolve(process.argv[1])) { + main(); +} diff --git a/tests/tools/doc.test.ts b/tests/tools/doc.test.ts new file mode 100644 index 00000000..8a89446b --- /dev/null +++ b/tests/tools/doc.test.ts @@ -0,0 +1,14 @@ +/** + * Guards docs/tools.md against drift from the tool registry. + */ + +import { describe, it, expect } from 'vitest'; +import { readFileSync } from 'node:fs'; +import { renderToolsDoc, DOC_PATH } from '../../scripts/generate-tools-doc.js'; + +describe('docs/tools.md', () => { + it('matches the generated output', () => { + const current = readFileSync(DOC_PATH, 'utf8'); + expect(current, 'docs/tools.md is out of date. Run: npm run docs:tools').toBe(renderToolsDoc()); + }); +});