Skip to main content
Glama
waldzellai

Exa Websets MCP Server

by waldzellai
ErrorHandler.ts8.03 kB
/** * API Error Handler * * Centralized error handling, classification, and retry logic for the Websets API. */ import { ApiError, ApiErrorType } from '../types/websets.js'; import { log } from '../utils/logger.js'; import { maskSensitiveData } from '../utils/security.js'; export class ApiErrorHandler { /** * Classify an error based on its characteristics */ static classify(error: unknown): ApiErrorType { // Type guard for error-like objects const errorObj = error as any; // Network/connection errors if (errorObj?.code === 'ECONNREFUSED' || errorObj?.code === 'ENOTFOUND' || errorObj?.code === 'ECONNRESET') { return ApiErrorType.NETWORK_ERROR; } // Timeout errors if (errorObj?.code === 'ETIMEDOUT' || errorObj?.message?.includes('timeout')) { return ApiErrorType.TIMEOUT_ERROR; } // HTTP status code based classification if (errorObj?.response?.status) { const status = errorObj.response.status; if (status === 401) { return ApiErrorType.AUTHENTICATION; } if (status === 403) { return ApiErrorType.AUTHORIZATION; } if (status === 404) { return ApiErrorType.NOT_FOUND; } if (status === 400 || status === 422) { return ApiErrorType.VALIDATION; } if (status === 429) { return ApiErrorType.RATE_LIMIT; } if (status >= 500) { return ApiErrorType.SERVER_ERROR; } } // Circuit breaker errors if (errorObj?.message?.includes('circuit breaker')) { return ApiErrorType.CIRCUIT_BREAKER_OPEN; } // Default to network error for unknown errors return ApiErrorType.NETWORK_ERROR; } /** * Determine if an error should trigger a retry */ static shouldRetry(errorType: ApiErrorType): boolean { switch (errorType) { case ApiErrorType.RATE_LIMIT: case ApiErrorType.SERVER_ERROR: case ApiErrorType.NETWORK_ERROR: case ApiErrorType.TIMEOUT_ERROR: return true; case ApiErrorType.AUTHENTICATION: case ApiErrorType.AUTHORIZATION: case ApiErrorType.VALIDATION: case ApiErrorType.NOT_FOUND: case ApiErrorType.CIRCUIT_BREAKER_OPEN: return false; default: return false; } } /** * Calculate retry delay with exponential backoff */ static getRetryDelay(attempt: number, errorType: ApiErrorType, baseDelay: number = 1000, maxDelay: number = 10000): number { // Special handling for rate limit errors if (errorType === ApiErrorType.RATE_LIMIT) { // Use longer delays for rate limit errors return Math.min(baseDelay * Math.pow(2, attempt) * 2, maxDelay); } // Exponential backoff with jitter const exponentialDelay = baseDelay * Math.pow(2, attempt); const jitter = Math.random() * 0.1 * exponentialDelay; // 10% jitter return Math.min(exponentialDelay + jitter, maxDelay); } /** * Format error message for logging and user display */ static formatErrorMessage(error: ApiError): string { let message = `${error.code}: ${error.message}`; if (error.details) { if (typeof error.details === 'string') { message += ` - ${maskSensitiveData(error.details)}`; } else if (typeof error.details === 'object') { try { const maskedDetails = maskSensitiveData(error.details); message += ` - ${JSON.stringify(maskedDetails)}`; } catch { message += ` - [Complex error details]`; } } } return message; } /** * Create a standardized ApiError from various error types */ static createApiError(error: unknown): ApiError { const errorType = this.classify(error); // Extract error information based on error structure let code = errorType; let message = 'An unknown error occurred'; let details: unknown = undefined; if (error && typeof error === 'object' && 'response' in error) { const responseError = error as any; if (responseError.response?.data?.error) { // Structured API error response const apiError = responseError.response.data.error; code = apiError.code || errorType; message = apiError.message || message; details = apiError.details; } else if ('message' in error && typeof (error as any).message === 'string') { // Standard Error object const err = error as any; message = err.message; details = { stack: err.stack, code: err.code, status: responseError.response?.status, }; } } else if (typeof error === 'string') { // String error message = error; } return { code, message, details, }; } /** * Log error with appropriate level based on error type */ static logError(error: ApiError, context?: string): void { const contextStr = context ? `[${context}] ` : ''; const errorMsg = this.formatErrorMessage(error); // Log with different levels based on error severity switch (error.code) { case ApiErrorType.AUTHENTICATION: case ApiErrorType.AUTHORIZATION: log(`${contextStr}Auth Error: ${errorMsg}`); break; case ApiErrorType.VALIDATION: log(`${contextStr}Validation Error: ${errorMsg}`); break; case ApiErrorType.NOT_FOUND: log(`${contextStr}Not Found: ${errorMsg}`); break; case ApiErrorType.RATE_LIMIT: log(`${contextStr}Rate Limited: ${errorMsg}`); break; case ApiErrorType.SERVER_ERROR: log(`${contextStr}Server Error: ${errorMsg}`); break; case ApiErrorType.NETWORK_ERROR: case ApiErrorType.TIMEOUT_ERROR: log(`${contextStr}Network Error: ${errorMsg}`); break; case ApiErrorType.CIRCUIT_BREAKER_OPEN: log(`${contextStr}Circuit Breaker Open: ${errorMsg}`); break; default: log(`${contextStr}Unknown Error: ${errorMsg}`); } } /** * Check if an error indicates a temporary failure */ static isTemporaryError(errorType: ApiErrorType): boolean { return [ ApiErrorType.RATE_LIMIT, ApiErrorType.SERVER_ERROR, ApiErrorType.NETWORK_ERROR, ApiErrorType.TIMEOUT_ERROR, ].includes(errorType); } /** * Check if an error indicates a permanent failure */ static isPermanentError(errorType: ApiErrorType): boolean { return [ ApiErrorType.AUTHENTICATION, ApiErrorType.AUTHORIZATION, ApiErrorType.VALIDATION, ApiErrorType.NOT_FOUND, ].includes(errorType); } /** * Get user-friendly error message */ static getUserFriendlyMessage(error: ApiError): string { switch (error.code) { case ApiErrorType.AUTHENTICATION: return 'Authentication failed. Please check your API key.'; case ApiErrorType.AUTHORIZATION: return 'Access denied. You may not have permission for this operation.'; case ApiErrorType.VALIDATION: return 'Invalid request data. Please check your input parameters.'; case ApiErrorType.NOT_FOUND: return 'The requested resource was not found.'; case ApiErrorType.RATE_LIMIT: return 'Rate limit exceeded. Please try again later.'; case ApiErrorType.SERVER_ERROR: return 'Server error occurred. Please try again later.'; case ApiErrorType.NETWORK_ERROR: return 'Network connection failed. Please check your internet connection.'; case ApiErrorType.TIMEOUT_ERROR: return 'Request timed out. Please try again.'; case ApiErrorType.CIRCUIT_BREAKER_OPEN: return 'Service temporarily unavailable. Please try again later.'; default: return error.message || 'An unexpected error occurred.'; } } }

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/waldzellai/exa-mcp-server-websets'

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