Skip to main content
Glama
parser.service.ts10.7 kB
import { Injectable, Logger } from '@nestjs/common'; import { AppConfigService } from '../../../config/app-config.service'; import { parseFromUrl as parserParseFromUrl, parseFromFile as parserParseFromFile, parseFromString as parserParseFromString, validate as parserValidate, parseAndTransform as parserParseAndTransform, ParseResult, ValidationResult, ApiEndpoint, ParsedApiSpec } from 'mcp-swagger-parser'; interface ParserOptions { strictMode?: boolean; resolveReferences?: boolean; validateSchema?: boolean; baseUrl?: string; } @Injectable() export class ParserService { private readonly logger = new Logger(ParserService.name); constructor(private readonly configService: AppConfigService) {} /** * 从URL解析OpenAPI规范 */ async parseFromUrl(url: string, options: ParserOptions = {}): Promise<ParseResult> { try { this.logger.log(`Parsing OpenAPI spec from URL: ${url}`); const result = await parserParseFromUrl(url, options); this.logger.log(`Successfully parsed OpenAPI spec from URL: ${url}`); return result; } catch (error) { this.logger.error(`Failed to parse OpenAPI spec from URL: ${url}`, error); throw new Error(`Parse failed: ${error.message}`); } } /** * 从文件解析OpenAPI规范 */ async parseFromFile(filePath: string, options: ParserOptions = {}): Promise<ParseResult> { try { this.logger.log(`Parsing OpenAPI spec from file: ${filePath}`); const result = await parserParseFromFile(filePath, options); this.logger.log(`Successfully parsed OpenAPI spec from file: ${filePath}`); return result; } catch (error) { this.logger.error(`Failed to parse OpenAPI spec from file: ${filePath}`, error); throw new Error(`Parse failed: ${error.message}`); } } /** * 从字符串内容解析OpenAPI规范 */ async parseFromString(content: string, options: ParserOptions = {}): Promise<ParseResult> { try { this.logger.log('Parsing OpenAPI spec from string content'); const result = await parserParseFromString(content, options); this.logger.log('Successfully parsed OpenAPI spec from string content'); return result; } catch (error) { this.logger.error('Failed to parse OpenAPI spec from string', error); throw new Error(`Parse failed: ${error.message}`); } } /** * 验证OpenAPI规范 */ async validateSpec(spec: any): Promise<ValidationResult> { try { this.logger.log('Validating OpenAPI specification'); const result = await parserValidate(spec); this.logger.log(`Validation completed: ${result.valid ? 'valid' : 'invalid'} (${result.errors.length} errors, ${result.warnings.length} warnings)`); return result; } catch (error) { this.logger.error('Failed to validate OpenAPI spec', error); throw new Error(`Validation failed: ${error.message}`); } } /** * 使用mcp-swagger-parser的高层解析和转换功能 */ async parseAndTransform(input: string, inputType: 'url' | 'file' | 'content', options?: ParserOptions): Promise<any> { try { this.logger.log(`Parsing and transforming OpenAPI spec from ${inputType}`); const result = await parserParseAndTransform(input, { isString: inputType === 'content', isUrl: inputType === 'url' }); this.logger.log(`Successfully parsed and transformed OpenAPI spec from ${inputType}`); return result; } catch (error) { this.logger.error(`Failed to parse and transform OpenAPI spec from ${inputType}`, error); throw new Error(`Parse and transform failed: ${error.message}`); } } /** * 提取API基本信息 */ extractApiInfo(spec: any): any { return { title: spec.info?.title || 'Unknown API', version: spec.info?.version || '1.0.0', description: spec.info?.description, termsOfService: spec.info?.termsOfService, contact: spec.info?.contact, license: spec.info?.license, servers: spec.servers, externalDocs: spec.externalDocs, }; } /** * 提取API端点信息 */ extractEndpoints(spec: any): ApiEndpoint[] { const endpoints: ApiEndpoint[] = []; if (!spec.paths) { return endpoints; } for (const [path, pathItem] of Object.entries(spec.paths)) { if (typeof pathItem !== 'object' || pathItem === null) continue; for (const [method, operation] of Object.entries(pathItem)) { if (!['get', 'post', 'put', 'delete', 'patch', 'head', 'options', 'trace'].includes(method)) { continue; } if (typeof operation !== 'object' || operation === null) continue; endpoints.push({ method: method.toUpperCase() as any, path, operationId: operation.operationId || `${method}_${path.replace(/[^a-zA-Z0-9]/g, '_')}`, summary: operation.summary, description: operation.description, tags: operation.tags || [], deprecated: operation.deprecated || false, parameters: operation.parameters || [], requestBody: operation.requestBody, responses: operation.responses || {}, }); } } return endpoints; } /** * 解析完整的OpenAPI规范 */ async parseSpecification(spec: any, options?: any): Promise<any> { try { this.logger.log('Parsing OpenAPI specification'); // 标准化规范 const normalized = await this.normalizeSpecification(spec); // 提取基本信息 const info = this.extractApiInfo(normalized); // 提取端点(用于生成工具和统计) const endpoints = this.extractEndpoints(normalized); return { ...normalized, info, // 保持原始的paths对象格式,符合OpenAPI规范 paths: normalized.paths || {}, // 添加解析后的端点数组,用于前端显示和工具生成 endpoints, servers: normalized.servers || [], components: normalized.components || {}, openapi: normalized.openapi || normalized.swagger || '3.0.0', }; } catch (error) { this.logger.error(`Failed to parse specification: ${error.message}`, error.stack); throw error; } } /** * 生成MCP工具 */ async generateMCPTools(parsedSpec: any, options?: any): Promise<any[]> { try { this.logger.log('Generating MCP tools from parsed specification'); const tools = []; if (!parsedSpec.endpoints || !Array.isArray(parsedSpec.endpoints)) { this.logger.warn('No endpoints found in parsed specification'); return tools; } for (const endpoint of parsedSpec.endpoints) { const tool = { name: endpoint.operationId || `${endpoint.method.toLowerCase()}_${endpoint.path.replace(/[^a-zA-Z0-9]/g, '_')}`, description: endpoint.summary || endpoint.description || `${endpoint.method} ${endpoint.path}`, inputSchema: this.generateInputSchema(endpoint), metadata: { method: endpoint.method, path: endpoint.path, tags: endpoint.tags || [], deprecated: endpoint.deprecated || false, }, }; tools.push(tool); } this.logger.log(`Generated ${tools.length} MCP tools`); return tools; } catch (error) { this.logger.error(`Failed to generate MCP tools: ${error.message}`, error.stack); throw error; } } /** * 标准化OpenAPI规范 */ async normalizeSpecification(spec: any): Promise<any> { try { this.logger.log('Normalizing OpenAPI specification'); if (!spec || typeof spec !== 'object') { throw new Error('Invalid specification: must be an object'); } // 创建标准化的规范副本 const normalized = JSON.parse(JSON.stringify(spec)); // 确保基本字段存在 if (!normalized.info) { normalized.info = { title: 'Untitled API', version: '1.0.0', }; } if (!normalized.paths) { normalized.paths = {}; } // 标准化版本字段 if (normalized.swagger && !normalized.openapi) { // Swagger 2.0 转换为 OpenAPI 3.0 normalized.openapi = '3.0.0'; delete normalized.swagger; // 转换其他字段 if (normalized.host || normalized.basePath) { normalized.servers = [{ url: `${normalized.schemes?.[0] || 'https'}://${normalized.host || 'localhost'}${normalized.basePath || ''}`, }]; delete normalized.host; delete normalized.basePath; delete normalized.schemes; } if (normalized.definitions) { normalized.components = normalized.components || {}; normalized.components.schemas = normalized.definitions; delete normalized.definitions; } } this.logger.log('Successfully normalized OpenAPI specification'); return normalized; } catch (error) { this.logger.error(`Failed to normalize specification: ${error.message}`, error.stack); throw error; } } /** * 为端点生成输入Schema */ private generateInputSchema(endpoint: any): any { const properties: any = {}; const required: string[] = []; // 处理路径参数 if (endpoint.parameters) { for (const param of endpoint.parameters) { if (param.in === 'path') { properties[param.name] = { type: param.schema?.type || 'string', description: param.description, }; if (param.required) { required.push(param.name); } } else if (param.in === 'query') { properties[param.name] = { type: param.schema?.type || 'string', description: param.description, }; if (param.required) { required.push(param.name); } } } } // 处理请求体 if (endpoint.requestBody) { properties.body = { type: 'object', description: endpoint.requestBody.description || 'Request body', }; if (endpoint.requestBody.required) { required.push('body'); } } return { type: 'object', properties, required, }; } }

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