clawdbot-workspace/mcp-diagrams/audit-servers.js

145 lines
4.7 KiB
JavaScript

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