Skip to main content
Glama

OpenAPI MCP Server

by aaker
openapi-processor.js7.63 kB
import fs from 'fs/promises'; import path from 'path'; import Ajv from 'ajv'; import addFormats from 'ajv-formats'; export class OpenAPIProcessor { constructor() { this.ajv = new Ajv({ strict: false, allErrors: true }); addFormats(this.ajv); this.spec = null; this.tools = new Map(); } async loadSpec(specPath) { try { const fullPath = path.resolve(specPath); const specContent = await fs.readFile(fullPath, 'utf-8'); this.spec = JSON.parse(specContent); this.validateSpec(); this.generateTools(); return this.spec; } catch (error) { throw new Error(`Failed to load OpenAPI spec: ${error.message}`); } } validateSpec() { if (!this.spec) { throw new Error('No OpenAPI specification loaded'); } if (!this.spec.openapi || !this.spec.openapi.startsWith('3.')) { throw new Error('Only OpenAPI 3.x specifications are supported'); } if (!this.spec.paths || Object.keys(this.spec.paths).length === 0) { throw new Error('OpenAPI specification must contain at least one path'); } } generateTools() { this.tools.clear(); for (const [pathStr, pathObj] of Object.entries(this.spec.paths)) { for (const [method, operation] of Object.entries(pathObj)) { if (!this.isValidHttpMethod(method)) continue; const toolName = this.generateToolName(operation, method, pathStr); const tool = this.createToolDefinition(toolName, method, pathStr, operation); this.tools.set(toolName, tool); } } } isValidHttpMethod(method) { const validMethods = ['get', 'post', 'put', 'delete', 'patch', 'options', 'head']; return validMethods.includes(method.toLowerCase()); } generateToolName(operation, method, pathStr) { if (operation.operationId) { return operation.operationId; } // Generate name from method and path const cleanPath = pathStr .replace(/^\//, '') .replace(/[{}]/g, '') .replace(/[^a-zA-Z0-9]/g, '_') .replace(/_+/g, '_') .replace(/_$/, ''); return `${method}_${cleanPath}`; } createToolDefinition(toolName, method, pathStr, operation) { const tool = { name: toolName, description: operation.summary || operation.description || `${method.toUpperCase()} ${pathStr}`, inputSchema: { type: 'object', properties: {}, required: [] } }; // Add path parameters this.addPathParameters(tool, pathStr, operation); // Add query parameters this.addQueryParameters(tool, operation); // Add header parameters this.addHeaderParameters(tool, operation); // Add request body this.addRequestBody(tool, operation); return { ...tool, method: method.toLowerCase(), path: pathStr, operation }; } addPathParameters(tool, pathStr, operation) { const pathParams = pathStr.match(/{([^}]+)}/g) || []; pathParams.forEach(param => { const paramName = param.slice(1, -1); // Remove { } const paramDef = this.findParameterDefinition(operation, paramName, 'path'); tool.inputSchema.properties[paramName] = { type: 'string', description: paramDef?.description || `Path parameter: ${paramName}` }; tool.inputSchema.required.push(paramName); }); } addQueryParameters(tool, operation) { const queryParams = this.getParametersByLocation(operation, 'query'); queryParams.forEach(param => { const schema = this.resolveSchema(param.schema || { type: 'string' }); tool.inputSchema.properties[param.name] = { ...schema, description: param.description || `Query parameter: ${param.name}` }; if (param.required) { tool.inputSchema.required.push(param.name); } }); } addHeaderParameters(tool, operation) { const headerParams = this.getParametersByLocation(operation, 'header'); headerParams.forEach(param => { if (param.name.toLowerCase() === 'authorization') return; // Skip auth headers const schema = this.resolveSchema(param.schema || { type: 'string' }); tool.inputSchema.properties[`header_${param.name}`] = { ...schema, description: param.description || `Header parameter: ${param.name}` }; if (param.required) { tool.inputSchema.required.push(`header_${param.name}`); } }); } addRequestBody(tool, operation) { if (!operation.requestBody) return; const requestBody = this.resolveReference(operation.requestBody); const jsonContent = requestBody.content?.['application/json']; if (jsonContent?.schema) { const schema = this.resolveSchema(jsonContent.schema); if (schema.type === 'object') { // Merge request body properties into tool schema Object.entries(schema.properties || {}).forEach(([propName, propSchema]) => { tool.inputSchema.properties[propName] = propSchema; }); // Add required properties if (schema.required) { tool.inputSchema.required.push(...schema.required); } } else { // Non-object request body tool.inputSchema.properties.requestBody = { ...schema, description: requestBody.description || 'Request body' }; if (requestBody.required !== false) { tool.inputSchema.required.push('requestBody'); } } } } getParametersByLocation(operation, location) { const params = operation.parameters || []; return params .map(param => this.resolveReference(param)) .filter(param => param.in === location); } findParameterDefinition(operation, name, location) { const params = operation.parameters || []; return params .map(param => this.resolveReference(param)) .find(param => param.name === name && param.in === location); } resolveReference(obj) { if (!obj || !obj.$ref) return obj; const refPath = obj.$ref.replace('#/', '').split('/'); let resolved = this.spec; for (const segment of refPath) { resolved = resolved[segment]; if (!resolved) { throw new Error(`Could not resolve reference: ${obj.$ref}`); } } return resolved; } resolveSchema(schema) { if (!schema) return { type: 'string' }; const resolved = this.resolveReference(schema); // Handle array schemas if (resolved.type === 'array' && resolved.items) { return { ...resolved, items: this.resolveSchema(resolved.items) }; } // Handle object schemas if (resolved.type === 'object' && resolved.properties) { const properties = {}; for (const [propName, propSchema] of Object.entries(resolved.properties)) { properties[propName] = this.resolveSchema(propSchema); } return { ...resolved, properties }; } return resolved; } getTools() { return Array.from(this.tools.values()); } getTool(name) { return this.tools.get(name); } getBaseUrl() { if (this.spec.servers && this.spec.servers.length > 0) { return this.spec.servers[0].url; } return ''; } getSecuritySchemes() { return this.spec.components?.securitySchemes || {}; } hasGlobalSecurity() { return this.spec.security && this.spec.security.length > 0; } getGlobalSecurity() { return this.spec.security || []; } }

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/aaker/mini-openapi-mcp'

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