Skip to main content
Glama
mcpService.ts4.77 kB
import { MCPConfig, MCPLog, MCPRequest, MCPResponse, MCPService } from '../types'; import { HttpException } from '../utils/errorHandler.js'; class MCPServiceImpl implements MCPService { private config: MCPConfig = { targetUrl: process.env.MCP_TARGET_URL || 'https://api.target-service.com', proxyHeaders: process.env.MCP_PROXY_HEADERS === 'true', logRequests: process.env.MCP_LOG_REQUESTS === 'true', allowedMethods: (process.env.MCP_ALLOWED_METHODS || 'GET,POST,PUT,DELETE').split(','), timeout: Number(process.env.MCP_TIMEOUT) || 30000, // 30s default }; private logs: MCPLog[] = []; private readonly MAX_LOGS = 1000; getConfig(): MCPConfig { return { ...this.config }; } updateConfig(config: Partial<MCPConfig>): void { this.config = { ...this.config, ...config, }; } getLogs(limit = 100): MCPLog[] { return this.logs.slice(-Math.min(limit, this.logs.length)); } async forwardRequest(request: MCPRequest): Promise<MCPResponse> { const { requestId, method, path, headers, query, body } = request; const startTime = Date.now(); // Validate that the method is allowed if (!this.config.allowedMethods.includes(method)) { throw new HttpException(405, `Method ${method} not allowed`); } // Build the URL with query parameters let url = `${this.config.targetUrl}${path}`; if (query && Object.keys(query).length > 0) { const searchParams = new URLSearchParams(); Object.keys(query).forEach((key) => { searchParams.append(key, query[key]); }); url += `?${searchParams.toString()}`; } // Prepare headers const requestHeaders: Record<string, string> = { 'Content-Type': 'application/json', }; // Add proxy headers if enabled if (this.config.proxyHeaders) { Object.keys(headers).forEach((key) => { // Exclude certain headers that shouldn't be proxied if (!['host', 'connection', 'content-length', 'cookie'].includes(key.toLowerCase())) { requestHeaders[key] = headers[key]; } }); } // Create AbortController for timeout const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), this.config.timeout); try { const response = await fetch(url, { method, headers: requestHeaders, body: body ? JSON.stringify(body) : undefined, signal: controller.signal, }); // Get response headers const responseHeaders: Record<string, string> = {}; response.headers.forEach((value, key) => { responseHeaders[key] = value; }); // Create the MCP response const responseBody = await this.parseResponseBody(response); const mcpResponse: MCPResponse = { requestId, statusCode: response.status, headers: responseHeaders, body: responseBody, }; // Log the request if enabled if (this.config.logRequests) { this.logRequest({ timestamp: new Date().toISOString(), requestId, method, path, statusCode: response.status, responseTime: Date.now() - startTime, }); } return mcpResponse; } catch (error: unknown) { // Handle timeout or other fetch errors const err = error as Error; const statusCode = err.name === 'AbortError' ? 504 : 502; const errorMessage = err.name === 'AbortError' ? 'Request timeout' : `Gateway error: ${err.message}`; // Log the error if enabled if (this.config.logRequests) { this.logRequest({ timestamp: new Date().toISOString(), requestId, method, path, statusCode, responseTime: Date.now() - startTime, }); } throw new HttpException(statusCode, errorMessage); } finally { clearTimeout(timeoutId); } } private async parseResponseBody(response: Response): Promise<any> { const contentType = response.headers.get('content-type') || ''; if (contentType.includes('application/json')) { try { return await response.json(); } catch { return { message: 'Invalid JSON response' }; } } else if (contentType.includes('text/')) { return await response.text(); } else { // For binary data, return a message indicating binary data return { message: 'Binary data response', contentType }; } } private logRequest(log: MCPLog): void { this.logs.push(log); // Keep logs under the maximum size if (this.logs.length > this.MAX_LOGS) { this.logs = this.logs.slice(-this.MAX_LOGS); } } } export default new MCPServiceImpl();

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/minhoyooDEV/confluence-mcp-server'

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