openapi-spec-master
Version:
🚀 Professional OpenAPI specification analyzer and MCP server. The ultimate toolkit for API developers with VS Code extension integration.
1,098 lines (1,051 loc) • 85.7 kB
JavaScript
#!/usr/bin/env node
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
import { OpenAPIParser } from '../utils/openapi-parser.js';
import { generateAnalytics } from '../utils/analytics.js';
class OpenAPIExplorerMCPServer {
server;
parser;
currentSpec = null;
currentEndpoints = [];
constructor() {
this.server = new Server({
name: 'openapi-explorer',
version: '1.0.0',
}, {
capabilities: {
tools: {},
},
});
this.parser = new OpenAPIParser();
this.setupToolHandlers();
}
setupToolHandlers() {
this.server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: [
{
name: 'load_openapi_spec',
description: 'Load and parse an OpenAPI specification from text, URL, or file content',
inputSchema: {
type: 'object',
properties: {
source: {
type: 'string',
description: 'The source of the OpenAPI spec (text content, URL, or file content)',
},
sourceType: {
type: 'string',
enum: ['text', 'url'],
description: 'Type of source: text (JSON/YAML content) or url',
default: 'text',
},
},
required: ['source'],
},
},
{
name: 'get_api_overview',
description: 'Get a comprehensive overview of the loaded API including basic info, statistics, and analytics',
inputSchema: {
type: 'object',
properties: {},
},
},
{
name: 'search_endpoints',
description: 'Search and filter API endpoints with advanced criteria',
inputSchema: {
type: 'object',
properties: {
query: {
type: 'string',
description: 'Search query for endpoint paths, summaries, or descriptions',
},
methods: {
type: 'array',
items: { type: 'string' },
description: 'Filter by HTTP methods (GET, POST, PUT, DELETE, etc.)',
},
tags: {
type: 'array',
items: { type: 'string' },
description: 'Filter by endpoint tags',
},
complexity: {
type: 'array',
items: { type: 'string', enum: ['low', 'medium', 'high'] },
description: 'Filter by endpoint complexity level',
},
deprecated: {
type: 'boolean',
description: 'Filter by deprecation status',
},
hasParameters: {
type: 'boolean',
description: 'Filter endpoints that have parameters',
},
hasRequestBody: {
type: 'boolean',
description: 'Filter endpoints that require a request body',
},
},
},
},
{
name: 'get_endpoint_details',
description: 'Get detailed information about a specific endpoint',
inputSchema: {
type: 'object',
properties: {
method: {
type: 'string',
description: 'HTTP method (GET, POST, etc.)',
},
path: {
type: 'string',
description: 'Endpoint path',
},
},
required: ['method', 'path'],
},
},
{
name: 'generate_code_examples',
description: 'Generate code examples for specific endpoints in various languages',
inputSchema: {
type: 'object',
properties: {
method: {
type: 'string',
description: 'HTTP method',
},
path: {
type: 'string',
description: 'Endpoint path',
},
language: {
type: 'string',
enum: ['curl', 'javascript', 'python', 'typescript'],
description: 'Programming language for the example',
default: 'curl',
},
},
required: ['method', 'path'],
},
},
{
name: 'get_api_analytics',
description: 'Get comprehensive analytics and insights about the API',
inputSchema: {
type: 'object',
properties: {
includeDistributions: {
type: 'boolean',
description: 'Include method, tag, and complexity distributions',
default: true,
},
},
},
},
{
name: 'validate_api_design',
description: 'Analyze the API design and provide recommendations for improvements',
inputSchema: {
type: 'object',
properties: {
focus: {
type: 'string',
enum: ['security', 'performance', 'design', 'documentation', 'all'],
description: 'Focus area for validation',
default: 'all',
},
},
},
},
{
name: 'export_documentation',
description: 'Export API documentation in various formats',
inputSchema: {
type: 'object',
properties: {
format: {
type: 'string',
enum: ['markdown', 'json', 'summary'],
description: 'Export format',
default: 'markdown',
},
includeExamples: {
type: 'boolean',
description: 'Include code examples in export',
default: true,
},
includeAnalytics: {
type: 'boolean',
description: 'Include analytics in export',
default: false,
},
},
},
},
{
name: 'search_request_body_properties',
description: 'Deep search through request body schemas to find specific properties, types, or patterns',
inputSchema: {
type: 'object',
properties: {
propertyName: {
type: 'string',
description: 'Name of the property to search for (supports partial matches)',
},
propertyType: {
type: 'string',
description: 'Type of property to search for (string, number, boolean, array, object)',
},
schemaPattern: {
type: 'string',
description: 'Regex pattern to match against schema descriptions or names',
},
required: {
type: 'boolean',
description: 'Filter by whether the property is required',
},
methods: {
type: 'array',
items: { type: 'string' },
description: 'Limit search to specific HTTP methods',
},
},
},
},
{
name: 'generate_typescript_types',
description: 'Generate TypeScript interfaces and types from OpenAPI schemas',
inputSchema: {
type: 'object',
properties: {
schemaName: {
type: 'string',
description: 'Specific schema name to generate types for (optional - generates all if not provided)',
},
includeRequestBodies: {
type: 'boolean',
description: 'Include request body types',
default: true,
},
includeResponses: {
type: 'boolean',
description: 'Include response types',
default: true,
},
exportFormat: {
type: 'string',
enum: ['individual', 'merged'],
description: 'Export as individual interfaces or merged into one file',
default: 'individual',
},
addValidation: {
type: 'boolean',
description: 'Add validation decorators (useful for class-validator)',
default: false,
},
},
},
},
{
name: 'find_schema_dependencies',
description: 'Trace and analyze schema references and dependencies throughout the API',
inputSchema: {
type: 'object',
properties: {
schemaName: {
type: 'string',
description: 'Schema to analyze dependencies for',
},
direction: {
type: 'string',
enum: ['dependencies', 'dependents', 'both'],
description: 'Find what this schema depends on, what depends on it, or both',
default: 'both',
},
depth: {
type: 'number',
description: 'Maximum depth of dependency traversal',
default: 5,
},
},
required: ['schemaName'],
},
},
{
name: 'validate_request_examples',
description: 'Validate that request/response examples match their schemas',
inputSchema: {
type: 'object',
properties: {
endpoint: {
type: 'string',
description: 'Specific endpoint to validate (method:path format, e.g., "POST:/users")',
},
strictMode: {
type: 'boolean',
description: 'Use strict validation (fail on additional properties)',
default: false,
},
},
},
},
{
name: 'extract_auth_patterns',
description: 'Analyze and extract authentication and authorization patterns across the API',
inputSchema: {
type: 'object',
properties: {
includeEndpointMapping: {
type: 'boolean',
description: 'Include mapping of which endpoints use which auth methods',
default: true,
},
analyzeScopes: {
type: 'boolean',
description: 'Analyze OAuth scopes and permissions',
default: true,
},
},
},
},
{
name: 'generate_mock_data',
description: 'Generate realistic mock data based on OpenAPI schemas',
inputSchema: {
type: 'object',
properties: {
schemaName: {
type: 'string',
description: 'Schema to generate mock data for',
},
endpoint: {
type: 'string',
description: 'Generate mock data for specific endpoint (method:path format)',
},
count: {
type: 'number',
description: 'Number of mock items to generate',
default: 3,
},
realistic: {
type: 'boolean',
description: 'Generate realistic data based on field names and types',
default: true,
},
format: {
type: 'string',
enum: ['json', 'javascript', 'typescript'],
description: 'Output format for mock data',
default: 'json',
},
},
},
},
{
name: 'find_unused_schemas',
description: 'Identify schemas that are defined but never referenced in the API',
inputSchema: {
type: 'object',
properties: {
includeIndirectReferences: {
type: 'boolean',
description: 'Check for indirect references through other schemas',
default: true,
},
},
},
},
{
name: 'analyze_schema_evolution',
description: 'Analyze how schemas might evolve and suggest versioning strategies',
inputSchema: {
type: 'object',
properties: {
schemaName: {
type: 'string',
description: 'Schema to analyze for evolution patterns',
},
suggestVersioning: {
type: 'boolean',
description: 'Suggest versioning strategies',
default: true,
},
},
},
},
],
};
});
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
try {
switch (name) {
case 'load_openapi_spec':
return await this.loadOpenAPISpec(args);
case 'get_api_overview':
return await this.getAPIOverview();
case 'search_endpoints':
return await this.searchEndpoints(args);
case 'get_endpoint_details':
return await this.getEndpointDetails(args);
case 'generate_code_examples':
return await this.generateCodeExamples(args);
case 'get_api_analytics':
return await this.getAPIAnalytics(args);
case 'validate_api_design':
return await this.validateAPIDesign(args);
case 'export_documentation':
return await this.exportDocumentation(args);
case 'search_request_body_properties':
return await this.searchRequestBodyProperties(args);
case 'generate_typescript_types':
return await this.generateTypeScriptTypes(args);
case 'find_schema_dependencies':
return await this.findSchemaDependencies(args);
case 'validate_request_examples':
return await this.validateRequestExamples(args);
case 'extract_auth_patterns':
return await this.extractAuthPatterns(args);
case 'generate_mock_data':
return await this.generateMockData(args);
case 'find_unused_schemas':
return await this.findUnusedSchemas(args);
case 'analyze_schema_evolution':
return await this.analyzeSchemaEvolution(args);
default:
throw new Error(`Unknown tool: ${name}`);
}
}
catch (error) {
return {
content: [
{
type: 'text',
text: `Error: ${error instanceof Error ? error.message : 'Unknown error occurred'}`,
},
],
isError: true,
};
}
});
}
async loadOpenAPISpec(args) {
const { source, sourceType = 'text' } = args;
try {
let spec;
if (sourceType === 'url') {
spec = await this.parser.parseFromUrl(source);
}
else {
spec = await this.parser.parseFromText(source);
}
this.currentSpec = spec;
this.currentEndpoints = this.parser.extractEndpoints();
const analytics = generateAnalytics(this.currentEndpoints);
return {
content: [
{
type: 'text',
text: `✅ Successfully loaded OpenAPI specification!
**API Information:**
- Title: ${spec.info.title}
- Version: ${spec.info.version}
- Description: ${spec.info.description || 'No description provided'}
- OpenAPI Version: ${spec.openapi}
**Statistics:**
- Total Endpoints: ${this.currentEndpoints.length}
- HTTP Methods: ${Object.keys(analytics.methodDistribution).join(', ')}
- Tags: ${Object.keys(analytics.tagDistribution).length}
- Deprecated Endpoints: ${analytics.deprecatedCount}
The API specification has been loaded and is ready for exploration. You can now use other tools to search endpoints, get analytics, generate code examples, and more!`,
},
],
};
}
catch (error) {
throw new Error(`Failed to load OpenAPI spec: ${error instanceof Error ? error.message : 'Unknown error'}`);
}
}
async getAPIOverview() {
if (!this.currentSpec || !this.currentEndpoints.length) {
throw new Error('No OpenAPI specification loaded. Please load a spec first.');
}
const analytics = generateAnalytics(this.currentEndpoints);
const tags = this.parser.getAllTags();
const methods = this.parser.getAllMethods();
return {
content: [
{
type: 'text',
text: `# ${this.currentSpec.info.title} - API Overview
## Basic Information
- **Version:** ${this.currentSpec.info.version}
- **OpenAPI Version:** ${this.currentSpec.openapi}
- **Description:** ${this.currentSpec.info.description || 'No description provided'}
## Statistics
- **Total Endpoints:** ${analytics.totalEndpoints}
- **Deprecated Endpoints:** ${analytics.deprecatedCount} (${((analytics.deprecatedCount / analytics.totalEndpoints) * 100).toFixed(1)}%)
- **Average Parameters per Endpoint:** ${analytics.averageParametersPerEndpoint.toFixed(1)}
- **Security Schemes:** ${analytics.securitySchemes.length}
## HTTP Methods Distribution
${Object.entries(analytics.methodDistribution)
.map(([method, count]) => `- **${method}:** ${count} endpoints`)
.join('\n')}
## Tags (${tags.length})
${tags.length > 0 ? tags.map(tag => `- ${tag}`).join('\n') : 'No tags defined'}
## Complexity Distribution
${Object.entries(analytics.complexityDistribution)
.map(([complexity, count]) => `- **${complexity.charAt(0).toUpperCase() + complexity.slice(1)}:** ${count} endpoints`)
.join('\n')}
## Top Response Codes
${Object.entries(analytics.responseCodeDistribution)
.sort(([, a], [, b]) => b - a)
.slice(0, 5)
.map(([code, count]) => `- **${code}:** ${count} endpoints`)
.join('\n')}`,
},
],
};
}
async searchEndpoints(args) {
if (!this.currentEndpoints.length) {
throw new Error('No OpenAPI specification loaded. Please load a spec first.');
}
const { query, methods, tags, complexity, deprecated, hasParameters, hasRequestBody, } = args;
let filteredEndpoints = [...this.currentEndpoints];
// Apply filters
if (query) {
const searchTerm = query.toLowerCase();
filteredEndpoints = filteredEndpoints.filter(endpoint => endpoint.path.toLowerCase().includes(searchTerm) ||
endpoint.summary?.toLowerCase().includes(searchTerm) ||
endpoint.description?.toLowerCase().includes(searchTerm) ||
endpoint.tags.some(tag => tag.toLowerCase().includes(searchTerm)));
}
if (methods && methods.length > 0) {
filteredEndpoints = filteredEndpoints.filter(endpoint => methods.includes(endpoint.method));
}
if (tags && tags.length > 0) {
filteredEndpoints = filteredEndpoints.filter(endpoint => endpoint.tags.some(tag => tags.includes(tag)));
}
if (complexity && complexity.length > 0) {
filteredEndpoints = filteredEndpoints.filter(endpoint => endpoint.complexity && complexity.includes(endpoint.complexity));
}
if (typeof deprecated === 'boolean') {
filteredEndpoints = filteredEndpoints.filter(endpoint => endpoint.deprecated === deprecated);
}
if (typeof hasParameters === 'boolean') {
filteredEndpoints = filteredEndpoints.filter(endpoint => (endpoint.parameters.length > 0) === hasParameters);
}
if (typeof hasRequestBody === 'boolean') {
filteredEndpoints = filteredEndpoints.filter(endpoint => !!endpoint.requestBody === hasRequestBody);
}
const results = filteredEndpoints.slice(0, 20); // Limit results
return {
content: [
{
type: 'text',
text: `# Search Results
Found **${filteredEndpoints.length}** endpoints matching your criteria${filteredEndpoints.length > 20 ? ' (showing first 20)' : ''}:
${results.map(endpoint => `
## ${endpoint.method} ${endpoint.path}
- **Summary:** ${endpoint.summary || 'No summary'}
- **Tags:** ${endpoint.tags.join(', ') || 'None'}
- **Complexity:** ${endpoint.complexity || 'Unknown'}
- **Parameters:** ${endpoint.parameters.length}
- **Deprecated:** ${endpoint.deprecated ? 'Yes' : 'No'}
- **Has Request Body:** ${endpoint.requestBody ? 'Yes' : 'No'}
${endpoint.description ? `- **Description:** ${endpoint.description}` : ''}
`).join('\n')}`,
},
],
};
}
async getEndpointDetails(args) {
if (!this.currentEndpoints.length) {
throw new Error('No OpenAPI specification loaded. Please load a spec first.');
}
const { method, path } = args;
const endpoint = this.currentEndpoints.find(ep => ep.method.toLowerCase() === method.toLowerCase() && ep.path === path);
if (!endpoint) {
throw new Error(`Endpoint ${method} ${path} not found.`);
}
return {
content: [
{
type: 'text',
text: `# ${endpoint.method} ${endpoint.path}
## Overview
- **Summary:** ${endpoint.summary || 'No summary provided'}
- **Description:** ${endpoint.description || 'No description provided'}
- **Tags:** ${endpoint.tags.join(', ') || 'None'}
- **Deprecated:** ${endpoint.deprecated ? '⚠️ Yes' : '✅ No'}
- **Complexity:** ${endpoint.complexity || 'Unknown'}
## Parameters (${endpoint.parameters.length})
${endpoint.parameters.length > 0 ?
endpoint.parameters.map(param => `
### ${param.name} (${param.in})
- **Type:** ${(param.schema && 'type' in param.schema) ? param.schema.type : 'Unknown'}
- **Required:** ${param.required ? 'Yes' : 'No'}
- **Description:** ${param.description || 'No description'}
`).join('\n') : 'No parameters'}
## Request Body
${endpoint.requestBody ? 'This endpoint accepts a request body' : 'No request body required'}
## Responses
${Object.entries(endpoint.responses).map(([code, response]) => `
### ${code}
${response.description}
`).join('\n')}
## Business Context
${endpoint.businessContext || 'No business context available'}
## AI Suggestions
${endpoint.aiSuggestions && endpoint.aiSuggestions.length > 0 ?
endpoint.aiSuggestions.map(suggestion => `- ${suggestion}`).join('\n') :
'No AI suggestions available'}`,
},
],
};
}
async generateCodeExamples(args) {
if (!this.currentEndpoints.length) {
throw new Error('No OpenAPI specification loaded. Please load a spec first.');
}
const { method, path, language = 'curl' } = args;
const endpoint = this.currentEndpoints.find(ep => ep.method.toLowerCase() === method.toLowerCase() && ep.path === path);
if (!endpoint) {
throw new Error(`Endpoint ${method} ${path} not found.`);
}
let example = '';
switch (language) {
case 'curl':
example = this.generateCurlExample(endpoint);
break;
case 'javascript':
example = this.generateJavaScriptExample(endpoint);
break;
case 'python':
example = this.generatePythonExample(endpoint);
break;
case 'typescript':
example = this.generateTypeScriptExample(endpoint);
break;
default:
throw new Error(`Unsupported language: ${language}`);
}
return {
content: [
{
type: 'text',
text: `# Code Example: ${endpoint.method} ${endpoint.path}
## ${language.charAt(0).toUpperCase() + language.slice(1)} Example
\`\`\`${language === 'typescript' ? 'typescript' : language}
${example}
\`\`\`
## Endpoint Details
- **Summary:** ${endpoint.summary || 'No summary'}
- **Parameters:** ${endpoint.parameters.length}
- **Request Body:** ${endpoint.requestBody ? 'Required' : 'Not required'}`,
},
],
};
}
generateCurlExample(endpoint) {
const method = endpoint.method.toLowerCase();
let curl = `curl -X ${endpoint.method} "${endpoint.path}"`;
if (endpoint.parameters?.some(p => p.in === 'header')) {
curl += ` \\\n -H "Content-Type: application/json"`;
}
if (endpoint.requestBody && (method === 'post' || method === 'put' || method === 'patch')) {
curl += ` \\\n -d '{"example": "data"}'`;
}
return curl;
}
generateJavaScriptExample(endpoint) {
const hasBody = endpoint.requestBody && ['post', 'put', 'patch'].includes(endpoint.method.toLowerCase());
return `const response = await fetch('${endpoint.path}', {
method: '${endpoint.method}',
headers: {
'Content-Type': 'application/json',
},${hasBody ? `
body: JSON.stringify({
// Add your request data here
}),` : ''}
});
const data = await response.json();
console.log(data);`;
}
generatePythonExample(endpoint) {
const hasBody = endpoint.requestBody && ['post', 'put', 'patch'].includes(endpoint.method.toLowerCase());
return `import requests
url = "${endpoint.path}"
headers = {"Content-Type": "application/json"}
${hasBody ? `
data = {
# Add your request data here
}
response = requests.${endpoint.method.toLowerCase()}(url, headers=headers, json=data)` : `
response = requests.${endpoint.method.toLowerCase()}(url, headers=headers)`}
print(response.json())`;
}
generateTypeScriptExample(endpoint) {
const hasBody = endpoint.requestBody && ['post', 'put', 'patch'].includes(endpoint.method.toLowerCase());
return `interface ApiResponse {
// Define your response type here
}
${hasBody ? `interface RequestData {
// Define your request data type here
}
` : ''}const response = await fetch('${endpoint.path}', {
method: '${endpoint.method}',
headers: {
'Content-Type': 'application/json',
},${hasBody ? `
body: JSON.stringify({
// Add your request data here
} as RequestData),` : ''}
});
const data: ApiResponse = await response.json();
console.log(data);`;
}
async getAPIAnalytics(args) {
if (!this.currentEndpoints.length) {
throw new Error('No OpenAPI specification loaded. Please load a spec first.');
}
const { includeDistributions = true } = args;
const analytics = generateAnalytics(this.currentEndpoints);
let result = `# API Analytics
## Overview Statistics
- **Total Endpoints:** ${analytics.totalEndpoints}
- **Deprecated Endpoints:** ${analytics.deprecatedCount} (${((analytics.deprecatedCount / analytics.totalEndpoints) * 100).toFixed(1)}%)
- **Average Parameters per Endpoint:** ${analytics.averageParametersPerEndpoint.toFixed(1)}
- **Security Schemes:** ${analytics.securitySchemes.length}
## Security Analysis
${analytics.securitySchemes.length > 0 ?
`**Security Schemes Used:** ${analytics.securitySchemes.join(', ')}` :
'⚠️ **No security schemes detected** - Consider adding authentication'}`;
if (includeDistributions) {
result += `
## Method Distribution
${Object.entries(analytics.methodDistribution)
.sort(([, a], [, b]) => b - a)
.map(([method, count]) => `- **${method}:** ${count} endpoints (${((count / analytics.totalEndpoints) * 100).toFixed(1)}%)`)
.join('\n')}
## Complexity Distribution
${Object.entries(analytics.complexityDistribution)
.map(([complexity, count]) => `- **${complexity.charAt(0).toUpperCase() + complexity.slice(1)}:** ${count} endpoints (${((count / analytics.totalEndpoints) * 100).toFixed(1)}%)`)
.join('\n')}
## Response Code Distribution
${Object.entries(analytics.responseCodeDistribution)
.sort(([, a], [, b]) => b - a)
.slice(0, 10)
.map(([code, count]) => `- **${code}:** ${count} endpoints`)
.join('\n')}
## Tag Distribution (Top 10)
${Object.entries(analytics.tagDistribution)
.sort(([, a], [, b]) => b - a)
.slice(0, 10)
.map(([tag, count]) => `- **${tag}:** ${count} endpoints`)
.join('\n')}`;
}
return {
content: [
{
type: 'text',
text: result,
},
],
};
}
async validateAPIDesign(args) {
if (!this.currentEndpoints.length) {
throw new Error('No OpenAPI specification loaded. Please load a spec first.');
}
const { focus = 'all' } = args;
const analytics = generateAnalytics(this.currentEndpoints);
const recommendations = [];
// Security validation
if (focus === 'security' || focus === 'all') {
if (analytics.securitySchemes.length === 0) {
recommendations.push('🔒 **Security:** No security schemes detected. Consider adding authentication to protect your API.');
}
const unsecuredEndpoints = this.currentEndpoints.filter(ep => !ep.security || ep.security.length === 0);
if (unsecuredEndpoints.length > 0) {
recommendations.push(`🔒 **Security:** ${unsecuredEndpoints.length} endpoints have no security requirements. Review if this is intentional.`);
}
}
// Documentation validation
if (focus === 'documentation' || focus === 'all') {
const undocumentedEndpoints = this.currentEndpoints.filter(ep => !ep.summary && !ep.description);
if (undocumentedEndpoints.length > 0) {
recommendations.push(`📝 **Documentation:** ${undocumentedEndpoints.length} endpoints lack summaries or descriptions.`);
}
const untaggedEndpoints = this.currentEndpoints.filter(ep => ep.tags.length === 0);
if (untaggedEndpoints.length > 0) {
recommendations.push(`🏷️ **Organization:** ${untaggedEndpoints.length} endpoints have no tags for better organization.`);
}
}
// Design validation
if (focus === 'design' || focus === 'all') {
if (analytics.deprecatedCount > 0) {
recommendations.push(`⚠️ **Maintenance:** ${analytics.deprecatedCount} deprecated endpoints found. Consider migration strategy.`);
}
const highComplexityEndpoints = this.currentEndpoints.filter(ep => ep.complexity === 'high');
if (highComplexityEndpoints.length > analytics.totalEndpoints * 0.3) {
recommendations.push(`🔧 **Design:** High number of complex endpoints (${highComplexityEndpoints.length}). Consider simplifying API design.`);
}
}
// Performance validation
if (focus === 'performance' || focus === 'all') {
const slowEndpoints = this.currentEndpoints.filter(ep => ep.estimatedResponseTime === 'slow');
if (slowEndpoints.length > 0) {
recommendations.push(`⚡ **Performance:** ${slowEndpoints.length} endpoints estimated as slow. Consider optimization.`);
}
}
if (recommendations.length === 0) {
recommendations.push('✅ **Great job!** No major issues detected in your API design.');
}
return {
content: [
{
type: 'text',
text: `# API Design Validation Report
## Analysis Focus: ${focus.charAt(0).toUpperCase() + focus.slice(1)}
## Recommendations
${recommendations.map(rec => rec).join('\n\n')}
## Summary Statistics
- **Total Endpoints:** ${analytics.totalEndpoints}
- **Security Coverage:** ${((this.currentEndpoints.filter(ep => ep.security && ep.security.length > 0).length / analytics.totalEndpoints) * 100).toFixed(1)}%
- **Documentation Coverage:** ${((this.currentEndpoints.filter(ep => ep.summary || ep.description).length / analytics.totalEndpoints) * 100).toFixed(1)}%
- **Tag Coverage:** ${((this.currentEndpoints.filter(ep => ep.tags.length > 0).length / analytics.totalEndpoints) * 100).toFixed(1)}%`,
},
],
};
}
async exportDocumentation(args) {
if (!this.currentSpec || !this.currentEndpoints.length) {
throw new Error('No OpenAPI specification loaded. Please load a spec first.');
}
const { format = 'markdown', includeExamples = true, includeAnalytics = false } = args;
if (format === 'summary') {
return {
content: [
{
type: 'text',
text: `# ${this.currentSpec.info.title} - API Summary
**Version:** ${this.currentSpec.info.version}
**Total Endpoints:** ${this.currentEndpoints.length}
## Endpoints by Method
${Object.entries(generateAnalytics(this.currentEndpoints).methodDistribution)
.map(([method, count]) => `- ${method}: ${count}`)
.join('\n')}
## All Endpoints
${this.currentEndpoints.map(ep => `- ${ep.method} ${ep.path}${ep.summary ? ` - ${ep.summary}` : ''}`).join('\n')}`,
},
],
};
}
if (format === 'json') {
const exportData = {
api: {
title: this.currentSpec.info.title,
version: this.currentSpec.info.version,
description: this.currentSpec.info.description,
},
endpoints: this.currentEndpoints.map(ep => ({
method: ep.method,
path: ep.path,
summary: ep.summary,
description: ep.description,
tags: ep.tags,
deprecated: ep.deprecated,
complexity: ep.complexity,
parameters: ep.parameters.length,
hasRequestBody: !!ep.requestBody,
responseCodes: Object.keys(ep.responses),
})),
...(includeAnalytics && { analytics: generateAnalytics(this.currentEndpoints) }),
};
return {
content: [
{
type: 'text',
text: JSON.stringify(exportData, null, 2),
},
],
};
}
// Markdown format (default)
let markdown = `# ${this.currentSpec.info.title}
**Version:** ${this.currentSpec.info.version}
**OpenAPI Version:** ${this.currentSpec.openapi}
${this.currentSpec.info.description ? `## Description\n${this.currentSpec.info.description}\n` : ''}
## Endpoints (${this.currentEndpoints.length})
`;
for (const endpoint of this.currentEndpoints) {
markdown += `### ${endpoint.method} ${endpoint.path}\n\n`;
if (endpoint.summary) {
markdown += `**Summary:** ${endpoint.summary}\n\n`;
}
if (endpoint.description) {
markdown += `**Description:** ${endpoint.description}\n\n`;
}
if (endpoint.tags.length > 0) {
markdown += `**Tags:** ${endpoint.tags.join(', ')}\n\n`;
}
if (endpoint.deprecated) {
markdown += `⚠️ **This endpoint is deprecated**\n\n`;
}
if (endpoint.parameters.length > 0) {
markdown += `**Parameters:**\n`;
endpoint.parameters.forEach(param => {
markdown += `- \`${param.name}\` (${param.in}) - ${param.description || 'No description'}\n`;
});
markdown += '\n';
}
markdown += `**Responses:**\n`;
Object.entries(endpoint.responses).forEach(([code, response]) => {
markdown += `- \`${code}\` - ${response.description}\n`;
});
markdown += '\n';
if (includeExamples) {
markdown += `**Example:**\n\`\`\`bash\n${this.generateCurlExample(endpoint)}\n\`\`\`\n\n`;
}
markdown += '---\n\n';
}
if (includeAnalytics) {
const analytics = generateAnalytics(this.currentEndpoints);
markdown += `## Analytics
**Total Endpoints:** ${analytics.totalEndpoints}
**Deprecated:** ${analytics.deprecatedCount}
**Average Parameters:** ${analytics.averageParametersPerEndpoint.toFixed(1)}
### Method Distribution
${Object.entries(analytics.methodDistribution)
.map(([method, count]) => `- ${method}: ${count}`)
.join('\n')}
### Complexity Distribution
${Object.entries(analytics.complexityDistribution)
.map(([complexity, count]) => `- ${complexity}: ${count}`)
.join('\n')}
`;
}
return {
content: [
{
type: 'text',
text: markdown,
},
],
};
}
async searchRequestBodyProperties(args) {
if (!this.currentSpec) {
return {
content: [{ type: 'text', text: 'No OpenAPI specification loaded. Please load a spec first.' }],
isError: true,
};
}
const { propertyName, propertyType, schemaPattern, required, methods } = args;
const results = [];
// Helper function to search properties in a schema
const searchInSchema = (schema, path = []) => {
const matches = [];
if (!schema || typeof schema !== 'object')
return matches;
// Handle $ref
if (schema.$ref) {
const refName = schema.$ref.split('/').pop();
if (this.currentSpec?.components?.schemas?.[refName]) {
return searchInSchema(this.currentSpec.components.schemas[refName], [...path, refName]);
}
return matches;
}
// Search in properties
if (schema.properties) {
Object.entries(schema.properties).forEach(([propName, propSchema]) => {
const currentPath = [...path, propName];
const isRequired = schema.required?.includes(propName) || false;
// Check if this property matches our search criteria
let matches_criteria = true;
if (propertyName && !propName.toLowerCase().includes(propertyName.toLowerCase())) {
matches_criteria = false;
}
if (propertyType && propSchema.type !== propertyType) {
matches_criteria = false;
}
if (required !== undefined && isRequired !== required) {
matches_criteria = false;
}
if (schemaPattern) {
const regex = new RegExp(schemaPattern, 'i');
if (!regex.test(propSchema.description || '') && !regex.test(propName)) {
matches_criteria = false;
}
}
if (matches_criteria) {
matches.push({
propertyName: propName,
propertyType: propSchema.type || 'unknown',
path: currentPath.join('.'),
required: isRequired,
description: propSchema.description,
format: propSchema.format,
example: propSchema.example,
enum: propSchema.enum,
schema: propSchema
});
}
// Recursively search nested objects
if (propSchema.type === 'object' || propSchema.properties) {
matches.push(...searchInSchema(propSchema, currentPath));
}
// Search in array items
if (propSchema.type === 'array' && propSchema.items) {
matches.push(...searchInSchema(propSchema.items, [...currentPath, '[items]']));
}
});
}
// Search in oneOf, anyOf, allOf
['oneOf', 'anyOf', 'allOf'].forEach(keyword => {
if (schema[keyword] && Array.isArray(schema[keyword])) {
schema[keyword].forEach((subSchema, index) => {
matches.push(...searchInSchema(subSchema, [...path, `${keyword}[${index}]`]));
});
}
});
return matches;
};
// Search through all endpoints with request bodies
this.currentEndpoints.forEach(endpoint => {
if (methods && !methods.includes(endpoint.method.toUpperCase())) {
return;
}
if (endpoint.requestBody?.content) {
Object.entries(endpoint.requestBody.content).forEach(([mediaType, content]) => {
if (content.schema) {
const properties = searchInSchema(content.schema);
if (properties.length > 0) {
results.push({
endpoint: `${endpoint.method.toUpperCase()} ${endpoint.path}`,
mediaType,
properties
});
}
}
});
}
});
return {
content: [{
type: 'text',
text: JSON.stringify({
searchCriteria: { propertyName, propertyType, schemaPattern, required, methods },
totalMatches: results.reduce((sum, r) => sum + r.properties.length, 0),
results
}, null, 2)
}],