Skip to main content
Glama
ApiProxy.ts7.85 kB
import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios'; import { ApiOperation } from '../types'; import { AuthManager } from './AuthManager'; import { logger } from '../utils/logger'; import { appConfig } from '../utils/config'; export class ApiProxy { private axiosInstance: AxiosInstance; private authManager: AuthManager; private baseUrl: string; constructor(baseUrl: string, authManager: AuthManager) { this.baseUrl = this.normalizeBaseUrl(baseUrl); this.authManager = authManager; this.axiosInstance = axios.create({ baseURL: this.baseUrl, timeout: appConfig.api.timeout, headers: { 'Accept': 'application/json', }, }); // Add request interceptor for logging this.axiosInstance.interceptors.request.use( (config) => { // Always log the full request details for debugging const fullUrl = config.baseURL ? `${config.baseURL}${config.url}` : config.url; logger.info(`Making API request: ${config.method?.toUpperCase()} ${fullUrl}`); if (config.params && Object.keys(config.params).length > 0) { logger.info(`Query parameters: ${JSON.stringify(config.params)}`); } if (config.headers) { logger.info(`Request headers: ${JSON.stringify(config.headers)}`); } logger.request(config.method?.toUpperCase() || 'GET', config.url || '', config.data); return config; }, (error) => { logger.error('Request error:', error); return Promise.reject(error); } ); // Add response interceptor for logging this.axiosInstance.interceptors.response.use( (response) => { logger.response( response.config.method?.toUpperCase() || 'GET', response.config.url || '', response.status, response.data ); return response; }, (error) => { if (error.response) { logger.response( error.config?.method?.toUpperCase() || 'GET', error.config?.url || '', error.response.status, error.response.data ); } else { logger.error('Response error:', error.message); } return Promise.reject(error); } ); } // Execute API operation async execute(operation: ApiOperation, params: Record<string, any>): Promise<any> { try { const config = this.buildRequestConfig(operation, params); const response = await this.axiosInstance.request(config); return this.handleResponse(response); } catch (error) { return this.handleError(error, operation); } } private buildRequestConfig(operation: ApiOperation, params: Record<string, any>): AxiosRequestConfig { let url = operation.path; const config: AxiosRequestConfig = { method: operation.method.toLowerCase() as any, url, params: {}, headers: {}, }; // Check if this is a JSONP endpoint (has callback parameter) const hasCallbackParam = operation.parameters?.some( p => p.name === 'callback' && p.in === 'query' ); // For JSONP endpoints, adjust headers if (hasCallbackParam) { config.headers!['Accept'] = 'application/javascript, application/json, */*'; } // For GET requests, ensure no Content-Type header if (config.method === 'get') { delete config.headers!['Content-Type']; } else if (!config.headers!['Content-Type']) { // Set Content-Type for non-GET requests if not already set config.headers!['Content-Type'] = 'application/json'; } // Process parameters if (operation.parameters) { for (const param of operation.parameters) { const value = params[param.name]; // Skip undefined optional parameters if (value === undefined && !param.required) { continue; } switch (param.in) { case 'path': // Replace path parameters url = url.replace(`{${param.name}}`, encodeURIComponent(value)); config.url = url; break; case 'query': config.params![param.name] = value; break; case 'header': config.headers![param.name] = value; break; case 'cookie': // Cookies are typically handled by the browser/client logger.warn(`Cookie parameter ${param.name} is not directly supported`); break; } } } // Process request body if (operation.requestBody) { // Collect all parameters that aren't path/query/header const bodyData: Record<string, any> = {}; for (const [key, value] of Object.entries(params)) { // Skip if this parameter was already handled const isHandledParam = operation.parameters?.some( p => p.name === key && ['path', 'query', 'header', 'cookie'].includes(p.in) ); if (!isHandledParam && value !== undefined) { bodyData[key] = value; } } // Check if we have a 'body' parameter (for non-object request bodies) if (params.body !== undefined) { config.data = params.body; } else if (Object.keys(bodyData).length > 0) { config.data = bodyData; } } // Apply authentication return this.authManager.applyAuth(config); } private handleResponse(response: AxiosResponse): any { // Check if this is a JSONP response const contentType = response.headers['content-type']; if (contentType && contentType.includes('javascript')) { // Parse JSONP response const data = response.data; if (typeof data === 'string') { // Extract JSON from JSONP callback // Format: callbackName({...json data...}) const jsonMatch = data.match(/^[^(]*\((.*)\)[^)]*$/); if (jsonMatch && jsonMatch[1]) { try { return JSON.parse(jsonMatch[1]); } catch (e) { logger.error('Failed to parse JSONP response:', e); // Return the raw response if parsing fails return data; } } } } // Return the response data directly for non-JSONP responses return response.data; } private handleError(error: any, operation: ApiOperation): never { if (axios.isAxiosError(error)) { if (error.response) { // Server responded with error status const status = error.response.status; const data = error.response.data; throw new Error( `API request failed: ${operation.method} ${operation.path} returned ${status}. ` + `Response: ${JSON.stringify(data)}` ); } else if (error.request) { // Request was made but no response received throw new Error( `API request failed: ${operation.method} ${operation.path} - No response received. ` + `Error: ${error.message}` ); } else { // Error in request setup throw new Error( `API request setup failed: ${operation.method} ${operation.path}. ` + `Error: ${error.message}` ); } } else { // Non-Axios error throw new Error( `Unexpected error during API request: ${operation.method} ${operation.path}. ` + `Error: ${error.message || error}` ); } } private normalizeBaseUrl(url: string): string { // Remove trailing slash return url.replace(/\/$/, ''); } // Update base URL updateBaseUrl(baseUrl: string): void { this.baseUrl = this.normalizeBaseUrl(baseUrl); this.axiosInstance.defaults.baseURL = this.baseUrl; logger.info(`API base URL updated to: ${this.baseUrl}`); } }

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/NakiriYuuzu/SwaggerMcp'

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