Skip to main content
Glama

OpenAPI MCP Server

by aaker
utils.js7.23 kB
/** * Utility functions for OpenAPI MCP Server */ /** * Deep clone an object */ export function deepClone(obj) { if (obj === null || typeof obj !== 'object') { return obj; } if (obj instanceof Date) { return new Date(obj.getTime()); } if (Array.isArray(obj)) { return obj.map(item => deepClone(item)); } const cloned = {}; for (const key in obj) { if (obj.hasOwnProperty(key)) { cloned[key] = deepClone(obj[key]); } } return cloned; } /** * Convert OpenAPI parameter to JSON Schema property */ export function parameterToJsonSchema(parameter) { const schema = { type: parameter.schema?.type || 'string', description: parameter.description || `${parameter.name} parameter` }; // Copy schema properties if (parameter.schema) { const { type, format, enum: enumValues, minimum, maximum, pattern, items, properties } = parameter.schema; if (format) schema.format = format; if (enumValues) schema.enum = enumValues; if (minimum !== undefined) schema.minimum = minimum; if (maximum !== undefined) schema.maximum = maximum; if (pattern) schema.pattern = pattern; if (items) schema.items = items; if (properties) schema.properties = properties; } // Add example if available if (parameter.example !== undefined) { schema.example = parameter.example; } return schema; } /** * Generate a tool name from operation details */ export function generateToolName(operation, method, path) { // First priority: operationId if (operation.operationId) { return sanitizeToolName(operation.operationId); } // Second priority: summary if (operation.summary) { const summary = operation.summary .toLowerCase() .replace(/[^a-zA-Z0-9\s]/g, '') .replace(/\s+/g, '_'); return sanitizeToolName(summary); } // Last resort: method + path const cleanPath = path .replace(/^\//, '') .replace(/[{}]/g, '') .replace(/[^a-zA-Z0-9]/g, '_') .replace(/_+/g, '_') .replace(/_$/, ''); return sanitizeToolName(`${method.toLowerCase()}_${cleanPath}`); } /** * Sanitize a tool name to ensure it's valid */ export function sanitizeToolName(name) { return name .replace(/[^a-zA-Z0-9_]/g, '_') .replace(/^[^a-zA-Z]/, 'tool_$&') .replace(/_+/g, '_') .replace(/^_|_$/g, '') .toLowerCase(); } /** * Extract path parameters from a path string */ export function extractPathParameters(path) { const matches = path.match(/{([^}]+)}/g) || []; return matches.map(match => match.slice(1, -1)); } /** * Merge multiple JSON schemas */ export function mergeSchemas(...schemas) { const merged = { type: 'object', properties: {}, required: [] }; schemas.forEach(schema => { if (!schema || schema.type !== 'object') return; // Merge properties if (schema.properties) { Object.assign(merged.properties, schema.properties); } // Merge required fields if (schema.required && Array.isArray(schema.required)) { merged.required.push(...schema.required); } }); // Remove duplicate required fields merged.required = [...new Set(merged.required)]; return merged; } /** * Validate that a value matches a JSON schema type */ export function validateType(value, schemaType) { switch (schemaType) { case 'string': return typeof value === 'string'; case 'number': return typeof value === 'number' && !isNaN(value); case 'integer': return Number.isInteger(value); case 'boolean': return typeof value === 'boolean'; case 'array': return Array.isArray(value); case 'object': return value !== null && typeof value === 'object' && !Array.isArray(value); case 'null': return value === null; default: return true; // Allow unknown types } } /** * Convert HTTP status code to category */ export function getStatusCategory(statusCode) { const code = parseInt(statusCode); if (code >= 200 && code < 300) return 'success'; if (code >= 300 && code < 400) return 'redirect'; if (code >= 400 && code < 500) return 'client_error'; if (code >= 500) return 'server_error'; return 'unknown'; } /** * Format error message for MCP response */ export function formatErrorResponse(error, statusCode = null) { const response = { error: true, message: error.message || 'An unknown error occurred' }; if (statusCode) { response.status = statusCode; response.category = getStatusCategory(statusCode); } if (error.status) { response.status = error.status; response.category = getStatusCategory(error.status); } if (error.data) { response.data = error.data; } return response; } /** * Create a success response for MCP */ export function formatSuccessResponse(data, statusCode = 200, headers = {}) { return { success: true, status: statusCode, category: getStatusCategory(statusCode), headers: headers, data: data }; } /** * Check if an HTTP method typically has a request body */ export function methodHasBody(method) { const methodsWithBody = ['post', 'put', 'patch']; return methodsWithBody.includes(method.toLowerCase()); } /** * Check if a parameter is a path parameter */ export function isPathParameter(parameter) { return parameter.in === 'path'; } /** * Check if a parameter is a query parameter */ export function isQueryParameter(parameter) { return parameter.in === 'query'; } /** * Check if a parameter is a header parameter */ export function isHeaderParameter(parameter) { return parameter.in === 'header'; } /** * Parse content type from media type string */ export function parseContentType(mediaType) { const [type, ...params] = mediaType.split(';'); const parsed = { type: type.trim() }; params.forEach(param => { const [key, value] = param.split('='); if (key && value) { parsed[key.trim()] = value.trim().replace(/['"]/g, ''); } }); return parsed; } /** * Get the primary content type from an OpenAPI content object */ export function getPrimaryContentType(content) { if (!content) return null; // Prefer JSON if (content['application/json']) return 'application/json'; // Then any JSON variant const jsonType = Object.keys(content).find(type => type.includes('json')); if (jsonType) return jsonType; // Then any text type const textType = Object.keys(content).find(type => type.startsWith('text/')); if (textType) return textType; // Return first available return Object.keys(content)[0]; } /** * Safely get a nested property from an object */ export function getNestedProperty(obj, path) { return path.split('.').reduce((current, key) => { return current && current[key] !== undefined ? current[key] : undefined; }, obj); } /** * Set a nested property in an object */ export function setNestedProperty(obj, path, value) { const keys = path.split('.'); const lastKey = keys.pop(); const target = keys.reduce((current, key) => { if (!current[key] || typeof current[key] !== 'object') { current[key] = {}; } return current[key]; }, obj); target[lastKey] = value; }

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