evolution-api-mcp
Version:
MCP Server for Evolution API v2 - Integrate WhatsApp functionality with Claude Desktop and other MCP clients
242 lines (241 loc) • 9.11 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.EvolutionHttpClient = exports.HttpClientConfigSchema = exports.ApiError = exports.ErrorType = void 0;
const axios_1 = __importDefault(require("axios"));
const zod_1 = require("zod");
const error_handler_1 = require("../utils/error-handler");
// Re-export error types for backward compatibility
var error_handler_2 = require("../utils/error-handler");
Object.defineProperty(exports, "ErrorType", { enumerable: true, get: function () { return error_handler_2.ErrorType; } });
Object.defineProperty(exports, "ApiError", { enumerable: true, get: function () { return error_handler_2.McpError; } });
// Configuration schema
exports.HttpClientConfigSchema = zod_1.z.object({
baseURL: zod_1.z.string().url('Base URL must be a valid URL'),
apiKey: zod_1.z.string().min(1, 'API key is required'),
timeout: zod_1.z.number().positive().optional().default(30000),
retryAttempts: zod_1.z.number().min(0).max(10).optional().default(3),
retryDelay: zod_1.z.number().positive().optional().default(1000),
maxRetryDelay: zod_1.z.number().positive().optional().default(30000),
enableLogging: zod_1.z.boolean().optional().default(false)
});
class EvolutionHttpClient {
constructor(config) {
this.requestCount = 0;
// Validate configuration
this.config = exports.HttpClientConfigSchema.parse(config);
// Initialize error handler
this.errorHandler = new error_handler_1.ErrorHandler({
enableLogging: this.config.enableLogging,
logLevel: this.config.enableLogging ? 'error' : 'error'
});
// Create axios instance with base configuration
this.axiosInstance = axios_1.default.create({
baseURL: this.config.baseURL,
timeout: this.config.timeout,
headers: {
'Content-Type': 'application/json',
'apikey': this.config.apiKey,
'User-Agent': 'evolution-api-mcp/1.0.0'
},
// Enable connection pooling
maxRedirects: 5,
validateStatus: (status) => status < 500 // Don't throw on 4xx errors
});
this.setupInterceptors();
}
setupInterceptors() {
// Request interceptor for logging and authentication
this.axiosInstance.interceptors.request.use((config) => {
if (this.config.enableLogging) {
console.log(`[HTTP Client] Request: ${config.method?.toUpperCase()} ${config.url}`);
if (config.data) {
console.log('[HTTP Client] Request data:', JSON.stringify(config.data, null, 2));
}
}
// Ensure API key is always present
if (!config.headers['apikey']) {
config.headers['apikey'] = this.config.apiKey;
}
return config;
}, (error) => {
if (this.config.enableLogging) {
console.error('[HTTP Client] Request error:', error.message);
}
return Promise.reject(this.createApiError(error, { operation: 'request_interceptor' }));
});
// Response interceptor for logging and error handling
this.axiosInstance.interceptors.response.use((response) => {
if (this.config.enableLogging) {
console.log(`[HTTP Client] Response: ${response.status} ${response.statusText}`);
console.log('[HTTP Client] Response data:', JSON.stringify(response.data, null, 2));
}
return response;
}, (error) => {
if (this.config.enableLogging) {
console.error('[HTTP Client] Response error:', error.message);
if (error.response) {
console.error('[HTTP Client] Error response:', {
status: error.response.status,
data: error.response.data
});
}
}
return Promise.reject(this.createApiError(error, { operation: 'response_interceptor' }));
});
}
createApiError(error, context) {
return this.errorHandler.handleHttpError(error, context);
}
async executeWithRetry(requestFn, retries = this.config.retryAttempts, context) {
let lastError;
let currentDelay = this.config.retryDelay;
for (let attempt = 0; attempt <= retries; attempt++) {
try {
this.requestCount++;
const response = await requestFn();
return {
success: true,
data: response.data,
statusCode: response.status,
headers: response.headers
};
}
catch (error) {
// Convert axios error to our McpError format with context
const errorContext = {
...context,
operation: 'http_request',
requestId: `req_${this.requestCount}_${Date.now()}`
};
lastError = this.createApiError(error, errorContext);
// Don't retry on non-retryable errors
if (!lastError.retryable) {
break;
}
// Don't retry on the last attempt
if (attempt === retries) {
break;
}
if (this.config.enableLogging) {
console.log(`[HTTP Client] Attempt ${attempt + 1} failed, retrying in ${currentDelay}ms...`);
}
// Wait before retrying with exponential backoff
await this.delay(currentDelay);
currentDelay = Math.min(currentDelay * 2, this.config.maxRetryDelay);
}
}
// Log the final error
this.errorHandler.logError(lastError);
return {
success: false,
error: lastError,
statusCode: lastError.statusCode || 0
};
}
delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
// Generic request method
async request(options) {
const { method, path, data, params, headers, timeout, retries } = options;
const requestConfig = {
method,
url: path,
data,
params,
headers: { ...headers },
timeout: timeout || this.config.timeout
};
const context = {
operation: 'http_request',
endpoint: path,
parameters: { method, path, params }
};
return this.executeWithRetry(() => this.axiosInstance.request(requestConfig), retries !== undefined ? retries : this.config.retryAttempts, context);
}
// Convenience methods
async get(path, params, options) {
return this.request({
method: 'GET',
path,
params,
...options
});
}
async post(path, data, params, options) {
return this.request({
method: 'POST',
path,
data,
params,
...options
});
}
async put(path, data, params, options) {
return this.request({
method: 'PUT',
path,
data,
params,
...options
});
}
async delete(path, params, options) {
return this.request({
method: 'DELETE',
path,
params,
...options
});
}
async patch(path, data, params, options) {
return this.request({
method: 'PATCH',
path,
data,
params,
...options
});
}
// Configuration methods
updateConfig(updates) {
this.config = { ...this.config, ...updates };
// Update axios instance if needed
if (updates.baseURL) {
this.axiosInstance.defaults.baseURL = updates.baseURL;
}
if (updates.apiKey) {
this.axiosInstance.defaults.headers['apikey'] = updates.apiKey;
}
if (updates.timeout) {
this.axiosInstance.defaults.timeout = updates.timeout;
}
}
getConfig() {
return { ...this.config };
}
// Health check method
async healthCheck() {
try {
return await this.get('/');
}
catch (error) {
const mcpError = this.createApiError(error, { operation: 'health_check' });
return {
success: false,
error: mcpError,
statusCode: mcpError.statusCode || 0
};
}
}
// Get request statistics
getStats() {
return {
requestCount: this.requestCount
};
}
}
exports.EvolutionHttpClient = EvolutionHttpClient;