#!/usr/bin/env node /** * MCP Factory — Fix Unknown Tool Error * Patches all 30 servers to properly throw McpError for unknown tools * instead of catching and returning isError:true (which MCP spec treats as success). * * The fix: * 1. Import McpError and ErrorCode from the SDK * 2. Check tool name against known tools before calling handler * 3. Throw McpError(ErrorCode.MethodNotFound) for unknown tools */ import { readFileSync, writeFileSync, readdirSync, existsSync } from 'fs'; import { execSync } from 'child_process'; import { resolve, dirname } from 'path'; import { fileURLToPath } from 'url'; const __dirname = dirname(fileURLToPath(import.meta.url)); const FACTORY_ROOT = resolve(__dirname, '..'); const registry = JSON.parse(readFileSync(resolve(FACTORY_ROOT, 'server-registry.json'), 'utf-8')); const SERVERS_ROOT = resolve(FACTORY_ROOT, registry.servers_root); let fixed = 0; let skipped = 0; let errors = 0; for (const [name] of Object.entries(registry.servers)) { const srcPath = resolve(SERVERS_ROOT, name, 'src/index.ts'); if (!existsSync(srcPath)) { console.log(`⚠️ ${name}: No src/index.ts`); skipped++; continue; } let src = readFileSync(srcPath, 'utf-8'); // Check if already fixed if (src.includes('McpError')) { console.log(`⏭️ ${name}: Already has McpError import`); skipped++; continue; } try { // Step 1: Add McpError and ErrorCode to imports // Find the import from types.js const typesImportMatch = src.match(/(import\s*\{[^}]*\}\s*from\s*"@modelcontextprotocol\/sdk\/types\.js";)/); if (typesImportMatch) { const oldImport = typesImportMatch[1]; // Extract existing imports const existingImports = oldImport.match(/\{([^}]+)\}/)[1].trim(); const newImport = oldImport.replace( `{${existingImports}}`, `{${existingImports}, McpError, ErrorCode}` ); src = src.replace(oldImport, newImport); } // Step 2: Add tool name validation before the try/catch in CallToolRequestSchema handler // Pattern: Find the handler and add a check const toolNames = [...src.matchAll(/name:\s*"([^"]+)"/g)].map(m => m[1]); // Filter to only tool names (in the tools array, not other name fields) const validToolNames = toolNames.filter(n => !['text', 'object'].includes(n)); // Find the CallToolRequestSchema handler and add validation const handlerPattern = /server\.setRequestHandler\(CallToolRequestSchema,\s*async\s*\(request\)\s*=>\s*\{\s*const\s*\{\s*name,\s*arguments:\s*args\s*\}\s*=\s*request\.params;\s*\n\s*try\s*\{/; if (handlerPattern.test(src)) { src = src.replace( handlerPattern, `server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; // Validate tool exists (MCP spec requires proper error for unknown tools) const knownTools = tools.map(t => t.name); if (!knownTools.includes(name)) { throw new McpError(ErrorCode.MethodNotFound, \`Unknown tool: \${name}\`); } try {` ); } else { // Try a more flexible pattern const altPattern = /server\.setRequestHandler\(CallToolRequestSchema,\s*async\s*\(request\)\s*=>\s*\{/; if (altPattern.test(src)) { // Check if there's already a tool validation const handlerBlock = src.substring(src.search(altPattern)); if (handlerBlock.includes('const { name') && !handlerBlock.includes('knownTools')) { // Insert after the destructuring src = src.replace( /const\s*\{\s*name,\s*arguments:\s*args\s*\}\s*=\s*request\.params;\s*\n/, `const { name, arguments: args } = request.params; // Validate tool exists (MCP spec requires proper error for unknown tools) const knownTools = tools.map(t => t.name); if (!knownTools.includes(name)) { throw new McpError(ErrorCode.MethodNotFound, \`Unknown tool: \${name}\`); } ` ); } } } writeFileSync(srcPath, src); console.log(`✅ ${name}: Patched`); fixed++; // Rebuild try { execSync('npm run build', { cwd: resolve(SERVERS_ROOT, name), timeout: 15000, stdio: 'pipe' }); console.log(` 🔨 ${name}: Rebuilt`); } catch (buildErr) { console.log(` ⚠️ ${name}: Build warning (check manually)`); } } catch (err) { console.log(`❌ ${name}: ${err.message}`); errors++; } } console.log('\n' + '═'.repeat(60)); console.log(`Fixed: ${fixed} | Skipped: ${skipped} | Errors: ${errors}`);