mcp-swagger-parser
Version:
Enterprise-grade OpenAPI/Swagger specification parser for Model Context Protocol (MCP) projects
258 lines • 9.93 kB
JavaScript
;
/**
* 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