Skip to main content
Glama
openapi-parser.ts5.23 kB
import SwaggerParser from '@apidevtools/swagger-parser'; import path from 'path'; import { ParsedSpec, GeneratedTool, ToolParameter } from '../types'; import { getApiNameFromFile } from './auth'; export async function parseOpenAPISpec(filePath: string): Promise<ParsedSpec | null> { try { const spec = await SwaggerParser.validate(filePath) as any; // OpenAPIV3.Document; const apiName = getApiNameFromFile(filePath); const tools = generateToolsFromSpec(spec, apiName); return { apiName, filePath, spec, tools }; } catch (error) { // Only log errors for files that look like they should be OpenAPI specs const filename = path.basename(filePath); if (filename.toLowerCase().includes('openapi') || filename.toLowerCase().includes('swagger') || filename.toLowerCase().includes('api')) { console.error(`Failed to parse OpenAPI spec at ${filePath}:`, error); } else { // For other files, just log a brief message console.log(`Skipping ${filename} - not a valid OpenAPI specification`); } return null; } } function generateToolsFromSpec(spec: any, apiName: string): GeneratedTool[] { const tools: GeneratedTool[] = []; const baseUrl = getBaseUrl(spec); if (!spec.paths) return tools; for (const [pathTemplate, pathItem] of Object.entries(spec.paths)) { if (!pathItem || typeof pathItem !== 'object') continue; const methods = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options'] as const; for (const method of methods) { const operation = (pathItem as any)[method]; if (!operation) continue; const tool = createToolFromOperation( apiName, pathTemplate, method, operation, baseUrl, (pathItem as any).parameters ); if (tool) { tools.push(tool); } } } return tools; } function createToolFromOperation( apiName: string, pathTemplate: string, method: string, operation: any, baseUrl: string, pathLevelParams?: any[] ): GeneratedTool | null { try { const toolName = generateToolName(apiName, operation, pathTemplate, method); const description = operation.summary || operation.description || `${method.toUpperCase()} ${pathTemplate}`; // Combine path-level and operation-level parameters const allParams = [ ...(pathLevelParams || []), ...(operation.parameters || []) ]; const parameters = extractParameters(allParams); return { name: toolName, description, operationId: operation.operationId, method: method.toUpperCase(), path: pathTemplate, parameters, requestBody: operation.requestBody as any, responses: operation.responses, security: operation.security || [], baseUrl }; } catch (error) { console.error(`Failed to create tool for ${method} ${pathTemplate}:`, error); return null; } } function generateToolName( apiName: string, operation: any, pathTemplate: string, method: string ): string { if (operation.operationId) { return `${apiName}_${operation.operationId}`; } // Generate from path and method const pathParts = pathTemplate .split('/') .filter(part => part && !part.startsWith('{')) .map(part => part.replace(/[^a-zA-Z0-9]/g, '')); const pathName = pathParts.join('_') || 'root'; return `${apiName}_${method}_${pathName}`; } function extractParameters( paramRefs: any[] ): ToolParameter[] { const parameters: ToolParameter[] = []; for (const paramRef of paramRefs) { // For simplicity, we're not resolving $ref parameters here // In a production version, you'd want to resolve references if ('$ref' in paramRef) { continue; // Skip reference parameters for now } const param = paramRef as any; if (param.in && param.name && param.schema) { parameters.push({ name: param.name, in: param.in as 'path' | 'query' | 'header' | 'cookie', required: param.required || param.in === 'path', schema: param.schema as any, description: param.description }); } } return parameters; } function getBaseUrl(spec: any): string { if (spec.servers && spec.servers.length > 0) { return spec.servers[0].url; } // Fallback for older specs or specs without servers return 'https://api.example.com'; } export function isOpenAPIFile(filePath: string): boolean { const ext = path.extname(filePath).toLowerCase(); const filename = path.basename(filePath).toLowerCase(); // Only accept certain extensions if (!['.json', '.yaml', '.yml'].includes(ext)) { return false; } // Skip common non-OpenAPI files const skipFiles = [ 'package.json', 'package-lock.json', 'tsconfig.json', '.eslintrc.json', 'jest.config.json', 'webpack.config.js', 'rollup.config.js' ]; if (skipFiles.includes(filename)) { return false; } return true; }

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/TBosak/specbridge'

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