#!/usr/bin/env node /** * Audit all MCP servers for: * 1. Missing argument descriptions * 2. Missing _meta labels * 3. Missing app resource URIs * 4. Improper tool naming */ import fs from 'fs'; import path from 'path'; import { fileURLToPath } from 'url'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const serversDir = path.join(__dirname, 'mcp-servers'); const results = { servers: [], totalServers: 0, totalTools: 0, issues: { missingArgDescriptions: 0, missingLabels: 0, missingAppURI: 0, badNaming: 0, } }; // Get all server directories const serverDirs = fs.readdirSync(serversDir).filter(dir => { const fullPath = path.join(serversDir, dir); return fs.statSync(fullPath).isDirectory() && fs.existsSync(path.join(fullPath, 'src/index.ts')); }); console.log(`\nšŸ” Auditing ${serverDirs.length} MCP servers...\n`); for (const serverName of serverDirs) { const indexPath = path.join(serversDir, serverName, 'src/index.ts'); const content = fs.readFileSync(indexPath, 'utf-8'); const serverResult = { name: serverName, tools: [], issues: [], }; // Extract tool definitions using regex (simple approach) const toolMatches = content.matchAll(/{\s*name:\s*["']([^"']+)["']\s*,\s*description:\s*["']([^"']+)["']/gs); for (const match of toolMatches) { const toolName = match[1]; const toolDesc = match[2]; const tool = { name: toolName, description: toolDesc, issues: [], }; // Check tool naming convention if (!toolName.match(/^[a-z]+_[a-z_]+$/)) { tool.issues.push('Bad naming (not snake_case verb_noun)'); serverResult.issues.push(`Tool "${toolName}": Bad naming`); results.issues.badNaming++; } // Find the tool's inputSchema section const toolStartIndex = content.indexOf(`name: "${toolName}"`); const nextToolIndex = content.indexOf('\n {', toolStartIndex + 1); const toolSection = content.substring( toolStartIndex, nextToolIndex === -1 ? content.length : nextToolIndex ); // Check for missing parameter descriptions const paramMatches = toolSection.matchAll(/(\w+):\s*{\s*type:\s*["'](\w+)["']\s*(?:,\s*description:\s*["']([^"']+)["'])?/g); let hasMissingDesc = false; for (const paramMatch of paramMatches) { const paramName = paramMatch[1]; const paramType = paramMatch[2]; const paramDesc = paramMatch[3]; // Skip if it's not actually a parameter (could be part of schema structure) if (paramName === 'type' || paramName === 'properties' || paramName === 'required') continue; if (!paramDesc && paramType !== 'object') { tool.issues.push(`Parameter "${paramName}" missing description`); hasMissingDesc = true; } } if (hasMissingDesc) { serverResult.issues.push(`Tool "${toolName}": Missing argument descriptions`); results.issues.missingArgDescriptions++; } // Check for _meta labels if (!toolSection.includes('_meta')) { tool.issues.push('Missing _meta labels'); serverResult.issues.push(`Tool "${toolName}": Missing _meta labels`); results.issues.missingLabels++; } // Check for app tools missing resourceUri if (toolName.startsWith('view_') || toolName.startsWith('show_')) { if (!toolSection.includes('resourceUri')) { tool.issues.push('App tool missing resourceUri'); serverResult.issues.push(`Tool "${toolName}": App tool missing resourceUri`); results.issues.missingAppURI++; } } serverResult.tools.push(tool); } results.servers.push(serverResult); results.totalServers++; results.totalTools += serverResult.tools.length; // Print server summary if (serverResult.issues.length > 0) { console.log(`āŒ ${serverName} (${serverResult.tools.length} tools, ${serverResult.issues.length} issues)`); serverResult.issues.forEach(issue => console.log(` - ${issue}`)); } else { console.log(`āœ… ${serverName} (${serverResult.tools.length} tools)`); } } // Summary console.log(`\nšŸ“Š Summary:`); console.log(` Total servers: ${results.totalServers}`); console.log(` Total tools: ${results.totalTools}`); console.log(`\nāš ļø Issues found:`); console.log(` Missing argument descriptions: ${results.issues.missingArgDescriptions}`); console.log(` Missing _meta labels: ${results.issues.missingLabels}`); console.log(` App tools missing resourceUri: ${results.issues.missingAppURI}`); console.log(` Bad tool naming: ${results.issues.badNaming}`); // Write detailed report const reportPath = path.join(__dirname, 'audit-report.json'); fs.writeFileSync(reportPath, JSON.stringify(results, null, 2)); console.log(`\nšŸ“„ Detailed report written to: audit-report.json\n`);