Skip to main content
Glama
error-handler.ts6.09 kB
/** * Error Handling Utilities * * Provides robust error handling, retry logic, and error formatting * for ByteBot API interactions. */ import { AxiosError } from 'axios'; import { ByteBotError, ErrorCode } from '../types/bytebot.js'; import { RetryConfig } from '../types/mcp.js'; /** * Custom error class for ByteBot operations */ export class ByteBotAPIError extends Error { constructor( message: string, public statusCode: number, public details?: unknown ) { super(message); this.name = 'ByteBotAPIError'; Object.setPrototypeOf(this, ByteBotAPIError.prototype); } } /** * Convert Axios error to ByteBotAPIError */ export function handleAxiosError(error: unknown): ByteBotAPIError { if (error instanceof AxiosError) { const statusCode = error.response?.status || ErrorCode.INTERNAL_ERROR; const errorData = error.response?.data as ByteBotError | undefined; // Handle different error scenarios if (error.code === 'ECONNREFUSED') { return new ByteBotAPIError( 'Cannot connect to ByteBot server. Please ensure ByteBot is running and the endpoint URL is correct.', ErrorCode.SERVICE_UNAVAILABLE, { endpoint: error.config?.baseURL } ); } if (error.code === 'ETIMEDOUT' || error.code === 'ECONNABORTED') { return new ByteBotAPIError( 'Request to ByteBot timed out. The operation took too long to complete.', ErrorCode.TIMEOUT, { timeout: error.config?.timeout } ); } // Use error message from API response if available const message = errorData?.message || error.message || 'An error occurred while communicating with ByteBot'; return new ByteBotAPIError(message, statusCode, errorData?.details); } // Handle non-Axios errors if (error instanceof Error) { return new ByteBotAPIError(error.message, ErrorCode.INTERNAL_ERROR); } return new ByteBotAPIError( 'An unknown error occurred', ErrorCode.INTERNAL_ERROR ); } /** * Retry a function with exponential backoff */ export async function withRetry<T>( fn: () => Promise<T>, config: RetryConfig ): Promise<T> { let lastError: Error | undefined; let delay = config.delay; for (let attempt = 0; attempt <= config.maxRetries; attempt++) { try { return await fn(); } catch (error) { lastError = error instanceof Error ? error : new Error(String(error)); // Don't retry on certain error types if ( error instanceof ByteBotAPIError && (error.statusCode === ErrorCode.BAD_REQUEST || error.statusCode === ErrorCode.NOT_FOUND) ) { throw error; } // Last attempt - throw the error if (attempt === config.maxRetries) { break; } // Wait before retrying console.error( `[ByteBot MCP] Attempt ${attempt + 1}/${config.maxRetries + 1} failed:`, lastError.message ); console.error(`[ByteBot MCP] Retrying in ${delay}ms...`); await sleep(delay); // Exponential backoff if (config.backoffMultiplier) { delay = Math.min( delay * config.backoffMultiplier, config.maxDelay || Infinity ); } } } throw lastError; } /** * Sleep for specified milliseconds */ export function sleep(ms: number): Promise<void> { return new Promise((resolve) => setTimeout(resolve, ms)); } /** * Format error for MCP response */ export function formatErrorForMCP(error: unknown): { error: string; details?: string; } { if (error instanceof ByteBotAPIError) { return { error: error.message, details: error.details ? JSON.stringify(error.details, null, 2) : undefined, }; } if (error instanceof Error) { return { error: error.message, details: error.stack, }; } return { error: 'An unknown error occurred', details: String(error), }; } /** * Validate ByteBot endpoint URL */ export function validateEndpoint(url: string, name: string): void { try { const parsed = new URL(url); if (!['http:', 'https:'].includes(parsed.protocol)) { throw new Error( `Invalid ${name} URL: Protocol must be http or https (got ${parsed.protocol})` ); } } catch (error) { if (error instanceof TypeError) { throw new Error(`Invalid ${name} URL: ${url}`); } throw error; } } /** * Validate WebSocket endpoint URL */ export function validateWebSocketEndpoint(url: string): void { try { const parsed = new URL(url); if (!['ws:', 'wss:'].includes(parsed.protocol)) { throw new Error( `Invalid WebSocket URL: Protocol must be ws or wss (got ${parsed.protocol})` ); } } catch (error) { if (error instanceof TypeError) { throw new Error(`Invalid WebSocket URL: ${url}`); } throw error; } } /** * Log error with context */ export function logError(context: string, error: unknown): void { console.error(`[ByteBot MCP] Error in ${context}:`); if (error instanceof ByteBotAPIError) { console.error(` Status: ${error.statusCode}`); console.error(` Message: ${error.message}`); if (error.details) { console.error(` Details:`, error.details); } } else if (error instanceof Error) { console.error(` ${error.message}`); if (error.stack) { console.error(` Stack:`, error.stack); } } else { console.error(` ${String(error)}`); } } /** * Check if error is retryable */ export function isRetryableError(error: unknown): boolean { if (error instanceof ByteBotAPIError) { // Retry on server errors and timeouts return ( error.statusCode >= 500 || error.statusCode === ErrorCode.TIMEOUT || error.statusCode === ErrorCode.SERVICE_UNAVAILABLE ); } if (error instanceof AxiosError) { // Retry on network errors return ( error.code === 'ECONNREFUSED' || error.code === 'ETIMEDOUT' || error.code === 'ECONNABORTED' || error.code === 'ENOTFOUND' ); } return false; }

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/sensuslab/spark-mcp'

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