clawdbot-workspace/factory-tools/scripts/fix-unknown-tool-error.mjs
2026-02-04 23:01:37 -05:00

126 lines
4.5 KiB
JavaScript

#!/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}`);