// Base error for all MCP server errors
export class MCPError extends Error {
constructor(
message: string,
public readonly code: string,
public readonly details?: unknown
) {
super(message);
this.name = 'MCPError';
}
toJSON() {
return {
name: this.name,
message: this.message,
code: this.code,
details: this.details,
};
}
}
// Validation errors
export class ValidationError extends MCPError {
constructor(message: string, details?: unknown) {
super(message, 'VALIDATION_ERROR', details);
this.name = 'ValidationError';
}
}
// API errors from Google
export class GoogleAPIError extends MCPError {
constructor(
message: string,
public readonly statusCode: number,
details?: unknown
) {
super(message, `GOOGLE_API_ERROR_${statusCode}`, details);
this.name = 'GoogleAPIError';
}
static fromGoogleError(error: unknown): GoogleAPIError {
// Handle googleapis error format
if (error && typeof error === 'object') {
const err = error as Record<string, unknown>;
// Google API error response format
if (err.response && typeof err.response === 'object') {
const response = err.response as Record<string, unknown>;
const status = (response.status as number) || 500;
const data = response.data as Record<string, unknown> | undefined;
let message = 'Google API Error';
if (data?.error && typeof data.error === 'object') {
const errorData = data.error as Record<string, unknown>;
message = (errorData.message as string) || message;
}
return new GoogleAPIError(message, status, data);
}
// Simple error with message
if (err.message) {
const status = (err.code as number) || (err.status as number) || 500;
return new GoogleAPIError(err.message as string, status, err);
}
}
// Fallback
return new GoogleAPIError(
error instanceof Error ? error.message : 'Unknown Google API Error',
500,
error
);
}
}
// Rate limiting
export class RateLimitError extends MCPError {
constructor(
public readonly retryAfter?: number,
details?: unknown
) {
super(
retryAfter
? `Rate limited. Retry after ${retryAfter} seconds.`
: 'Rate limited. Please try again later.',
'RATE_LIMIT',
details
);
this.name = 'RateLimitError';
}
}
// Permission errors
export class PermissionDeniedError extends MCPError {
constructor(message: string = 'Permission denied', details?: unknown) {
super(message, 'PERMISSION_DENIED', details);
this.name = 'PermissionDeniedError';
}
}
// Resource not found
export class NotFoundError extends MCPError {
constructor(resource: string, id: string) {
super(`${resource} not found: ${id}`, 'NOT_FOUND', { resource, id });
this.name = 'NotFoundError';
}
}
// Configuration errors
export class ConfigurationError extends MCPError {
constructor(message: string, details?: unknown) {
super(message, 'CONFIGURATION_ERROR', details);
this.name = 'ConfigurationError';
}
}
// Format error response for MCP
export function formatErrorResponse(error: unknown): {
error: {
name: string;
message: string;
code: string;
details?: unknown;
};
} {
if (error instanceof MCPError) {
return { error: error.toJSON() };
}
if (error instanceof Error) {
return {
error: {
name: error.name,
message: error.message,
code: 'UNKNOWN_ERROR',
},
};
}
return {
error: {
name: 'UnknownError',
message: String(error),
code: 'UNKNOWN_ERROR',
},
};
}
// Error handling wrapper for tool functions
export async function withErrorHandling<T>(
toolName: string,
fn: () => Promise<T>
): Promise<T> {
try {
return await fn();
} catch (error) {
// Check for rate limiting
if (error && typeof error === 'object') {
const err = error as Record<string, unknown>;
if (err.code === 429 || (err.response as Record<string, unknown>)?.status === 429) {
const retryAfter = err.retryAfter as number | undefined;
throw new RateLimitError(retryAfter, error);
}
}
// Convert Google API errors
if (error && typeof error === 'object' && 'response' in error) {
throw GoogleAPIError.fromGoogleError(error);
}
// Re-throw MCP errors as-is
if (error instanceof MCPError) {
throw error;
}
// Wrap other errors
throw new MCPError(
error instanceof Error ? error.message : String(error),
'TOOL_ERROR',
{ tool: toolName, originalError: error }
);
}
}