UNPKG

pms-analysis-reports-mcp-server

Version:

PMS analysis reports server handling maintenance reports, equipment analysis, compliance tracking, and performance metrics with ERP access for data extraction

283 lines 9.15 kB
import axios from 'axios'; import { logger } from "../utils/logger.js"; import { BaseService } from "./base-service.js"; import { SanitizationMiddleware } from "../middleware/sanitization-middleware.js"; export class ExternalApiService extends BaseService { constructor(apiConfig) { super(apiConfig); this.apiConfig = apiConfig; this.rateLimitState = { requests: 0, windowStart: Date.now(), blocked: false }; this.rateLimit = apiConfig.rateLimit; // Create axios instance with default configuration this.client = axios.create({ baseURL: apiConfig.baseURL, timeout: this.config.timeout, headers: { 'Content-Type': 'application/json', 'User-Agent': 'PMS-Analysis-MCP-Server/1.0', ...apiConfig.headers } }); // Add API key if provided if (apiConfig.apiKey) { this.client.defaults.headers.common['Authorization'] = `Bearer ${apiConfig.apiKey}`; } // Add request interceptor for rate limiting and logging this.client.interceptors.request.use((config) => this.handleRequest(config), (error) => this.handleRequestError(error)); // Add response interceptor for logging and error handling this.client.interceptors.response.use((response) => this.handleResponse(response), (error) => this.handleResponseError(error)); } /** * Make a GET request */ async get(url, params, config) { return this.makeRequest({ url, method: 'GET', params }); } /** * Make a POST request */ async post(url, data, config) { return this.makeRequest({ url, method: 'POST', data }); } /** * Make a PUT request */ async put(url, data, config) { return this.makeRequest({ url, method: 'PUT', data }); } /** * Make a DELETE request */ async delete(url, config) { return this.makeRequest({ url, method: 'DELETE' }); } /** * Generic request method */ async makeRequest(request) { return this.executeWithResilience(async () => { // Check rate limiting if (this.rateLimit && !this.checkRateLimit()) { throw new Error('Rate limit exceeded'); } // Sanitize request data const sanitizedRequest = this.sanitizeRequest(request); const response = await this.client.request({ url: sanitizedRequest.url, method: (sanitizedRequest.method || 'GET'), data: sanitizedRequest.data, params: sanitizedRequest.params, headers: sanitizedRequest.headers }); return response.data; }, { toolName: 'external_api_request', arguments: { url: request.url, method: request.method || 'GET' } }); } /** * Check rate limiting */ checkRateLimit() { if (!this.rateLimit) return true; const now = Date.now(); const windowElapsed = now - this.rateLimitState.windowStart; // Reset window if expired if (windowElapsed >= this.rateLimit.windowMs) { this.rateLimitState = { requests: 0, windowStart: now, blocked: false }; } // Check if we're within limits if (this.rateLimitState.requests >= this.rateLimit.requests) { this.rateLimitState.blocked = true; logger.warn('API rate limit exceeded', { service: this.constructor.name, requests: this.rateLimitState.requests, limit: this.rateLimit.requests, windowMs: this.rateLimit.windowMs }); return false; } this.rateLimitState.requests++; return true; } /** * Sanitize request to prevent injection attacks */ sanitizeRequest(request) { return { url: SanitizationMiddleware.sanitizeString(request.url, 2000), method: request.method || 'GET', data: request.data ? SanitizationMiddleware.sanitizeToolResponse(request.data) : undefined, params: request.params ? SanitizationMiddleware.sanitizeMongoQuery(request.params) : undefined, headers: request.headers ? this.sanitizeHeaders(request.headers) : undefined }; } /** * Sanitize HTTP headers */ sanitizeHeaders(headers) { const sanitized = {}; for (const [key, value] of Object.entries(headers)) { const sanitizedKey = SanitizationMiddleware.sanitizeString(key, 100); const sanitizedValue = SanitizationMiddleware.sanitizeString(value, 1000); // Only allow safe header names if (/^[a-zA-Z0-9\-_]+$/.test(sanitizedKey)) { sanitized[sanitizedKey] = sanitizedValue; } } return sanitized; } /** * Handle request interceptor */ handleRequest(config) { const sanitizedConfig = SanitizationMiddleware.sanitizeForLogging({ url: config.url, method: config.method, params: config.params }); logger.debug('Making external API request', { service: this.constructor.name, ...sanitizedConfig }); return config; } /** * Handle request error */ handleRequestError(error) { logger.error('External API request error', { service: this.constructor.name, error: SanitizationMiddleware.sanitizeErrorMessage(error) }); return Promise.reject(error); } /** * Handle response interceptor */ handleResponse(response) { logger.debug('External API response received', { service: this.constructor.name, status: response.status, statusText: response.statusText, responseTime: response.config.metadata?.responseTime }); return response; } /** * Handle response error */ handleResponseError(error) { const sanitizedError = { message: SanitizationMiddleware.sanitizeErrorMessage(error), status: error.response?.status, statusText: error.response?.statusText, url: error.config?.url }; logger.error('External API response error', { service: this.constructor.name, ...sanitizedError }); return Promise.reject(error); } /** * Health check implementation */ async healthCheck() { try { // Try to make a simple request to test connectivity // This should be overridden by specific API services const response = await this.client.get('/health', { timeout: 5000 }); return response.status >= 200 && response.status < 300; } catch (error) { logger.warn('External API health check failed', { service: this.constructor.name, error: SanitizationMiddleware.sanitizeErrorMessage(error) }); return false; } } /** * Get rate limit status */ getRateLimitStatus() { if (!this.rateLimit) { return { enabled: false, requests: 0, limit: 0, windowMs: 0, blocked: false }; } const resetTime = this.rateLimitState.windowStart + this.rateLimit.windowMs; return { enabled: true, requests: this.rateLimitState.requests, limit: this.rateLimit.requests, windowMs: this.rateLimit.windowMs, blocked: this.rateLimitState.blocked, resetTime }; } /** * Reset rate limit (useful for testing) */ resetRateLimit() { this.rateLimitState = { requests: 0, windowStart: Date.now(), blocked: false }; } /** * Update API configuration */ updateConfig(config) { if (config.baseURL) { this.client.defaults.baseURL = config.baseURL; } if (config.apiKey) { this.client.defaults.headers.common['Authorization'] = `Bearer ${config.apiKey}`; } if (config.headers) { Object.assign(this.client.defaults.headers.common, config.headers); } if (config.timeout) { this.client.defaults.timeout = config.timeout; } if (config.rateLimit) { this.rateLimit = config.rateLimit; this.resetRateLimit(); } } } //# sourceMappingURL=external-api-service.js.map