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
JavaScript
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