/**
* Custom error classes for structured error handling
*
* All errors extend McpServerError and include structured metadata
* for consistent error formatting and observability.
*/
import { ErrorCode, type ErrorResponse } from '../types/index.js';
/**
* Base error class for MCP server errors
*/
export class McpServerError extends Error {
readonly code: ErrorCode;
readonly details?: Record<string, unknown>;
readonly retryable: boolean;
readonly retryAfterSeconds?: number;
constructor(
message: string,
code: ErrorCode,
options?: {
details?: Record<string, unknown>;
retryable?: boolean;
retryAfterSeconds?: number;
cause?: Error;
}
) {
super(message, { cause: options?.cause });
this.name = 'McpServerError';
this.code = code;
this.details = options?.details;
this.retryable = options?.retryable ?? false;
this.retryAfterSeconds = options?.retryAfterSeconds;
}
/**
* Convert to structured error response
*/
toResponse(): ErrorResponse {
return {
error: this.message,
code: this.code,
details: this.details,
retryable: this.retryable,
retryAfterSeconds: this.retryAfterSeconds,
};
}
}
/**
* Resource not found error
*/
export class NotFoundError extends McpServerError {
constructor(resource: string, identifier?: string) {
super(
identifier ? `${resource} not found: ${identifier}` : `${resource} not found`,
ErrorCode.NOT_FOUND,
{
details: { resource, identifier },
retryable: false,
}
);
this.name = 'NotFoundError';
}
}
/**
* Input validation error
*/
export class ValidationError extends McpServerError {
constructor(message: string, details?: Record<string, unknown>) {
super(message, ErrorCode.VALIDATION_ERROR, {
details,
retryable: false,
});
this.name = 'ValidationError';
}
}
/**
* Rate limiting error
*/
export class RateLimitError extends McpServerError {
constructor(retryAfterSeconds: number = 60) {
super('Rate limit exceeded', ErrorCode.RATE_LIMITED, {
retryable: true,
retryAfterSeconds,
});
this.name = 'RateLimitError';
}
}
/**
* External API error
*/
export class ExternalApiError extends McpServerError {
constructor(service: string, message: string, cause?: Error) {
super(`${service}: ${message}`, ErrorCode.EXTERNAL_API_ERROR, {
details: { service },
retryable: true,
cause,
});
this.name = 'ExternalApiError';
}
}
/**
* Timeout error
*/
export class TimeoutError extends McpServerError {
constructor(operation: string, timeoutMs: number) {
super(`${operation} timed out after ${timeoutMs}ms`, ErrorCode.TIMEOUT, {
details: { operation, timeoutMs },
retryable: true,
});
this.name = 'TimeoutError';
}
}
/**
* Cache error
*/
export class CacheError extends McpServerError {
constructor(message: string, cause?: Error) {
super(message, ErrorCode.CACHE_ERROR, {
retryable: false,
cause,
});
this.name = 'CacheError';
}
}
/**
* Authentication error
*/
export class AuthenticationError extends McpServerError {
constructor(message: string, details?: Record<string, unknown>) {
super(message, ErrorCode.UNAUTHORIZED, {
details,
retryable: false,
});
this.name = 'AuthenticationError';
}
}
/**
* Authorization error (authenticated but insufficient permissions)
*/
export class AuthorizationError extends McpServerError {
constructor(message: string, requiredScopes?: string[]) {
super(message, ErrorCode.FORBIDDEN, {
details: requiredScopes ? { requiredScopes } : undefined,
retryable: false,
});
this.name = 'AuthorizationError';
}
}
/**
* Format any error into a structured response
*/
export function formatErrorResponse(error: unknown): ErrorResponse {
if (error instanceof McpServerError) {
return error.toResponse();
}
if (error instanceof Error) {
return {
error: error.message,
code: ErrorCode.INTERNAL_ERROR,
retryable: false,
};
}
return {
error: String(error),
code: ErrorCode.INTERNAL_ERROR,
retryable: false,
};
}