http-client.js•10.9 kB
import axios from 'axios';
export class HttpClient {
constructor(baseURL = '', bearerToken = null, timeout = 30000) {
console.error(`[HTTP CLIENT] === INITIALIZING HTTP CLIENT ===`);
console.error(`[HTTP CLIENT] Base URL: ${baseURL || 'NONE'}`);
console.error(`[HTTP CLIENT] Bearer Token: ${bearerToken ? 'PROVIDED' : 'NONE'}`);
console.error(`[HTTP CLIENT] Timeout: ${timeout}ms`);
this.baseURL = baseURL;
this.bearerToken = bearerToken;
this.timeout = timeout;
this.client = axios.create({
baseURL: this.baseURL,
timeout: this.timeout,
headers: {
'Content-Type': 'application/json',
'User-Agent': 'netsapiens-mcp/1.0.0'
}
});
// Add request interceptor for authentication and logging
this.client.interceptors.request.use(
(config) => {
// Log every outgoing request
console.error(`[HTTP INTERCEPT] === OUTGOING REQUEST ===`);
console.error(`[HTTP INTERCEPT] ${config.method?.toUpperCase()} ${config.baseURL}${config.url}`);
console.error(`[HTTP INTERCEPT] Headers:`, JSON.stringify(config.headers, null, 2));
console.error(`[HTTP INTERCEPT] Query Params:`, config.params);
console.error(`[HTTP INTERCEPT] Request Body:`, config.data);
console.error(`[HTTP INTERCEPT] Timeout:`, config.timeout);
if (this.bearerToken) {
config.headers.Authorization = `Bearer ${this.bearerToken}`;
console.error(`[HTTP INTERCEPT] Bearer token added to Authorization header`);
} else {
console.error(`[HTTP INTERCEPT] No bearer token available`);
}
console.error(`[HTTP INTERCEPT] Final headers:`, JSON.stringify(config.headers, null, 2));
return config;
},
(error) => {
console.error(`[HTTP INTERCEPT] Request interceptor error:`, error.message);
return Promise.reject(error);
}
);
// Add response interceptor for logging and error handling
this.client.interceptors.response.use(
(response) => {
// Log every incoming response
console.error(`[HTTP INTERCEPT] === INCOMING RESPONSE ===`);
console.error(`[HTTP INTERCEPT] Status: ${response.status} ${response.statusText}`);
console.error(`[HTTP INTERCEPT] URL: ${response.config.url}`);
console.error(`[HTTP INTERCEPT] Response Headers:`, JSON.stringify(response.headers, null, 2));
console.error(`[HTTP INTERCEPT] Response Data Type:`, typeof response.data);
console.error(`[HTTP INTERCEPT] Response Data Length:`, Array.isArray(response.data) ? response.data.length : 'N/A');
console.error(`[HTTP INTERCEPT] Response Data:`, JSON.stringify(response.data, null, 2));
return response;
},
(error) => {
// Log every error response
console.error(`[HTTP INTERCEPT] === ERROR RESPONSE ===`);
console.error(`[HTTP INTERCEPT] Error Message:`, error.message);
console.error(`[HTTP INTERCEPT] Error Code:`, error.code);
if (error.response) {
console.error(`[HTTP INTERCEPT] Error Status: ${error.response.status} ${error.response.statusText}`);
console.error(`[HTTP INTERCEPT] Error URL: ${error.response.config?.url}`);
console.error(`[HTTP INTERCEPT] Error Headers:`, JSON.stringify(error.response.headers, null, 2));
console.error(`[HTTP INTERCEPT] Error Data:`, JSON.stringify(error.response.data, null, 2));
} else if (error.request) {
console.error(`[HTTP INTERCEPT] No response received`);
console.error(`[HTTP INTERCEPT] Request was:`, error.request);
}
return Promise.reject(this.processError(error));
}
);
}
async executeRequest(method, path, parameters = {}) {
console.error(`[HTTP DEBUG] Starting HTTP request`);
console.error(`[HTTP DEBUG] Method: ${method.toUpperCase()}`);
console.error(`[HTTP DEBUG] Path: ${path}`);
console.error(`[HTTP DEBUG] Parameters:`, JSON.stringify(parameters, null, 2));
console.error(`[HTTP DEBUG] Base URL: ${this.baseURL}`);
console.error(`[HTTP DEBUG] Bearer Token: ${this.bearerToken ? 'PROVIDED' : 'NONE'}`);
try {
const requestConfig = this.buildRequestConfig(method, path, parameters);
console.error(`[HTTP DEBUG] Final request config:`, {
method: requestConfig.method,
url: requestConfig.url,
params: requestConfig.params,
headers: Object.keys(requestConfig.headers || {}),
hasData: !!requestConfig.data,
dataType: requestConfig.data ? typeof requestConfig.data : 'none'
});
console.error(`[HTTP DEBUG] Full URL: ${this.baseURL}${requestConfig.url}`);
const response = await this.client.request(requestConfig);
console.error(`[HTTP DEBUG] Response received:`, {
status: response.status,
statusText: response.statusText,
hasData: !!response.data,
dataType: response.data ? typeof response.data : 'none',
dataLength: Array.isArray(response.data) ? response.data.length : 'N/A'
});
return {
success: true,
status: response.status,
statusText: response.statusText,
headers: response.headers,
data: response.data
};
} catch (error) {
console.error(`[HTTP DEBUG] Request failed:`, {
message: error.message,
status: error.response?.status,
statusText: error.response?.statusText,
url: error.config?.url,
method: error.config?.method
});
if (error.response?.data) {
console.error(`[HTTP DEBUG] Error response data:`, error.response.data);
}
return {
success: false,
error: error.message,
status: error.response?.status,
statusText: error.response?.statusText,
data: error.response?.data
};
}
}
buildRequestConfig(method, path, parameters) {
const config = {
method: method.toLowerCase(),
url: this.interpolatePath(path, parameters),
headers: {}
};
// Add query parameters
const queryParams = this.extractQueryParameters(parameters);
if (Object.keys(queryParams).length > 0) {
config.params = queryParams;
}
// Add custom headers
const customHeaders = this.extractHeaderParameters(parameters);
if (Object.keys(customHeaders).length > 0) {
config.headers = { ...config.headers, ...customHeaders };
}
// Add request body for methods that support it
if (this.methodSupportsBody(method)) {
const requestBody = this.buildRequestBody(parameters);
if (requestBody !== null) {
config.data = requestBody;
}
}
return config;
}
interpolatePath(path, parameters) {
let interpolatedPath = path;
// Replace path parameters like {id} with actual values
const pathParams = path.match(/{([^}]+)}/g) || [];
pathParams.forEach(param => {
const paramName = param.slice(1, -1); // Remove { }
const value = parameters[paramName];
if (value !== undefined) {
interpolatedPath = interpolatedPath.replace(param, encodeURIComponent(value));
} else {
throw new Error(`Missing required path parameter: ${paramName}`);
}
});
return interpolatedPath;
}
extractQueryParameters(parameters) {
const queryParams = {};
// Extract non-path, non-header, non-body parameters as query params
Object.entries(parameters).forEach(([key, value]) => {
if (value !== undefined &&
!key.startsWith('header_') &&
!this.isBodyParameter(key) &&
!this.isPathParameter(key, parameters)) {
queryParams[key] = value;
}
});
return queryParams;
}
extractHeaderParameters(parameters) {
const headers = {};
Object.entries(parameters).forEach(([key, value]) => {
if (key.startsWith('header_') && value !== undefined) {
const headerName = key.substring(7); // Remove 'header_' prefix
headers[headerName] = value;
}
});
return headers;
}
buildRequestBody(parameters) {
// Check if there's a dedicated requestBody parameter
if (parameters.requestBody !== undefined) {
return parameters.requestBody;
}
// Build object from non-query, non-header, non-path parameters
const bodyParams = {};
let hasBodyParams = false;
Object.entries(parameters).forEach(([key, value]) => {
if (value !== undefined &&
!key.startsWith('header_') &&
!this.isPathParameter(key, parameters) &&
this.isBodyParameter(key)) {
bodyParams[key] = value;
hasBodyParams = true;
}
});
return hasBodyParams ? bodyParams : null;
}
isBodyParameter(key) {
// Body parameters are those that are not query, header, or path parameters
// This is a heuristic - in practice, we'd determine this from the OpenAPI spec
return !key.startsWith('header_') && key !== 'requestBody';
}
isPathParameter(key, parameters) {
// This is simplified - in practice, we'd get this info from the OpenAPI spec
// For now, assume path parameters are single values that appear in curly braces
return false; // We handle path params separately in interpolatePath
}
methodSupportsBody(method) {
const methodsWithBody = ['post', 'put', 'patch'];
return methodsWithBody.includes(method.toLowerCase());
}
processError(error) {
const processed = {
message: error.message,
status: null,
statusText: null,
data: null
};
if (error.response) {
// Server responded with error status
processed.status = error.response.status;
processed.statusText = error.response.statusText;
processed.data = error.response.data;
if (error.response.data) {
if (typeof error.response.data === 'string') {
processed.message = error.response.data;
} else if (error.response.data.message) {
processed.message = error.response.data.message;
} else if (error.response.data.error) {
processed.message = error.response.data.error;
}
}
} else if (error.request) {
// Request was made but no response received
processed.message = 'No response received from server';
} else {
// Something else happened
processed.message = error.message || 'An unknown error occurred';
}
return processed;
}
updateBearerToken(token) {
this.bearerToken = token;
}
updateBaseURL(baseURL) {
this.baseURL = baseURL;
this.client.defaults.baseURL = baseURL;
}
setTimeout(timeout) {
this.timeout = timeout;
this.client.defaults.timeout = timeout;
}
}