Skip to main content
Glama
openapi.service.ts8.58 kB
import { Injectable, Logger, BadRequestException, InternalServerErrorException, NotFoundException } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { AppConfigService } from '../../../config/app-config.service'; import { ParserService } from './parser.service'; import { ValidatorService } from './validator.service'; import { ConfigureOpenAPIDto } from '../dto/configure-openapi.dto'; import { OpenAPIResponseDto } from '../dto/openapi-response.dto'; import { MCPServerEntity } from '../../../database/entities/mcp-server.entity'; import { UrlParser, FileParser, TextParser } from 'mcp-swagger-parser'; import { log } from 'util'; @Injectable() export class OpenAPIService { private readonly logger = new Logger(OpenAPIService.name); constructor( private readonly configService: ConfigService, private readonly appConfigService: AppConfigService, private readonly parserService: ParserService, private readonly validatorService: ValidatorService, @InjectRepository(MCPServerEntity) private readonly serverRepository: Repository<MCPServerEntity>, ) { } async parseOpenAPI(configDto: ConfigureOpenAPIDto): Promise<OpenAPIResponseDto> { try { this.logger.log(`Starting OpenAPI parsing for source type: ${configDto.source.type}`); // 1. Load raw OpenAPI specification (without parsing) const spec = await this.loadOpenAPISpec(configDto); // 3. Parse the validated specification const parsedSpec = await this.parserService.parseSpecification(spec, configDto.options); // 4. Generate MCP tools from parsed specification const tools = await this.parserService.generateMCPTools(parsedSpec, configDto.options); // 5. Build response const response: OpenAPIResponseDto = { info: { title: parsedSpec.info?.title || 'Untitled API', version: parsedSpec.info?.version || '1.0.0', description: parsedSpec.info?.description, termsOfService: parsedSpec.info?.termsOfService, contact: parsedSpec.info?.contact, license: parsedSpec.info?.license, }, paths: parsedSpec.paths || {}, endpoints: parsedSpec.endpoints || [], tools: tools || [], servers: parsedSpec.servers || [], openapi: parsedSpec.openapi || parsedSpec.swagger || '3.0.0', components: parsedSpec.components || {}, parsedAt: new Date().toISOString(), parseId: this.generateParseId(), }; this.logger.log(`Successfully parsed OpenAPI specification with ${response.endpoints.length} endpoints and ${response.tools.length} tools`); return response; } catch (error) { this.logger.error(`Failed to parse OpenAPI specification: ${error.message}`, error.stack); if (error instanceof BadRequestException) { throw error; } throw new InternalServerErrorException(`OpenAPI parsing failed: ${error.message}`); } } async validateOpenAPI(configDto: ConfigureOpenAPIDto): Promise<{ valid: boolean; errors: string[]; warnings: string[]; }> { try { this.logger.log(`Starting OpenAPI validation for source type: ${configDto.source.type}`); // Check if content is empty if (configDto.source.content == "") { throw new Error("验证文档不能为空") } else { try { const content = JSON.parse(configDto.source.content) // 传入的就是原始的编辑器中的openapi规范 //const spec = await this.loadOpenAPISpec(configDto); console.log("Loaded spec for validation:", content); // Validate the parsed specification const result = await this.validatorService.validateSpecification(content); this.logger.log(`OpenAPI validation completed: ${result.valid ? 'valid' : 'invalid'} with ${result.errors.length} errors and ${result.warnings.length} warnings`); return result; } catch (e) { } } } catch (error) { this.logger.error(`Failed to validate OpenAPI specification: ${error.message}`, error.stack); return { valid: false, errors: [error.message], warnings: [], }; } } async normalizeOpenAPI(configDto: ConfigureOpenAPIDto): Promise<{ normalized: any; format: string; version: string; }> { try { this.logger.log(`Starting OpenAPI normalization for source type: ${configDto.source.type}`); // Load OpenAPI specification const spec = await this.loadOpenAPISpec(configDto); // Normalize the specification const normalized = await this.parserService.normalizeSpecification(spec); const format = normalized.openapi ? 'openapi' : 'swagger'; const version = normalized.openapi || normalized.swagger || '3.0.0'; this.logger.log(`Successfully normalized OpenAPI specification to ${format} ${version}`); return { normalized, format, version, }; } catch (error) { this.logger.error(`Failed to normalize OpenAPI specification: ${error.message}`, error.stack); throw new BadRequestException(`OpenAPI normalization failed: ${error.message}`); } } private async loadOpenAPISpec(configDto: ConfigureOpenAPIDto): Promise<any> { const { source } = configDto; switch (source.type) { case 'url': return this.loadFromUrl(source.content); case 'file': return this.loadFromFile(source.content); case 'content': return this.loadFromContent(source.content); default: throw new BadRequestException(`Unsupported source type: ${source.type}`); } } private async loadFromUrl(url: string): Promise<any> { try { this.logger.log(`Loading OpenAPI specification from URL: ${url}`); // Use ParserService to parse from URL directly const result = await this.parserService.parseFromUrl(url); return result.spec; } catch (error) { this.logger.error(`Failed to load OpenAPI specification from URL: ${error.message}`); throw new BadRequestException(`Failed to load from URL: ${error.message}`); } } private async loadFromFile(filePath: string): Promise<any> { try { this.logger.log(`Loading OpenAPI specification from file: ${filePath}`); // Use ParserService to parse from file directly const parseResult = await this.parserService.parseFromFile(filePath); return parseResult.spec; } catch (error) { this.logger.error(`Failed to load OpenAPI specification from file: ${error.message}`); throw new BadRequestException(`Failed to load from file: ${error.message}`); } } private async loadFromContent(content: string): Promise<any> { try { this.logger.log(`Loading OpenAPI specification from content`); // Use ParserService to parse from string directly const parseResult = await this.parserService.parseFromString(content); return parseResult.spec; } catch (error) { this.logger.error(`Failed to load OpenAPI specification from content: ${error.message}`); throw new BadRequestException(`Failed to parse content: ${error.message}`); } } async getOpenApiByServerId(serverId: string): Promise<any> { try { this.logger.log(`Retrieving OpenAPI document for server ID: ${serverId}`); // 从数据库中查询服务器实体 const server = await this.serverRepository.findOne({ where: { id: serverId }, select: ['id', 'name', 'openApiData'] }); if (!server) { throw new NotFoundException(`Server with ID ${serverId} not found`); } if (!server.openApiData) { throw new NotFoundException(`No OpenAPI document found for server ${serverId}`); } this.logger.log(`Successfully retrieved OpenAPI document for server: ${server.name}`); // 直接返回 openApiData,它已经是 JSON 格式 return server.openApiData; } catch (error) { this.logger.error(`Failed to retrieve OpenAPI document for server ID ${serverId}: ${error.message}`, error.stack); if (error instanceof NotFoundException) { throw error; } throw new InternalServerErrorException(`Failed to retrieve OpenAPI document: ${error.message}`); } } private generateParseId(): string { return `parse_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; } }

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/zaizaizhao/mcp-swagger-server'

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