126 lines
4.5 KiB
JavaScript
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}`);
|