Files
mcp-tool/comprehensive-api-audit.js
nasir@endelospay.com 811b9bee91 fix add tool
2025-07-19 03:08:10 +05:00

576 lines
16 KiB
JavaScript

#!/usr/bin/env node
/**
* Comprehensive API Audit: api-docs.json vs MCP Tools
* Achieves 100% API coverage with accurate parameter mapping
*/
import fs from "fs";
import path from "path";
class ComprehensiveAPIAuditor {
constructor() {
this.apiEndpoints = [];
this.currentTools = [];
this.missingEndpoints = [];
this.parameterMismatches = [];
this.newTools = [];
this.auditResults = {};
}
/**
* Phase 1: Load and parse api-docs.json (OpenAPI 3.0)
*/
loadApiDocs() {
try {
console.log("📖 Loading api-docs.json...");
const apiDocsPath = path.join(process.cwd(), "../api-docs.json");
const apiDocsContent = fs.readFileSync(apiDocsPath, "utf8");
const apiDocs = JSON.parse(apiDocsContent);
console.log(
`📊 API Docs Info: ${apiDocs.info.title} v${apiDocs.info.version}`
);
// Parse OpenAPI paths
for (const [pathUrl, pathData] of Object.entries(apiDocs.paths || {})) {
for (const [method, methodData] of Object.entries(pathData)) {
if (typeof methodData === "object" && methodData.summary) {
const endpoint = {
path: pathUrl,
method: method.toUpperCase(),
operationId: methodData.operationId || "",
summary: methodData.summary || "",
description: methodData.description || "",
tags: methodData.tags || [],
parameters: this.extractParameters(methodData),
security: methodData.security || [],
authRequired: this.hasAuthRequired(methodData.security),
};
this.apiEndpoints.push(endpoint);
}
}
}
console.log(
`✅ Loaded ${this.apiEndpoints.length} endpoints from api-docs.json`
);
return true;
} catch (error) {
console.error("❌ Error loading api-docs.json:", error.message);
return false;
}
}
/**
* Extract parameters from OpenAPI method data
*/
extractParameters(methodData) {
const parameters = [];
// Path parameters
if (methodData.parameters) {
methodData.parameters.forEach((param) => {
parameters.push({
name: param.name,
type: param.schema?.type || "string",
required: param.required || false,
description: param.description || "",
location: param.in || "query",
});
});
}
// Request body parameters
if (
methodData.requestBody?.content?.["application/json"]?.schema?.properties
) {
const schema = methodData.requestBody.content["application/json"].schema;
const required = schema.required || [];
Object.entries(schema.properties).forEach(([propName, propData]) => {
parameters.push({
name: propName,
type: propData.type || "string",
required: required.includes(propName),
description: propData.description || "",
location: "body",
});
});
}
return parameters;
}
/**
* Check if endpoint requires authentication
*/
hasAuthRequired(security) {
return (
security &&
security.length > 0 &&
security.some((sec) => Object.keys(sec).length > 0)
);
}
/**
* Load current MCP tools from endpoints.js
*/
loadCurrentTools() {
try {
console.log("🔧 Loading current MCP tools...");
const endpointsPath = path.join(process.cwd(), "src/config/endpoints.js");
const endpointsContent = fs.readFileSync(endpointsPath, "utf8");
const authTypes = [
"PUBLIC",
"PROVIDER",
"PATIENT",
"PARTNER",
"AFFILIATE",
"NETWORK",
];
authTypes.forEach((authType) => {
const regex = new RegExp(
`export const ${authType}_ENDPOINTS\\s*=\\s*\\[([\\s\\S]*?)\\];`,
"g"
);
const match = regex.exec(endpointsContent);
if (match) {
const sectionContent = match[1];
const endpointRegex = /\{[\s\S]*?\}/g;
let endpointMatch;
while (
(endpointMatch = endpointRegex.exec(sectionContent)) !== null
) {
const endpointStr = endpointMatch[0];
const tool = this.parseEndpointString(
endpointStr,
authType.toLowerCase()
);
if (tool) {
this.currentTools.push(tool);
}
}
}
});
console.log(`✅ Loaded ${this.currentTools.length} current MCP tools`);
return true;
} catch (error) {
console.error("❌ Error loading current tools:", error.message);
return false;
}
}
/**
* Parse endpoint string to extract tool information
*/
parseEndpointString(endpointStr, authType) {
const pathMatch = endpointStr.match(/path:\s*["']([^"']+)["']/);
const methodMatch = endpointStr.match(/method:\s*["']([^"']+)["']/);
const descMatch = endpointStr.match(/description:\s*["']([^"']*?)["']/);
if (!pathMatch || !methodMatch) return null;
return {
authType,
path: pathMatch[1],
method: methodMatch[1].toUpperCase(),
description: descMatch ? descMatch[1] : "",
parameters: this.extractParametersFromString(endpointStr),
toolName: this.generateToolName(authType, pathMatch[1], methodMatch[1]),
};
}
/**
* Extract parameters from endpoint string
*/
extractParametersFromString(endpointStr) {
const parameters = [];
const paramMatch = endpointStr.match(
/parameters:\s*\{([\s\S]*?)\}(?:\s*,\s*\}|\s*\})/
);
if (paramMatch) {
const paramContent = paramMatch[1];
const paramRegex = /(\w+):\s*\{([^}]*)\}/g;
let match;
while ((match = paramRegex.exec(paramContent)) !== null) {
const paramName = match[1];
const paramDef = match[2];
const typeMatch = paramDef.match(/type:\s*["']([^"']+)["']/);
const requiredMatch = paramDef.match(/required:\s*(true|false)/);
const descMatch = paramDef.match(/description:\s*["']([^"']*?)["']/);
parameters.push({
name: paramName,
type: typeMatch ? typeMatch[1] : "string",
required: requiredMatch ? requiredMatch[1] === "true" : false,
description: descMatch ? descMatch[1] : "",
});
}
}
return parameters;
}
/**
* Generate tool name following MCP conventions
*/
generateToolName(authType, path, method) {
let cleanPath = path.replace(/^\//, "").replace(/\{[^}]+\}/g, "");
const pathParts = cleanPath.split("/").filter((part) => part.length > 0);
if (pathParts[0] === "api") {
pathParts.shift();
}
let toolName = pathParts
.join("")
.replace(/[^a-zA-Z0-9]/g, "")
.toLowerCase();
if (!toolName) {
toolName = method.toLowerCase();
}
return `${authType}_${method.toLowerCase()}_${toolName}`;
}
/**
* Phase 1: Individual Endpoint Verification
*/
performEndpointVerification() {
console.log("\n🔍 Phase 1: Individual Endpoint Verification");
this.apiEndpoints.forEach((apiEndpoint) => {
const matchingTool = this.currentTools.find(
(tool) =>
tool.path === apiEndpoint.path && tool.method === apiEndpoint.method
);
if (!matchingTool) {
// Missing endpoint
this.missingEndpoints.push({
...apiEndpoint,
authType: this.determineAuthType(apiEndpoint),
});
} else {
// Check parameter mismatches
const mismatch = this.compareParameters(apiEndpoint, matchingTool);
if (mismatch.hasDifferences) {
this.parameterMismatches.push({
endpoint: apiEndpoint,
tool: matchingTool,
...mismatch,
});
}
}
});
console.log(`📊 Missing endpoints: ${this.missingEndpoints.length}`);
console.log(`📊 Parameter mismatches: ${this.parameterMismatches.length}`);
}
/**
* Determine authentication type based on endpoint characteristics
*/
determineAuthType(endpoint) {
const path = endpoint.path.toLowerCase();
const hasAuth = endpoint.authRequired;
// Public endpoints (no auth or login/register)
if (
!hasAuth ||
path.includes("/login") ||
path.includes("/register") ||
path.includes("/forgot-password") ||
path.includes("/reset-password") ||
path.includes("/verify-email") ||
path.includes("/webhook") ||
path.includes("/room-joined")
) {
return "public";
}
// Provider endpoints (clinical/EMR data)
if (
path.includes("/emr/") ||
path.includes("/patient") ||
path.includes("/appointment") ||
path.includes("/prescription") ||
path.includes("/medical-record") ||
path.includes("/provider") ||
path.includes("/practitioner") ||
path.includes("/doctor")
) {
return "provider";
}
// Patient endpoints
if (path.includes("/patient/") && !path.includes("/emr/")) {
return "patient";
}
// Partner endpoints
if (path.includes("/partner/")) {
return "partner";
}
// Affiliate endpoints
if (path.includes("/affiliate/")) {
return "affiliate";
}
// Network endpoints
if (path.includes("/network/")) {
return "network";
}
// Default to provider for authenticated clinical endpoints
return "provider";
}
/**
* Compare parameters between API endpoint and MCP tool
*/
compareParameters(apiEndpoint, tool) {
const missing = [];
const extra = [];
const different = [];
// Check for missing parameters in tool
apiEndpoint.parameters.forEach((apiParam) => {
const toolParam = tool.parameters.find((tp) => tp.name === apiParam.name);
if (!toolParam) {
missing.push(apiParam);
} else {
// Check for differences
if (
apiParam.type !== toolParam.type ||
apiParam.required !== toolParam.required ||
(apiParam.description &&
apiParam.description !== toolParam.description)
) {
different.push({
name: apiParam.name,
api: apiParam,
tool: toolParam,
});
}
}
});
// Check for extra parameters in tool
tool.parameters.forEach((toolParam) => {
const apiParam = apiEndpoint.parameters.find(
(ap) => ap.name === toolParam.name
);
if (!apiParam) {
extra.push(toolParam);
}
});
return {
hasDifferences:
missing.length > 0 || extra.length > 0 || different.length > 0,
missing,
extra,
different,
};
}
/**
* Phase 2: Generate new tools for missing endpoints
*/
generateNewTools() {
console.log("\n🔧 Phase 2: Tool Generation and Updates");
this.missingEndpoints.forEach((endpoint) => {
const toolName = this.generateToolName(
endpoint.authType,
endpoint.path,
endpoint.method
);
const newTool = {
toolName,
authType: endpoint.authType,
path: endpoint.path,
method: endpoint.method,
controller: this.generateController(endpoint),
category: this.determineCategory(endpoint),
description: this.cleanString(endpoint.summary || endpoint.description),
parameters: this.convertParametersToMCPFormat(endpoint.parameters),
};
this.newTools.push(newTool);
});
console.log(`✅ Generated ${this.newTools.length} new tools`);
}
/**
* Generate controller name from endpoint
*/
generateController(endpoint) {
if (endpoint.operationId) {
return `ApiController@${endpoint.operationId}`;
}
const pathParts = endpoint.path
.split("/")
.filter((part) => part && !part.startsWith("{"));
if (pathParts.length > 1) {
const controller = pathParts[pathParts.length - 1];
return `${
controller.charAt(0).toUpperCase() + controller.slice(1)
}Controller@${endpoint.method.toLowerCase()}`;
}
return "ApiController@handleRequest";
}
/**
* Determine category based on endpoint characteristics
*/
determineCategory(endpoint) {
const path = endpoint.path.toLowerCase();
const tags = endpoint.tags.map((tag) => tag.toLowerCase());
if (
tags.includes("meetings") ||
path.includes("meeting") ||
path.includes("call")
) {
return "ENDPOINT_CATEGORIES.MEETINGS";
}
if (path.includes("appointment")) {
return "ENDPOINT_CATEGORIES.APPOINTMENT_SCHEDULING";
}
if (path.includes("patient")) {
return "ENDPOINT_CATEGORIES.PATIENT_MANAGEMENT";
}
if (path.includes("prescription") || path.includes("medication")) {
return "ENDPOINT_CATEGORIES.PRESCRIPTION_MANAGEMENT";
}
if (path.includes("document")) {
return "ENDPOINT_CATEGORIES.DOCUMENT_MANAGEMENT";
}
if (path.includes("form")) {
return "ENDPOINT_CATEGORIES.FORMS_QUESTIONNAIRES";
}
if (path.includes("lab")) {
return "ENDPOINT_CATEGORIES.MEDICAL_RECORDS";
}
if (path.includes("user") || path.includes("admin")) {
return "ENDPOINT_CATEGORIES.USER_MANAGEMENT";
}
return "ENDPOINT_CATEGORIES.PROVIDER_MANAGEMENT";
}
/**
* Clean string for JavaScript
*/
cleanString(str) {
if (!str) return "";
return str
.replace(/"/g, '\\"')
.replace(/\n/g, " ")
.replace(/\r/g, "")
.trim();
}
/**
* Convert API parameters to MCP format
*/
convertParametersToMCPFormat(apiParams) {
const mcpParams = {};
apiParams.forEach((param) => {
const cleanName = param.name.replace(/[^a-zA-Z0-9_]/g, "_");
mcpParams[cleanName] = {
type: param.type || "string",
required: param.required || false,
description: this.cleanString(param.description) || "Parameter",
};
});
return mcpParams;
}
/**
* Run comprehensive audit
*/
async runAudit() {
console.log("🚀 COMPREHENSIVE API AUDIT STARTING\n");
// Load data
if (!this.loadApiDocs()) return false;
if (!this.loadCurrentTools()) return false;
// Perform verification
this.performEndpointVerification();
// Generate new tools
this.generateNewTools();
// Save results
this.auditResults = {
summary: {
totalApiEndpoints: this.apiEndpoints.length,
totalCurrentTools: this.currentTools.length,
missingEndpoints: this.missingEndpoints.length,
parameterMismatches: this.parameterMismatches.length,
newToolsGenerated: this.newTools.length,
},
missingEndpoints: this.missingEndpoints,
parameterMismatches: this.parameterMismatches,
newTools: this.newTools,
timestamp: new Date().toISOString(),
};
fs.writeFileSync(
"comprehensive-audit-results.json",
JSON.stringify(this.auditResults, null, 2)
);
console.log("\n📋 AUDIT RESULTS SUMMARY:");
console.log(
`📊 Total API endpoints: ${this.auditResults.summary.totalApiEndpoints}`
);
console.log(
`📊 Current MCP tools: ${this.auditResults.summary.totalCurrentTools}`
);
console.log(
`❌ Missing endpoints: ${this.auditResults.summary.missingEndpoints}`
);
console.log(
`⚠️ Parameter mismatches: ${this.auditResults.summary.parameterMismatches}`
);
console.log(
`🆕 New tools generated: ${this.auditResults.summary.newToolsGenerated}`
);
console.log("\n💾 Results saved to comprehensive-audit-results.json");
return true;
}
}
// Run the audit
const auditor = new ComprehensiveAPIAuditor();
auditor.runAudit().then((success) => {
if (success) {
console.log("\n✅ Comprehensive audit completed successfully!");
} else {
console.log("\n❌ Audit failed");
}
});