UNPKG

mcp-swagger-parser

Version:

Enterprise-grade OpenAPI/Swagger specification parser for Model Context Protocol (MCP) projects

258 lines 9.93 kB
"use strict"; /** * Metadata extractor for OpenAPI specifications */ Object.defineProperty(exports, "__esModule", { value: true }); exports.MetadataExtractor = void 0; class MetadataExtractor { /** * Extract comprehensive metadata from OpenAPI specification */ static extractMetadata(spec) { return { info: this.extractInfo(spec), servers: this.extractServers(spec), tags: this.extractTags(spec), externalDocs: spec.externalDocs, statistics: this.calculateStatistics(spec) }; } /** * Extract API information */ static extractInfo(spec) { return { title: spec.info.title, version: spec.info.version, description: spec.info.description, contact: spec.info.contact, license: spec.info.license, termsOfService: spec.info.termsOfService }; } /** * Extract server information */ static extractServers(spec) { if (!spec.servers || spec.servers.length === 0) { return [{ url: 'http://localhost', description: 'Default server (no servers specified)' }]; } return spec.servers.map(server => ({ url: server.url, description: server.description, variables: server.variables })); } /** * Extract tag information */ static extractTags(spec) { const definedTags = spec.tags || []; const usedTags = new Set(); // Collect tags used in operations if (spec.paths) { for (const pathItem of Object.values(spec.paths)) { const methods = ['get', 'post', 'put', 'delete', 'patch', 'head', 'options', 'trace']; for (const method of methods) { const operation = pathItem[method]; if (operation?.tags) { operation.tags.forEach(tag => usedTags.add(tag)); } } } } // Combine defined tags with used tags const allTags = new Map(); // Add defined tags definedTags.forEach(tag => { allTags.set(tag.name, tag); }); // Add used but not defined tags usedTags.forEach(tagName => { if (!allTags.has(tagName)) { allTags.set(tagName, { name: tagName, description: `Tag used in operations (not defined in global tags)` }); } }); return Array.from(allTags.values()).sort((a, b) => a.name.localeCompare(b.name)); } /** * Calculate comprehensive statistics */ static calculateStatistics(spec) { const stats = { pathCount: 0, operationCount: 0, schemaCount: 0, securitySchemeCount: 0, parameterCount: 0, responseCount: 0, operationsByMethod: {}, operationsByTag: {}, deprecatedOperations: 0 }; // Count paths and operations if (spec.paths) { stats.pathCount = Object.keys(spec.paths).length; for (const pathItem of Object.values(spec.paths)) { const methods = ['get', 'post', 'put', 'delete', 'patch', 'head', 'options', 'trace']; for (const method of methods) { const operation = pathItem[method]; if (!operation) continue; stats.operationCount++; // Count by method const methodUpper = method.toUpperCase(); stats.operationsByMethod[methodUpper] = (stats.operationsByMethod[methodUpper] || 0) + 1; // Count by tag if (operation.tags) { operation.tags.forEach(tag => { stats.operationsByTag[tag] = (stats.operationsByTag[tag] || 0) + 1; }); } else { stats.operationsByTag['untagged'] = (stats.operationsByTag['untagged'] || 0) + 1; } // Count deprecated operations if (operation.deprecated) { stats.deprecatedOperations++; } // Count parameters if (operation.parameters) { stats.parameterCount += operation.parameters.length; } // Count responses if (operation.responses) { stats.responseCount += Object.keys(operation.responses).length; } } // Count path-level parameters if (pathItem.parameters) { stats.parameterCount += pathItem.parameters.length; } } } // Count components if (spec.components) { stats.schemaCount = Object.keys(spec.components.schemas || {}).length; stats.securitySchemeCount = Object.keys(spec.components.securitySchemes || {}).length; stats.parameterCount += Object.keys(spec.components.parameters || {}).length; stats.responseCount += Object.keys(spec.components.responses || {}).length; } return stats; } /** * Generate API summary */ static generateSummary(metadata) { const { info, statistics } = metadata; const lines = [ `# ${info.title} (v${info.version})`, '' ]; if (info.description) { lines.push(info.description, ''); } lines.push('## API Statistics', `- **Paths**: ${statistics.pathCount}`, `- **Operations**: ${statistics.operationCount}`, `- **Schemas**: ${statistics.schemaCount}`, `- **Security Schemes**: ${statistics.securitySchemeCount}`, ''); if (statistics.deprecatedOperations > 0) { lines.push(`⚠️ **Deprecated Operations**: ${statistics.deprecatedOperations}`, ''); } // Operations by method if (Object.keys(statistics.operationsByMethod).length > 0) { lines.push('## Operations by Method'); Object.entries(statistics.operationsByMethod) .sort(([, a], [, b]) => b - a) .forEach(([method, count]) => { lines.push(`- **${method}**: ${count}`); }); lines.push(''); } // Operations by tag if (Object.keys(statistics.operationsByTag).length > 0) { lines.push('## Operations by Tag'); Object.entries(statistics.operationsByTag) .sort(([, a], [, b]) => b - a) .slice(0, 10) // Show top 10 tags .forEach(([tag, count]) => { lines.push(`- **${tag}**: ${count}`); }); lines.push(''); } // Servers if (metadata.servers.length > 0) { lines.push('## Servers'); metadata.servers.forEach(server => { const desc = server.description ? ` - ${server.description}` : ''; lines.push(`- ${server.url}${desc}`); }); lines.push(''); } return lines.join('\n'); } /** * Validate metadata completeness */ static validateMetadata(metadata) { let score = 0; const maxScore = 100; const suggestions = []; // Check basic info completeness (40 points) if (metadata.info.description) score += 10; else suggestions.push('Add API description'); if (metadata.info.contact) score += 10; else suggestions.push('Add contact information'); if (metadata.info.license) score += 10; else suggestions.push('Add license information'); if (metadata.info.termsOfService) score += 10; else suggestions.push('Add terms of service'); // Check documentation (20 points) if (metadata.externalDocs) score += 10; else suggestions.push('Add external documentation links'); const taggedOperations = Object.values(metadata.statistics.operationsByTag) .reduce((sum, count) => sum + count, 0) - (metadata.statistics.operationsByTag['untagged'] || 0); if (taggedOperations === metadata.statistics.operationCount) score += 10; else suggestions.push('Tag all operations for better organization'); // Check operation quality (20 points) const deprecatedRatio = metadata.statistics.deprecatedOperations / metadata.statistics.operationCount; if (deprecatedRatio < 0.1) score += 10; else suggestions.push('Reduce number of deprecated operations'); if (metadata.statistics.operationCount > 0) score += 10; // Check server configuration (10 points) const hasProductionServer = metadata.servers.some(server => !server.url.includes('localhost') && !server.url.includes('127.0.0.1')); if (hasProductionServer) score += 10; else suggestions.push('Add production server URL'); // Check security (10 points) if (metadata.statistics.securitySchemeCount > 0) score += 10; else suggestions.push('Define security schemes'); return { score: Math.round((score / maxScore) * 100), suggestions }; } } exports.MetadataExtractor = MetadataExtractor; //# sourceMappingURL=metadata-extractor.js.map