error-handler.tsโข4.22 kB
/**
* Error Handling Utilities
*
* Provides:
* - Custom error classes for different scenarios
* - Error formatting for user-friendly messages
* - Error logging integration
*
* ๐ TEACHING: Why custom error classes?
* - Type safety: Can catch specific error types
* - Context: Include relevant data in error
* - User-friendly: Convert technical errors to readable messages
*/
import { getLogger } from './logger.js';
/**
* Base error class for MCP server errors
*/
export class McpError extends Error {
constructor(
message: string,
public code: string,
public details?: any
) {
super(message);
this.name = 'McpError';
}
}
/**
* Configuration error (invalid config file, missing credentials, etc.)
*/
export class ConfigurationError extends McpError {
constructor(message: string, details?: any) {
super(message, 'CONFIG_ERROR', details);
this.name = 'ConfigurationError';
}
}
/**
* SOAP API error (network issues, invalid SOAP response, etc.)
*/
export class SoapApiError extends McpError {
constructor(message: string, details?: any) {
super(message, 'SOAP_API_ERROR', details);
this.name = 'SoapApiError';
}
}
/**
* Validation error (invalid tool inputs, bad parameters, etc.)
*/
export class ValidationError extends McpError {
constructor(message: string, details?: any) {
super(message, 'VALIDATION_ERROR', details);
this.name = 'ValidationError';
}
}
/**
* Authentication error (invalid credentials)
*/
export class AuthenticationError extends McpError {
constructor(message: string, details?: any) {
super(message, 'AUTH_ERROR', details);
this.name = 'AuthenticationError';
}
}
/**
* Format error for user-friendly display
*
* Converts technical errors into messages Claude can understand and relay to users
*/
export function formatErrorForUser(error: unknown): string {
const logger = getLogger();
if (error instanceof McpError) {
logger.error('MCP Error:', {
code: error.code,
message: error.message,
details: error.details,
});
// Return user-friendly message
let message = `${error.name}: ${error.message}`;
if (error.details) {
message += `\n\nDetails: ${JSON.stringify(error.details, null, 2)}`;
}
return message;
}
if (error instanceof Error) {
logger.error('Unexpected error:', {
name: error.name,
message: error.message,
stack: error.stack,
});
return `Error: ${error.message}`;
}
logger.error('Unknown error:', error);
return 'An unknown error occurred. Please check the logs for details.';
}
/**
* Handle async errors gracefully
*
* Wrapper for async functions to catch and format errors
*/
export function handleAsyncErrors<T extends (...args: any[]) => Promise<any>>(
fn: T
): T {
return (async (...args: Parameters<T>) => {
try {
return await fn(...args);
} catch (error) {
throw error; // Re-throw, will be caught by caller
}
}) as T;
}
/**
* Retry logic for transient errors
*
* Automatically retries failed operations with exponential backoff
*/
export async function retryWithBackoff<T>(
fn: () => Promise<T>,
options: {
maxRetries: number;
initialDelay: number;
maxDelay?: number;
shouldRetry?: (error: any) => boolean;
}
): Promise<T> {
const logger = getLogger();
let lastError: any;
let delay = options.initialDelay;
const maxDelay = options.maxDelay || 30000;
for (let attempt = 0; attempt <= options.maxRetries; attempt++) {
try {
return await fn();
} catch (error) {
lastError = error;
// Check if we should retry this error
if (options.shouldRetry && !options.shouldRetry(error)) {
throw error;
}
// Last attempt failed
if (attempt === options.maxRetries) {
break;
}
logger.warn(`Attempt ${attempt + 1} failed, retrying in ${delay}ms...`, {
error: error instanceof Error ? error.message : String(error),
});
// Wait before retrying
await new Promise(resolve => setTimeout(resolve, delay));
// Exponential backoff
delay = Math.min(delay * 2, maxDelay);
}
}
throw lastError;
}