diff --git a/package-lock.json b/package-lock.json index 5584040..fae69ee 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,19 +1,20 @@ { "name": "firecrawl-mcp", - "version": "3.3.2", + "version": "3.3.6", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "firecrawl-mcp", - "version": "3.3.2", + "version": "3.3.6", "license": "MIT", "dependencies": { "@mendable/firecrawl-js": "^4.3.6", "dotenv": "^17.2.2", "firecrawl-fastmcp": "^1.0.3", "typescript": "^5.9.2", - "zod": "^4.1.5" + "zod": "^4.1.5", + "zod-to-json-schema": "^3.24.6" }, "bin": { "firecrawl-mcp": "dist/index.js" @@ -59,15 +60,6 @@ "url": "https://github.com/sponsors/colinhacks" } }, - "node_modules/@mendable/firecrawl-js/node_modules/zod-to-json-schema": { - "version": "3.24.6", - "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.6.tgz", - "integrity": "sha512-h/z3PKvcTcTetyjl1fkj79MHNEjm+HpD6NXheWjzOekY7kV+lwDYnHw+ivHkijnCSMz1yJaWBD9vu/Fcmk+vEg==", - "license": "ISC", - "peerDependencies": { - "zod": "^3.24.1" - } - }, "node_modules/@modelcontextprotocol/sdk": { "version": "1.18.0", "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.18.0.tgz", @@ -100,15 +92,6 @@ "url": "https://github.com/sponsors/colinhacks" } }, - "node_modules/@modelcontextprotocol/sdk/node_modules/zod-to-json-schema": { - "version": "3.24.6", - "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.6.tgz", - "integrity": "sha512-h/z3PKvcTcTetyjl1fkj79MHNEjm+HpD6NXheWjzOekY7kV+lwDYnHw+ivHkijnCSMz1yJaWBD9vu/Fcmk+vEg==", - "license": "ISC", - "peerDependencies": { - "zod": "^3.24.1" - } - }, "node_modules/@sec-ant/readable-stream": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz", @@ -776,15 +759,6 @@ "url": "https://github.com/sponsors/colinhacks" } }, - "node_modules/firecrawl-fastmcp/node_modules/zod-to-json-schema": { - "version": "3.24.6", - "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.6.tgz", - "integrity": "sha512-h/z3PKvcTcTetyjl1fkj79MHNEjm+HpD6NXheWjzOekY7kV+lwDYnHw+ivHkijnCSMz1yJaWBD9vu/Fcmk+vEg==", - "license": "ISC", - "peerDependencies": { - "zod": "^3.24.1" - } - }, "node_modules/follow-redirects": { "version": "1.15.11", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", @@ -1912,6 +1886,15 @@ "funding": { "url": "https://github.com/sponsors/colinhacks" } + }, + "node_modules/zod-to-json-schema": { + "version": "3.24.6", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.6.tgz", + "integrity": "sha512-h/z3PKvcTcTetyjl1fkj79MHNEjm+HpD6NXheWjzOekY7kV+lwDYnHw+ivHkijnCSMz1yJaWBD9vu/Fcmk+vEg==", + "license": "ISC", + "peerDependencies": { + "zod": "^3.24.1" + } } } } diff --git a/package.json b/package.json index 89b07ab..3e72aa6 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,8 @@ "dotenv": "^17.2.2", "firecrawl-fastmcp": "^1.0.3", "typescript": "^5.9.2", - "zod": "^4.1.5" + "zod": "^4.1.5", + "zod-to-json-schema": "^3.24.6" }, "engines": { "node": ">=18.0.0" diff --git a/src/index.ts b/src/index.ts index 63af336..e5e5190 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,11 @@ #!/usr/bin/env node import dotenv from 'dotenv'; +import { patchFastMCPSchemas } from './schema-patch.js'; + +// CRITICAL: Apply MCP SDK patches BEFORE importing FastMCP +// This patches the Server.prototype.setRequestHandler method +patchFastMCPSchemas(); + import { FastMCP, type Logger } from 'firecrawl-fastmcp'; import { z } from 'zod'; import FirecrawlApp from '@mendable/firecrawl-js'; diff --git a/src/schema-patch.ts b/src/schema-patch.ts new file mode 100644 index 0000000..b15217c --- /dev/null +++ b/src/schema-patch.ts @@ -0,0 +1,103 @@ +/** + * Patch for VS Code MCP JSON Schema Compatibility + * + * This module patches JSON schemas from draft 2020-12 to draft-07 + * to ensure compatibility with VS Code's MCP validator. + */ + +import { Server } from '@modelcontextprotocol/sdk/server/index.js'; + +/** + * Recursively converts JSON Schema draft 2020-12 to draft-07 + * @param schema - The schema object to convert + * @returns The converted schema + */ +export function convertSchemaToDraft07(schema: any): any { + if (!schema || typeof schema !== 'object') { + return schema; + } + + // Create a copy to avoid mutating the original + const converted = Array.isArray(schema) ? [...schema] : { ...schema }; + + // Replace the $schema version + if (converted.$schema === 'https://json-schema.org/draft/2020-12/schema') { + converted.$schema = 'http://json-schema.org/draft-07/schema#'; + } + + // Remove unsupported draft 2020-12 features + delete converted.$dynamicRef; + delete converted.$dynamicAnchor; + delete converted.prefixItems; + delete converted.dependentSchemas; + delete converted.dependentRequired; + + // Convert $defs to definitions (draft-07 uses 'definitions') + if (converted.$defs) { + converted.definitions = converted.$defs; + delete converted.$defs; + } + + // Recursively process nested schemas + for (const key in converted) { + if (typeof converted[key] === 'object' && converted[key] !== null) { + converted[key] = convertSchemaToDraft07(converted[key]); + } + } + + return converted; +} + +/** + * Patches the MCP SDK Server class directly by monkey-patching its setRequestHandler method + * This must be called BEFORE any Server instances are created + */ +export function patchFastMCPSchemas(_server?: any): void { + console.error('[Schema Patch] Applying VS Code compatibility patches...'); + + // Patch the Server prototype's setRequestHandler method + const originalSetRequestHandler = Server.prototype.setRequestHandler; + if (!originalSetRequestHandler) { + console.error('[Schema Patch] ERROR: Server.prototype.setRequestHandler not found'); + return; + } + + console.error('[Schema Patch] Patching Server.prototype.setRequestHandler...'); + + Server.prototype.setRequestHandler = function(requestSchema: any, handler: any) { + // Extract the method name from the Zod schema + const method = requestSchema?.shape?.method?.value; + + console.error(`[Schema Patch] setRequestHandler called for method: ${method}`); + + if (method === 'tools/list') { + console.error('[Schema Patch] ✓ Intercepting tools/list handler registration'); + + const wrappedHandler = async (...args: any[]) => { + console.error('[Schema Patch] tools/list handler called, converting schemas...'); + const response = await handler(...args); + + if (response && response.tools && Array.isArray(response.tools)) { + console.error(`[Schema Patch] Converting ${response.tools.length} tool schemas to draft-07`); + + response.tools = response.tools.map((tool: any) => { + if (tool.inputSchema) { + const before = tool.inputSchema.$schema || 'unknown'; + tool.inputSchema = convertSchemaToDraft07(tool.inputSchema); + console.error(`[Schema Patch] ✓ ${tool.name}: ${before} -> draft-07`); + } + return tool; + }); + } + + return response; + }; + + return originalSetRequestHandler.call(this, requestSchema, wrappedHandler); + } + + return originalSetRequestHandler.call(this, requestSchema, handler); + }; + + console.error('[Schema Patch] Successfully patched Server.prototype.setRequestHandler'); +}