From 134ce85ab65c4c4d19a0ffa7ff8de965062f4b5f Mon Sep 17 00:00:00 2001 From: f3tch Date: Sat, 22 Aug 2026 21:50:09 +0500 Subject: [PATCH 1/3] chore: improve tool input schema typing --- src/tools/module.ts | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/tools/module.ts b/src/tools/module.ts index 910b8cce..1b4d8351 100644 --- a/src/tools/module.ts +++ b/src/tools/module.ts @@ -8,11 +8,29 @@ import type { McpToolResponse } from '../types/common.js'; +export interface JsonSchemaProperty { + type: string | readonly string[]; + description?: string; + enum?: readonly string[]; + /** Schema of array elements (when type is 'array'). */ + items?: JsonSchemaProperty; + /** Schema for values of an open-key object (prefs-style maps). */ + additionalProperties?: { oneOf: Array<{ type: string }> }; + properties?: Record; + required?: readonly string[]; +} + +export interface InputSchema { + type: string; + properties: Record; + required?: readonly string[]; +} + export interface ToolDefinition { name: string; description: string; annotations?: { readOnlyHint?: boolean; [key: string]: unknown }; - inputSchema: Record; + inputSchema: InputSchema; } export type ToolHandler = (input: unknown) => Promise; From 1933f5839159a4d23e951962e30a40ada3a8cbd3 Mon Sep 17 00:00:00 2001 From: f3tch Date: Wed, 26 Aug 2026 01:55:51 +0500 Subject: [PATCH 2/3] refactor: narrow tool schema type values --- src/tools/module.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/tools/module.ts b/src/tools/module.ts index 1b4d8351..4811af30 100644 --- a/src/tools/module.ts +++ b/src/tools/module.ts @@ -8,20 +8,22 @@ import type { McpToolResponse } from '../types/common.js'; +export type JsonSchemaType = 'array' | 'boolean' | 'integer' | 'number' | 'object' | 'string'; + export interface JsonSchemaProperty { - type: string | readonly string[]; + type: JsonSchemaType | readonly JsonSchemaType[]; description?: string; enum?: readonly string[]; /** Schema of array elements (when type is 'array'). */ items?: JsonSchemaProperty; /** Schema for values of an open-key object (prefs-style maps). */ - additionalProperties?: { oneOf: Array<{ type: string }> }; + additionalProperties?: { oneOf: Array<{ type: JsonSchemaType }> }; properties?: Record; required?: readonly string[]; } export interface InputSchema { - type: string; + type: 'object'; properties: Record; required?: readonly string[]; } From 9b55067f0088605b2d4b4a93fca7cd715dca474f Mon Sep 17 00:00:00 2001 From: f3tch Date: Wed, 26 Aug 2026 02:00:08 +0500 Subject: [PATCH 3/3] refactor: check tool definitions against the schema --- src/tools/console.ts | 6 +++--- src/tools/debugging.ts | 14 +++++++------- src/tools/downloads.ts | 14 +++++++------- src/tools/firefox-management.ts | 8 ++++---- src/tools/firefox-prefs.ts | 6 +++--- src/tools/input.ts | 14 +++++++------- src/tools/network.ts | 10 +++++----- src/tools/pages.ts | 18 +++++++++--------- src/tools/privileged-context.ts | 8 ++++---- src/tools/profiler.ts | 8 ++++---- src/tools/screencast.ts | 6 +++--- src/tools/screenshot.ts | 6 +++--- src/tools/script.ts | 4 ++-- src/tools/snapshot.ts | 8 ++++---- src/tools/utilities.ts | 10 +++++----- src/tools/webextension.ts | 8 ++++---- 16 files changed, 74 insertions(+), 74 deletions(-) diff --git a/src/tools/console.ts b/src/tools/console.ts index 13630ffe..59b2c9dc 100644 --- a/src/tools/console.ts +++ b/src/tools/console.ts @@ -12,7 +12,7 @@ import { truncationFooter, } from '../utils/response-helpers.js'; import { saveOutput } from '../utils/save-output.js'; -import { defineModule } from './module.js'; +import { defineModule, type ToolDefinition } from './module.js'; import type { McpToolResponse } from '../types/common.js'; export const listConsoleMessagesTool = { @@ -63,7 +63,7 @@ export const listConsoleMessagesTool = { }, }, }, -}; +} satisfies ToolDefinition; export const clearConsoleMessagesTool = { name: 'clear_console_messages', @@ -75,7 +75,7 @@ export const clearConsoleMessagesTool = { type: 'object', properties: {}, }, -}; +} satisfies ToolDefinition; const DEFAULT_LIMIT = 50; diff --git a/src/tools/debugging.ts b/src/tools/debugging.ts index 8cba07cb..62e03e3c 100644 --- a/src/tools/debugging.ts +++ b/src/tools/debugging.ts @@ -1,7 +1,7 @@ import { successResponse, errorResponse } from '../utils/response-helpers.js'; import { compareVersions } from '../utils/version.js'; import { remoteValueToNative } from '../utils/remote-value.js'; -import { defineModule } from './module.js'; +import { defineModule, type ToolDefinition } from './module.js'; import type { McpToolResponse } from '../types/common.js'; const MIN_VERSION = '153'; @@ -34,7 +34,7 @@ export const enableDebuggerTool = { readOnlyHint: false, }, inputSchema: { type: 'object', properties: {} }, -}; +} satisfies ToolDefinition; export const listScriptsTool = { name: 'list_scripts', @@ -44,7 +44,7 @@ export const listScriptsTool = { readOnlyHint: true, }, inputSchema: { type: 'object', properties: {} }, -}; +} satisfies ToolDefinition; export const getScriptSourceTool = { name: 'get_script_source', @@ -60,7 +60,7 @@ export const getScriptSourceTool = { }, required: ['scriptUrl'], }, -}; +} satisfies ToolDefinition; export const setLogpointTool = { name: 'set_logpoint', @@ -81,7 +81,7 @@ export const setLogpointTool = { }, required: ['url', 'line', 'expression'], }, -}; +} satisfies ToolDefinition; export const removeLogpointTool = { name: 'remove_logpoint', @@ -96,7 +96,7 @@ export const removeLogpointTool = { }, required: ['logpoint'], }, -}; +} satisfies ToolDefinition; export const getLogpointResultsTool = { name: 'get_logpoint_results', @@ -111,7 +111,7 @@ export const getLogpointResultsTool = { }, required: ['logpoint'], }, -}; +} satisfies ToolDefinition; // ============================================================================ // Handlers diff --git a/src/tools/downloads.ts b/src/tools/downloads.ts index f3203ed6..e8ab7028 100644 --- a/src/tools/downloads.ts +++ b/src/tools/downloads.ts @@ -4,7 +4,7 @@ */ import { successResponse, errorResponse, jsonResponse } from '../utils/response-helpers.js'; -import { defineModule } from './module.js'; +import { defineModule, type ToolDefinition } from './module.js'; import type { McpToolResponse } from '../types/common.js'; // Tool definitions @@ -15,7 +15,7 @@ export const listDownloadsTool = { readOnlyHint: true, }, inputSchema: { - type: 'object' as const, + type: 'object', properties: { status: { type: 'string', @@ -37,23 +37,23 @@ export const listDownloadsTool = { }, }, }, -}; +} satisfies ToolDefinition; export const clearDownloadsTool = { name: 'clear_downloads', description: 'Clear the tracked downloads buffer.', inputSchema: { - type: 'object' as const, + type: 'object', properties: {}, }, -}; +} satisfies ToolDefinition; export const setDownloadBehaviorTool = { name: 'set_download_behavior', description: '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.', inputSchema: { - type: 'object' as const, + type: 'object', properties: { behavior: { type: 'string', @@ -64,7 +64,7 @@ export const setDownloadBehaviorTool = { }, required: ['behavior'], }, -}; +} satisfies ToolDefinition; // Tool handlers export async function handleListDownloads(args: unknown): Promise { diff --git a/src/tools/firefox-management.ts b/src/tools/firefox-management.ts index 5950fec2..e8310db0 100644 --- a/src/tools/firefox-management.ts +++ b/src/tools/firefox-management.ts @@ -5,7 +5,7 @@ import { readFileSync, existsSync, statSync } from 'node:fs'; import { errorResponse, successResponse } from '../utils/response-helpers.js'; -import { defineModule } from './module.js'; +import { defineModule, type ToolDefinition } from './module.js'; // ============================================================================ // Tool: get_firefox_logs @@ -35,7 +35,7 @@ export const getFirefoxLogsTool = { }, }, }, -}; +} satisfies ToolDefinition; export async function handleGetFirefoxLogs(input: unknown) { try { @@ -121,7 +121,7 @@ export const getFirefoxInfoTool = { type: 'object', properties: {}, }, -}; +} satisfies ToolDefinition; export async function handleGetFirefoxInfo(_input: unknown) { try { @@ -239,7 +239,7 @@ export const restartFirefoxTool = { }, }, }, -}; +} satisfies ToolDefinition; export async function handleRestartFirefox(input: unknown) { try { diff --git a/src/tools/firefox-prefs.ts b/src/tools/firefox-prefs.ts index 2787e643..e5412903 100644 --- a/src/tools/firefox-prefs.ts +++ b/src/tools/firefox-prefs.ts @@ -6,7 +6,7 @@ import { successResponse, errorResponse } from '../utils/response-helpers.js'; import { generatePrefScript } from '../firefox/pref-utils.js'; -import { defineModule } from './module.js'; +import { defineModule, type ToolDefinition } from './module.js'; import type { McpToolResponse } from '../types/common.js'; // ============================================================================ @@ -34,7 +34,7 @@ export const setFirefoxPrefsTool = { }, required: ['prefs'], }, -}; +} satisfies ToolDefinition; export async function handleSetFirefoxPrefs(args: unknown): Promise { try { @@ -145,7 +145,7 @@ export const getFirefoxPrefsTool = { }, required: ['names'], }, -}; +} satisfies ToolDefinition; export async function handleGetFirefoxPrefs(args: unknown): Promise { try { diff --git a/src/tools/input.ts b/src/tools/input.ts index 37ff43e1..1e235c54 100644 --- a/src/tools/input.ts +++ b/src/tools/input.ts @@ -5,7 +5,7 @@ import { successResponse, errorResponse } from '../utils/response-helpers.js'; import { handleUidError } from '../utils/uid-helpers.js'; -import { defineModule } from './module.js'; +import { defineModule, type ToolDefinition } from './module.js'; import type { McpToolResponse } from '../types/common.js'; // Tool definitions @@ -29,7 +29,7 @@ export const clickByUidTool = { }, required: ['uid'], }, -}; +} satisfies ToolDefinition; export const hoverByUidTool = { name: 'hover_by_uid', @@ -47,7 +47,7 @@ export const hoverByUidTool = { }, required: ['uid'], }, -}; +} satisfies ToolDefinition; export const fillByUidTool = { name: 'fill_by_uid', @@ -69,7 +69,7 @@ export const fillByUidTool = { }, required: ['uid', 'value'], }, -}; +} satisfies ToolDefinition; export const dragByUidToUidTool = { name: 'drag_by_uid_to_uid', @@ -91,7 +91,7 @@ export const dragByUidToUidTool = { }, required: ['fromUid', 'toUid'], }, -}; +} satisfies ToolDefinition; export const fillFormByUidTool = { name: 'fill_form_by_uid', @@ -123,7 +123,7 @@ export const fillFormByUidTool = { }, required: ['elements'], }, -}; +} satisfies ToolDefinition; export const uploadFileByUidTool = { name: 'upload_file_by_uid', @@ -145,7 +145,7 @@ export const uploadFileByUidTool = { }, required: ['uid', 'filePath'], }, -}; +} satisfies ToolDefinition; // Handlers export async function handleClickByUid(args: unknown): Promise { diff --git a/src/tools/network.ts b/src/tools/network.ts index 12841fc7..aed63186 100644 --- a/src/tools/network.ts +++ b/src/tools/network.ts @@ -14,7 +14,7 @@ import { TOKEN_LIMITS, } from '../utils/response-helpers.js'; import { saveOutput } from '../utils/save-output.js'; -import { defineModule } from './module.js'; +import { defineModule, type ToolDefinition } from './module.js'; import type { McpToolResponse } from '../types/common.js'; import type { NetworkBodyResult } from '../firefox/events/network.js'; @@ -27,7 +27,7 @@ export const listNetworkRequestsTool = { readOnlyHint: true, }, inputSchema: { - type: 'object' as const, + type: 'object', properties: { limit: { type: 'number', @@ -92,7 +92,7 @@ export const listNetworkRequestsTool = { }, }, }, -}; +} satisfies ToolDefinition; export const getNetworkRequestTool = { name: 'get_network_request', @@ -102,7 +102,7 @@ export const getNetworkRequestTool = { readOnlyHint: true, }, inputSchema: { - type: 'object' as const, + type: 'object', properties: { id: { type: 'string', @@ -129,7 +129,7 @@ export const getNetworkRequestTool = { }, }, }, -}; +} satisfies ToolDefinition; /** * Fetch a body without letting a missing facade method or transport error fail diff --git a/src/tools/pages.ts b/src/tools/pages.ts index ebc4b4d4..2ece6e16 100644 --- a/src/tools/pages.ts +++ b/src/tools/pages.ts @@ -9,8 +9,8 @@ import { truncationFooter, } from '../utils/response-helpers.js'; import { saveOutput } from '../utils/save-output.js'; -import { defineModule } from './module.js'; import { READINESS_STATES, isReadinessState, type ReadinessState } from '../firefox/pages.js'; +import { defineModule, type JsonSchemaProperty, type ToolDefinition } from './module.js'; import type { McpToolResponse } from '../types/common.js'; const DEFAULT_MAX_CONTENT_CHARS = 20_000; @@ -25,7 +25,7 @@ const waitSchema = { type: 'string', enum: [...READINESS_STATES], description: WAIT_DESCRIPTION, -}; +} satisfies JsonSchemaProperty; /** * Validate the optional `wait` argument. @@ -61,7 +61,7 @@ export const listPagesTool = { type: 'object', properties: {}, }, -}; +} satisfies ToolDefinition; export const newPageTool = { name: 'new_page', @@ -80,7 +80,7 @@ export const newPageTool = { }, required: ['url'], }, -}; +} satisfies ToolDefinition; export const navigatePageTool = { name: 'navigate_page', @@ -99,7 +99,7 @@ export const navigatePageTool = { }, required: ['url'], }, -}; +} satisfies ToolDefinition; export const selectPageTool = { name: 'select_page', @@ -125,7 +125,7 @@ export const selectPageTool = { }, required: [], }, -}; +} satisfies ToolDefinition; export const closePageTool = { name: 'close_page', @@ -143,7 +143,7 @@ export const closePageTool = { }, required: ['pageIdx'], }, -}; +} satisfies ToolDefinition; export const getPageTextTool = { name: 'get_page_text', @@ -153,7 +153,7 @@ export const getPageTextTool = { readOnlyHint: true, }, inputSchema: { - type: 'object' as const, + type: 'object', properties: { maxLength: { type: 'number', @@ -172,7 +172,7 @@ export const getPageTextTool = { }, }, }, -}; +} satisfies ToolDefinition; /** * Format page list compactly diff --git a/src/tools/privileged-context.ts b/src/tools/privileged-context.ts index 5ad1c02f..325f8e0e 100644 --- a/src/tools/privileged-context.ts +++ b/src/tools/privileged-context.ts @@ -7,7 +7,7 @@ import { successResponse, errorResponse, previewExcerpt } from '../utils/respons import { validateFunction } from '../utils/js-validation.js'; import { remoteValueToNative } from '../utils/remote-value.js'; import { saveOutput } from '../utils/save-output.js'; -import { defineModule } from './module.js'; +import { defineModule, type ToolDefinition } from './module.js'; // list_extensions lives with the other extension tools in webextension.ts, but // it needs parent access (AddonManager), so it is registered here under the // privileged module rather than the unprivileged webextension module. @@ -25,7 +25,7 @@ export const listPrivilegedContextsTool = { type: 'object', properties: {}, }, -}; +} satisfies ToolDefinition; export const selectPrivilegedContextTool = { name: 'select_privileged_context', @@ -44,7 +44,7 @@ export const selectPrivilegedContextTool = { }, required: ['contextId'], }, -}; +} satisfies ToolDefinition; export const evaluatePrivilegedScriptTool = { name: 'evaluate_privileged_script', @@ -77,7 +77,7 @@ export const evaluatePrivilegedScriptTool = { }, required: ['function', 'context'], }, -}; +} satisfies ToolDefinition; function formatContextList(contexts: any[]): string { if (contexts.length === 0) { diff --git a/src/tools/profiler.ts b/src/tools/profiler.ts index 5992eddd..986efcf4 100644 --- a/src/tools/profiler.ts +++ b/src/tools/profiler.ts @@ -1,7 +1,7 @@ import { successResponse, errorResponse } from '../utils/response-helpers.js'; import { compareVersions } from '../utils/version.js'; import type { FirefoxDevTools } from '../firefox/index.js'; -import { defineModule } from './module.js'; +import { defineModule, type ToolDefinition } from './module.js'; import type { McpToolResponse } from '../types/common.js'; const MIN_FIREFOX_VERSION = '154.0'; @@ -40,7 +40,7 @@ export const profilerIsActiveTool = { type: 'object', properties: {}, }, -}; +} satisfies ToolDefinition; export async function handleProfilerIsActive(_args: unknown): Promise { try { @@ -101,7 +101,7 @@ export const profilerStartTool = { }, }, }, -}; +} satisfies ToolDefinition; export async function handleProfilerStart(args: unknown): Promise { try { @@ -172,7 +172,7 @@ export const profilerStopTool = { }, }, }, -}; +} satisfies ToolDefinition; export async function handleProfilerStop(args: unknown): Promise { try { diff --git a/src/tools/screencast.ts b/src/tools/screencast.ts index cc289217..dd83eac3 100644 --- a/src/tools/screencast.ts +++ b/src/tools/screencast.ts @@ -1,7 +1,7 @@ import { successResponse, errorResponse } from '../utils/response-helpers.js'; import { compareVersions } from '../utils/version.js'; import type { FirefoxDevTools } from '../firefox/index.js'; -import { defineModule } from './module.js'; +import { defineModule, type ToolDefinition } from './module.js'; import type { McpToolResponse } from '../types/common.js'; const MIN_FIREFOX_VERSION = '154.0'; @@ -60,7 +60,7 @@ export const screencastStartTool = { }, }, }, -}; +} satisfies ToolDefinition; export async function handleScreencastStart(args: unknown): Promise { try { @@ -134,7 +134,7 @@ export const screencastStopTool = { }, }, }, -}; +} satisfies ToolDefinition; export async function handleScreencastStop(args: unknown): Promise { try { diff --git a/src/tools/screenshot.ts b/src/tools/screenshot.ts index 1e1bac9d..1705ad8c 100644 --- a/src/tools/screenshot.ts +++ b/src/tools/screenshot.ts @@ -5,7 +5,7 @@ import { successResponse, errorResponse } from '../utils/response-helpers.js'; import { handleUidError } from '../utils/uid-helpers.js'; import { saveOutput } from '../utils/save-output.js'; -import { defineModule } from './module.js'; +import { defineModule, type ToolDefinition } from './module.js'; import type { McpToolResponse } from '../types/common.js'; const SAVE_TO_SCHEMA = { @@ -27,7 +27,7 @@ export const screenshotPageTool = { saveTo: SAVE_TO_SCHEMA, }, }, -}; +} satisfies ToolDefinition; export const screenshotByUidTool = { name: 'screenshot_by_uid', @@ -46,7 +46,7 @@ export const screenshotByUidTool = { }, required: ['uid'], }, -}; +} satisfies ToolDefinition; /** * Save screenshot to file and return text response with path. diff --git a/src/tools/script.ts b/src/tools/script.ts index 0c4bac30..686eb1c3 100644 --- a/src/tools/script.ts +++ b/src/tools/script.ts @@ -6,7 +6,7 @@ import { successResponse, errorResponse, previewExcerpt } from '../utils/respons import { remoteValueToNative } from '../utils/remote-value.js'; import { validateFunction } from '../utils/js-validation.js'; import { saveOutput } from '../utils/save-output.js'; -import { defineModule } from './module.js'; +import { defineModule, type ToolDefinition } from './module.js'; import type { McpToolResponse } from '../types/common.js'; export const evaluateScriptTool = { @@ -59,7 +59,7 @@ export const evaluateScriptTool = { }, required: ['function'], }, -}; +} satisfies ToolDefinition; // Constants const DEFAULT_TIMEOUT = 5000; // 5 seconds diff --git a/src/tools/snapshot.ts b/src/tools/snapshot.ts index 00f2b475..8903e1c7 100644 --- a/src/tools/snapshot.ts +++ b/src/tools/snapshot.ts @@ -11,7 +11,7 @@ import { } from '../utils/response-helpers.js'; import { handleUidError } from '../utils/uid-helpers.js'; import { saveOutput } from '../utils/save-output.js'; -import { defineModule } from './module.js'; +import { defineModule, type ToolDefinition } from './module.js'; import type { McpToolResponse } from '../types/common.js'; const DEFAULT_SNAPSHOT_LINES = 100; @@ -64,7 +64,7 @@ export const takeSnapshotTool = { }, }, }, -}; +} satisfies ToolDefinition; export const resolveUidToSelectorTool = { name: 'resolve_uid_to_selector', @@ -82,7 +82,7 @@ export const resolveUidToSelectorTool = { }, required: ['uid'], }, -}; +} satisfies ToolDefinition; export const clearSnapshotTool = { name: 'clear_snapshot', @@ -94,7 +94,7 @@ export const clearSnapshotTool = { type: 'object', properties: {}, }, -}; +} satisfies ToolDefinition; // Handlers export async function handleTakeSnapshot(args: unknown): Promise { diff --git a/src/tools/utilities.ts b/src/tools/utilities.ts index c5922598..bb344396 100644 --- a/src/tools/utilities.ts +++ b/src/tools/utilities.ts @@ -3,7 +3,7 @@ */ import { successResponse, errorResponse } from '../utils/response-helpers.js'; -import { defineModule } from './module.js'; +import { defineModule, type ToolDefinition } from './module.js'; import type { McpToolResponse } from '../types/common.js'; // Tool definitions - Dialogs @@ -22,7 +22,7 @@ export const acceptDialogTool = { }, }, }, -}; +} satisfies ToolDefinition; export const dismissDialogTool = { name: 'dismiss_dialog', @@ -34,7 +34,7 @@ export const dismissDialogTool = { type: 'object', properties: {}, }, -}; +} satisfies ToolDefinition; // Tool definitions - History export const navigateHistoryTool = { @@ -54,7 +54,7 @@ export const navigateHistoryTool = { }, required: ['direction'], }, -}; +} satisfies ToolDefinition; // Tool definitions - Viewport export const setViewportSizeTool = { @@ -77,7 +77,7 @@ export const setViewportSizeTool = { }, required: ['width', 'height'], }, -}; +} satisfies ToolDefinition; // Handlers - Dialogs export async function handleAcceptDialog(args: unknown): Promise { diff --git a/src/tools/webextension.ts b/src/tools/webextension.ts index d22f961e..f68c3a86 100644 --- a/src/tools/webextension.ts +++ b/src/tools/webextension.ts @@ -10,7 +10,7 @@ */ import { successResponse, errorResponse } from '../utils/response-helpers.js'; -import { defineModule } from './module.js'; +import { defineModule, type ToolDefinition } from './module.js'; import type { McpToolResponse } from '../types/common.js'; // ============================================================================ @@ -49,7 +49,7 @@ export const installExtensionTool = { }, required: ['type'], }, -}; +} satisfies ToolDefinition; export async function handleInstallExtension(args: unknown): Promise { try { @@ -124,7 +124,7 @@ export const uninstallExtensionTool = { }, required: ['id'], }, -}; +} satisfies ToolDefinition; export async function handleUninstallExtension(args: unknown): Promise { try { @@ -182,7 +182,7 @@ export const listExtensionsTool = { }, }, }, -}; +} satisfies ToolDefinition; interface ExtensionInfo { id: string;