#!/usr/bin/env node /** * Comprehensive API Audit Script * Cross-references api-docs.json against endpoints.js to identify missing endpoints * and parameter discrepancies for complete MCP server coverage */ import fs from "fs"; import path from "path"; /** * Load and parse API documentation */ function loadApiDocumentation() { try { const apiDocsPath = path.join( process.cwd(), "complete-api-parameters.json" ); const apiDocsContent = fs.readFileSync(apiDocsPath, "utf8"); return JSON.parse(apiDocsContent); } catch (error) { console.error("āŒ Error loading API documentation:", error.message); process.exit(1); } } /** * Load and parse current endpoints configuration */ function loadCurrentEndpoints() { try { const endpointsPath = path.join(process.cwd(), "src/config/endpoints.js"); const endpointsContent = fs.readFileSync(endpointsPath, "utf8"); // Extract endpoint arrays using regex const publicMatch = endpointsContent.match( /export const PUBLIC_ENDPOINTS = \[([\s\S]*?)\];/ ); const providerMatch = endpointsContent.match( /export const PROVIDER_ENDPOINTS = \[([\s\S]*?)\];/ ); const patientMatch = endpointsContent.match( /export const PATIENT_ENDPOINTS = \[([\s\S]*?)\];/ ); const partnerMatch = endpointsContent.match( /export const PARTNER_ENDPOINTS = \[([\s\S]*?)\];/ ); const affiliateMatch = endpointsContent.match( /export const AFFILIATE_ENDPOINTS = \[([\s\S]*?)\];/ ); const networkMatch = endpointsContent.match( /export const NETWORK_ENDPOINTS = \[([\s\S]*?)\];/ ); const endpoints = { PUBLIC: [], PROVIDER: [], PATIENT: [], PARTNER: [], AFFILIATE: [], NETWORK: [], }; // Parse endpoints from each array if (publicMatch) { endpoints.PUBLIC = extractEndpointsFromText(publicMatch[1]); } if (providerMatch) { endpoints.PROVIDER = extractEndpointsFromText(providerMatch[1]); } if (patientMatch) { endpoints.PATIENT = extractEndpointsFromText(patientMatch[1]); } if (partnerMatch) { endpoints.PARTNER = extractEndpointsFromText(partnerMatch[1]); } if (affiliateMatch) { endpoints.AFFILIATE = extractEndpointsFromText(affiliateMatch[1]); } if (networkMatch) { endpoints.NETWORK = extractEndpointsFromText(networkMatch[1]); } return endpoints; } catch (error) { console.error("āŒ Error loading current endpoints:", error.message); process.exit(1); } } /** * Extract endpoint objects from text using regex */ function extractEndpointsFromText(text) { const endpoints = []; const endpointRegex = /\{[\s\S]*?path:\s*["']([^"']+)["'][\s\S]*?method:\s*["']([^"']+)["'][\s\S]*?\}/g; let match; while ((match = endpointRegex.exec(text)) !== null) { endpoints.push({ path: match[1], method: match[2].toUpperCase(), }); } return endpoints; } /** * Categorize API endpoints by authentication type */ function categorizeApiEndpoints(apiEndpoints) { const categorized = { PUBLIC: [], PROVIDER: [], PATIENT: [], PARTNER: [], AFFILIATE: [], NETWORK: [], }; apiEndpoints.forEach((endpoint) => { const path = endpoint.path; const requiresAuth = endpoint.requiresAuth; // Categorization logic based on path patterns and authentication if ( !requiresAuth || path.includes("/login") || path.includes("/register") || path.includes("/password") ) { categorized.PUBLIC.push(endpoint); } else if ( path.includes("/emr/") || path.includes("/api/emr") || endpoint.tags?.some((tag) => ["Provider", "Medical", "Clinical"].includes(tag) ) ) { categorized.PROVIDER.push(endpoint); } else if ( path.includes("/patient/") || path.includes("/frontend/") || endpoint.tags?.some((tag) => ["Patient", "Patient Portal"].includes(tag)) ) { categorized.PATIENT.push(endpoint); } else if ( path.includes("/partner/") || endpoint.tags?.some((tag) => ["Partner"].includes(tag)) ) { categorized.PARTNER.push(endpoint); } else if ( path.includes("/affiliate/") || endpoint.tags?.some((tag) => ["Affiliate"].includes(tag)) ) { categorized.AFFILIATE.push(endpoint); } else if ( path.includes("/network/") || endpoint.tags?.some((tag) => ["Network"].includes(tag)) ) { categorized.NETWORK.push(endpoint); } else { // Default to PROVIDER for authenticated endpoints categorized.PROVIDER.push(endpoint); } }); return categorized; } /** * Find missing endpoints by comparing API docs with current implementation */ function findMissingEndpoints(apiEndpoints, currentEndpoints) { const missing = { PUBLIC: [], PROVIDER: [], PATIENT: [], PARTNER: [], AFFILIATE: [], NETWORK: [], }; Object.keys(apiEndpoints).forEach((authType) => { const apiList = apiEndpoints[authType]; const currentList = currentEndpoints[authType] || []; apiList.forEach((apiEndpoint) => { const exists = currentList.some( (current) => current.path === apiEndpoint.path && current.method === apiEndpoint.method.toUpperCase() ); if (!exists) { missing[authType].push(apiEndpoint); } }); }); return missing; } /** * Generate audit report */ function generateAuditReport(apiEndpoints, currentEndpoints, missingEndpoints) { const report = { timestamp: new Date().toISOString(), summary: { totalApiEndpoints: 0, totalCurrentEndpoints: 0, totalMissingEndpoints: 0, byAuthType: {}, }, missingEndpoints, recommendations: [], }; // Calculate totals Object.keys(apiEndpoints).forEach((authType) => { const apiCount = apiEndpoints[authType].length; const currentCount = currentEndpoints[authType]?.length || 0; const missingCount = missingEndpoints[authType].length; report.summary.totalApiEndpoints += apiCount; report.summary.totalCurrentEndpoints += currentCount; report.summary.totalMissingEndpoints += missingCount; report.summary.byAuthType[authType] = { apiEndpoints: apiCount, currentEndpoints: currentCount, missingEndpoints: missingCount, coverage: currentCount > 0 ? ((currentCount / apiCount) * 100).toFixed(1) + "%" : "0%", }; }); // Generate recommendations Object.keys(missingEndpoints).forEach((authType) => { if (missingEndpoints[authType].length > 0) { report.recommendations.push({ authType, action: `Implement ${missingEndpoints[authType].length} missing ${authType} endpoints`, priority: authType === "PROVIDER" ? "HIGH" : authType === "PUBLIC" ? "MEDIUM" : "LOW", }); } }); return report; } /** * Main audit function */ function performAudit() { console.log("šŸ” Starting comprehensive API audit...\n"); // Load data console.log("šŸ“‹ Loading API documentation..."); const apiDocs = loadApiDocumentation(); console.log(`āœ… Loaded ${apiDocs.length} API endpoints\n`); console.log("šŸ“‹ Loading current endpoint configuration..."); const currentEndpoints = loadCurrentEndpoints(); const currentTotal = Object.values(currentEndpoints).reduce( (sum, arr) => sum + arr.length, 0 ); console.log(`āœ… Loaded ${currentTotal} current endpoints\n`); // Categorize API endpoints console.log("šŸ·ļø Categorizing API endpoints by authentication type..."); const categorizedApiEndpoints = categorizeApiEndpoints(apiDocs); console.log("āœ… Categorization complete\n"); // Find missing endpoints console.log("šŸ” Identifying missing endpoints..."); const missingEndpoints = findMissingEndpoints( categorizedApiEndpoints, currentEndpoints ); console.log("āœ… Analysis complete\n"); // Generate report console.log("šŸ“Š Generating audit report..."); const report = generateAuditReport( categorizedApiEndpoints, currentEndpoints, missingEndpoints ); // Save report const reportPath = path.join( process.cwd(), "comprehensive-api-audit-report.json" ); fs.writeFileSync(reportPath, JSON.stringify(report, null, 2)); console.log(`āœ… Report saved to: ${reportPath}\n`); // Display summary console.log("šŸ“ˆ AUDIT SUMMARY:"); console.log(`Total API endpoints: ${report.summary.totalApiEndpoints}`); console.log( `Current implementation: ${report.summary.totalCurrentEndpoints}` ); console.log(`Missing endpoints: ${report.summary.totalMissingEndpoints}`); console.log( `Overall coverage: ${( (report.summary.totalCurrentEndpoints / report.summary.totalApiEndpoints) * 100 ).toFixed(1)}%\n` ); // Display by auth type console.log("šŸ“Š COVERAGE BY AUTHENTICATION TYPE:"); Object.keys(report.summary.byAuthType).forEach((authType) => { const stats = report.summary.byAuthType[authType]; console.log( `${authType}: ${stats.currentEndpoints}/${stats.apiEndpoints} (${stats.coverage}) - Missing: ${stats.missingEndpoints}` ); }); console.log("\nšŸŽÆ RECOMMENDATIONS:"); report.recommendations.forEach((rec) => { console.log( `${ rec.priority === "HIGH" ? "šŸ”“" : rec.priority === "MEDIUM" ? "🟔" : "🟢" } ${rec.action} (${rec.priority} priority)` ); }); return report; } // Run audit if called directly if (import.meta.url === `file://${process.argv[1]}`) { performAudit(); } // Also run if this is the main module (for Node.js compatibility) if (process.argv[1] && process.argv[1].endsWith("comprehensive-api-audit.js")) { performAudit(); } export { performAudit };