Skip to main content
Glama
openapi-generator.ts20.7 kB
/** * OpenAPI 3.0 Specification Generator * * Automatically generates OpenAPI 3.0 documentation from the tool registry. * Creates comprehensive API documentation with proper schemas and examples. */ import { RestToolRegistry, ToolInfo } from './rest-registry'; import { Logger } from '../core/error-handling'; /** * OpenAPI 3.0 specification structure */ export interface OpenApiSpec { openapi: string; info: { title: string; description: string; version: string; contact?: { name: string; url: string; email: string; }; license?: { name: string; url: string; }; }; servers: Array<{ url: string; description: string; }>; paths: Record<string, any>; components?: { schemas?: Record<string, any>; responses?: Record<string, any>; securitySchemes?: Record<string, any>; }; tags?: Array<{ name: string; description: string; }>; } /** * OpenAPI generator configuration */ export interface OpenApiConfig { title: string; description: string; version: string; basePath: string; apiVersion: string; serverUrl?: string; } /** * OpenAPI 3.0 specification generator */ export class OpenApiGenerator { private registry: RestToolRegistry; private logger: Logger; private config: OpenApiConfig; private specCache?: OpenApiSpec; private lastCacheUpdate: number = 0; private cacheValidityMs: number = 60000; // 1 minute constructor(registry: RestToolRegistry, logger: Logger, config: Partial<OpenApiConfig> = {}) { this.registry = registry; this.logger = logger; this.config = { title: 'DevOps AI Toolkit REST API', description: 'REST API gateway for DevOps AI Toolkit MCP tools - provides HTTP access to all AI-powered DevOps automation capabilities', version: '1.0.0', basePath: '/api', apiVersion: 'v1', serverUrl: 'http://localhost:3456', ...config }; } /** * Generate complete OpenAPI 3.0 specification */ generateSpec(): OpenApiSpec { // Check cache validity const now = Date.now(); if (this.specCache && (now - this.lastCacheUpdate) < this.cacheValidityMs) { this.logger.debug('Returning cached OpenAPI specification'); return this.specCache; } this.logger.info('Generating OpenAPI 3.0 specification', { toolCount: this.registry.getToolCount(), cacheExpired: !this.specCache || (now - this.lastCacheUpdate) >= this.cacheValidityMs }); const tools = this.registry.getAllTools(); const categories = this.registry.getCategories(); const spec: OpenApiSpec = { openapi: '3.0.0', info: this.generateInfo(), servers: this.generateServers(), paths: this.generatePaths(tools), components: this.generateComponents(tools), tags: this.generateTags(categories) }; // Cache the generated spec this.specCache = spec; this.lastCacheUpdate = now; this.logger.info('OpenAPI specification generated successfully', { pathCount: Object.keys(spec.paths).length, componentCount: Object.keys(spec.components?.schemas || {}).length, tagCount: spec.tags?.length || 0 }); return spec; } /** * Generate API info section */ private generateInfo(): OpenApiSpec['info'] { const info: OpenApiSpec['info'] = { title: this.config.title, description: this.config.description, version: this.config.version, contact: { name: 'Viktor Farcic', url: 'https://devopstoolkit.live/', email: 'viktor@farcic.com' }, license: { name: 'MIT', url: 'https://github.com/vfarcic/dot-ai/blob/main/LICENSE' } }; return info; } /** * Generate server definitions */ private generateServers(): OpenApiSpec['servers'] { return [ { url: this.config.serverUrl || 'http://localhost:3456', description: 'DevOps AI Toolkit MCP Server' } ]; } /** * Generate paths for all endpoints */ private generatePaths(tools: ToolInfo[]): Record<string, any> { const paths: Record<string, any> = {}; const basePath = `${this.config.basePath}/${this.config.apiVersion}`; // MCP Protocol Endpoints paths['/'] = { get: { summary: 'Open MCP SSE stream', description: 'Opens a Server-Sent Events (SSE) stream for Model Context Protocol communication. This endpoint allows the server to push messages to the client without the client first sending data.', tags: ['MCP Protocol'], parameters: [], responses: { 200: { description: 'SSE stream opened successfully', content: { 'text/event-stream': { schema: { type: 'string', description: 'Server-Sent Events stream' } } } }, 405: { description: 'Method not allowed - server does not support SSE', content: { 'application/json': { schema: { $ref: '#/components/schemas/ErrorResponse' } } } } } }, post: { summary: 'Send MCP JSON-RPC message', description: 'Send a JSON-RPC message using Model Context Protocol. Used for tool calls, initialization, and other MCP operations. The server may respond with either a JSON object or open an SSE stream.', tags: ['MCP Protocol'], requestBody: { required: true, content: { 'application/json': { schema: { $ref: '#/components/schemas/McpJsonRpcRequest' }, examples: { initialize: { summary: 'Initialize MCP session', value: { jsonrpc: '2.0', id: 1, method: 'initialize', params: { protocolVersion: '2024-11-05', capabilities: {}, clientInfo: { name: 'example-client', version: '1.0.0' } } } }, toolCall: { summary: 'Call a tool', value: { jsonrpc: '2.0', id: 2, method: 'tools/call', params: { name: 'version', arguments: {} } } } } } } }, responses: { 200: { description: 'JSON-RPC response or SSE stream', content: { 'application/json': { schema: { $ref: '#/components/schemas/McpJsonRpcResponse' } }, 'text/event-stream': { schema: { type: 'string', description: 'Server-Sent Events stream with JSON-RPC messages' } } } }, 400: { description: 'Bad request - invalid JSON-RPC message', content: { 'application/json': { schema: { $ref: '#/components/schemas/McpJsonRpcError' } } } } } } }; // Tool discovery endpoint paths[`${basePath}/tools`] = { get: { summary: 'Discover available tools', description: 'Get a list of all available tools with their schemas and metadata', tags: ['Tool Discovery'], parameters: [ { name: 'category', in: 'query', description: 'Filter tools by category', required: false, schema: { type: 'string' } }, { name: 'tag', in: 'query', description: 'Filter tools by tag', required: false, schema: { type: 'string' } }, { name: 'search', in: 'query', description: 'Search tools by name or description', required: false, schema: { type: 'string' } } ], responses: { 200: { description: 'List of available tools', content: { 'application/json': { schema: { $ref: '#/components/schemas/ToolDiscoveryResponse' } } } } } } }; // Individual tool execution endpoints for (const tool of tools) { paths[`${basePath}/tools/${tool.name}`] = { post: { summary: `Execute ${tool.name} tool`, description: tool.description, tags: [tool.category || 'Tools'], requestBody: { required: true, content: { 'application/json': { schema: { $ref: `#/components/schemas/${tool.name}Request` }, example: this.generateExampleForTool(tool) } } }, responses: { 200: { description: 'Tool execution result', content: { 'application/json': { schema: { $ref: '#/components/schemas/ToolExecutionResponse' } } } }, 400: { description: 'Bad request - invalid parameters', content: { 'application/json': { schema: { $ref: '#/components/schemas/ErrorResponse' } } } }, 404: { description: 'Tool not found', content: { 'application/json': { schema: { $ref: '#/components/schemas/ErrorResponse' } } } }, 500: { description: 'Internal server error', content: { 'application/json': { schema: { $ref: '#/components/schemas/ErrorResponse' } } } } } } }; } // OpenAPI specification endpoint paths[`${basePath}/openapi`] = { get: { summary: 'Get OpenAPI specification', description: 'Returns the complete OpenAPI 3.0 specification for this API', tags: ['Documentation'], responses: { 200: { description: 'OpenAPI specification', content: { 'application/json': { schema: { type: 'object', description: 'OpenAPI 3.0 specification' } } } } } } }; return paths; } /** * Generate component schemas */ private generateComponents(tools: ToolInfo[]): OpenApiSpec['components'] { const schemas: Record<string, any> = {}; // Base response schemas schemas.RestApiResponse = { type: 'object', properties: { success: { type: 'boolean', description: 'Whether the request was successful' }, data: { type: 'object', description: 'Response data' }, error: { type: 'object', properties: { code: { type: 'string', description: 'Error code' }, message: { type: 'string', description: 'Error message' }, details: { type: 'object', description: 'Additional error details' } } }, meta: { type: 'object', properties: { timestamp: { type: 'string', format: 'date-time', description: 'Response timestamp' }, requestId: { type: 'string', description: 'Unique request identifier' }, version: { type: 'string', description: 'API version' } } } }, required: ['success'] }; schemas.ToolExecutionResponse = { allOf: [ { $ref: '#/components/schemas/RestApiResponse' }, { type: 'object', properties: { data: { type: 'object', properties: { result: { type: 'object', description: 'Tool execution result' }, tool: { type: 'string', description: 'Name of the executed tool' }, executionTime: { type: 'number', description: 'Execution time in milliseconds' } } } } } ] }; schemas.ToolDiscoveryResponse = { allOf: [ { $ref: '#/components/schemas/RestApiResponse' }, { type: 'object', properties: { data: { type: 'object', properties: { tools: { type: 'array', items: { $ref: '#/components/schemas/ToolInfo' } }, total: { type: 'number', description: 'Total number of tools' }, categories: { type: 'array', items: { type: 'string' }, description: 'Available tool categories' }, tags: { type: 'array', items: { type: 'string' }, description: 'Available tool tags' } } } } } ] }; schemas.ToolInfo = { type: 'object', properties: { name: { type: 'string', description: 'Tool name' }, description: { type: 'string', description: 'Tool description' }, schema: { type: 'object', description: 'Tool input schema' }, category: { type: 'string', description: 'Tool category' }, tags: { type: 'array', items: { type: 'string' }, description: 'Tool tags' } }, required: ['name', 'description', 'schema'] }; schemas.ErrorResponse = { allOf: [ { $ref: '#/components/schemas/RestApiResponse' }, { type: 'object', properties: { success: { type: 'boolean', enum: [false] }, error: { type: 'object', properties: { code: { type: 'string' }, message: { type: 'string' }, details: { type: 'object' } }, required: ['code', 'message'] } }, required: ['error'] } ] }; // MCP JSON-RPC schemas schemas.McpJsonRpcRequest = { type: 'object', description: 'JSON-RPC 2.0 request message for MCP protocol', properties: { jsonrpc: { type: 'string', enum: ['2.0'], description: 'JSON-RPC version' }, id: { type: ['number', 'string', 'null'], description: 'Request identifier' }, method: { type: 'string', description: 'Method name (e.g., initialize, tools/call, tools/list)' }, params: { type: 'object', description: 'Method parameters' } }, required: ['jsonrpc', 'method'] }; schemas.McpJsonRpcResponse = { type: 'object', description: 'JSON-RPC 2.0 response message', properties: { jsonrpc: { type: 'string', enum: ['2.0'], description: 'JSON-RPC version' }, id: { type: ['number', 'string', 'null'], description: 'Request identifier' }, result: { type: 'object', description: 'Method result' }, error: { type: 'object', properties: { code: { type: 'number', description: 'Error code' }, message: { type: 'string', description: 'Error message' }, data: { type: 'object', description: 'Additional error data' } } } }, required: ['jsonrpc', 'id'] }; schemas.McpJsonRpcError = { type: 'object', description: 'JSON-RPC 2.0 error response', properties: { jsonrpc: { type: 'string', enum: ['2.0'], description: 'JSON-RPC version' }, id: { type: ['number', 'string', 'null'], description: 'Request identifier' }, error: { type: 'object', properties: { code: { type: 'number', description: 'Error code' }, message: { type: 'string', description: 'Error message' }, data: { type: 'object', description: 'Additional error data' } }, required: ['code', 'message'] } }, required: ['jsonrpc', 'id', 'error'] }; // Individual tool request schemas for (const tool of tools) { schemas[`${tool.name}Request`] = tool.schema; } return { schemas }; } /** * Generate tags for grouping endpoints */ private generateTags(categories: string[]): OpenApiSpec['tags'] { const tags: OpenApiSpec['tags'] = [ { name: 'MCP Protocol', description: 'Model Context Protocol endpoints for AI assistant integration via JSON-RPC and Server-Sent Events' }, { name: 'Tool Discovery', description: 'Endpoints for discovering available tools and their capabilities' }, { name: 'Documentation', description: 'API documentation and specification endpoints' } ]; // Add category-based tags for (const category of categories) { tags.push({ name: category, description: `${category} tools and operations` }); } // Add generic tools tag for uncategorized tools if (this.registry.getAllTools().some(tool => !tool.category)) { tags.push({ name: 'Tools', description: 'General purpose tools and utilities' }); } return tags; } /** * Generate example request body for a tool */ private generateExampleForTool(tool: ToolInfo): any { const example: any = {}; try { const schema = tool.schema; if (schema.properties) { for (const [propName, propSchema] of Object.entries(schema.properties)) { example[propName] = this.generateExampleValue(propSchema as any, propName); } } } catch (error) { this.logger.warn('Failed to generate example for tool', { toolName: tool.name, error: error instanceof Error ? error.message : String(error) }); } return example; } /** * Generate example value for a property schema */ private generateExampleValue(propSchema: any, propName: string): any { if (propSchema.example !== undefined) { return propSchema.example; } switch (propSchema.type) { case 'string': if (propSchema.enum) { return propSchema.enum[0]; } if (propName.toLowerCase().includes('email')) { return 'user@example.com'; } if (propName.toLowerCase().includes('url')) { return 'https://example.com'; } if (propName.toLowerCase().includes('name')) { return `example ${propName}`; } if (propName.toLowerCase().includes('intent')) { return 'deploy web application with PostgreSQL database'; } return `example ${propName}`; case 'number': case 'integer': if (propName.toLowerCase().includes('port')) { return 8080; } if (propName.toLowerCase().includes('timeout')) { return 30; } return 42; case 'boolean': return false; case 'array': return [this.generateExampleValue(propSchema.items, 'item')]; case 'object': { const objExample: any = {}; if (propSchema.properties) { for (const [subPropName, subPropSchema] of Object.entries(propSchema.properties)) { objExample[subPropName] = this.generateExampleValue(subPropSchema as any, subPropName); } } return objExample; } default: return `example ${propName}`; } } /** * Invalidate the specification cache */ invalidateCache(): void { this.specCache = undefined; this.lastCacheUpdate = 0; this.logger.debug('OpenAPI specification cache invalidated'); } /** * Update configuration */ updateConfig(newConfig: Partial<OpenApiConfig>): void { this.config = { ...this.config, ...newConfig }; this.invalidateCache(); this.logger.info('OpenAPI generator configuration updated'); } /** * Get current configuration */ getConfig(): OpenApiConfig { return { ...this.config }; } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/vfarcic/dot-ai'

If you have feedback or need assistance with the MCP directory API, please join our Discord server