Skip to main content
Glama

Google Drive MCP Server

by ducla5
error-handler.ts10.6 kB
/** * Comprehensive error handling utilities */ import { GoogleDriveMCPError, ErrorCategory, ErrorResponse, ErrorHandlerConfig, DEFAULT_ERROR_CONFIG, AuthenticationError, TokenExpiredError, PermissionDeniedError, InsufficientScopeError, GoogleAPIError, NetworkError, TimeoutError, FileProcessingError, ValidationError, InvalidFileIdError, RateLimitError, QuotaExceededError, InternalError } from '../types/errors.js'; import { Logger } from '../logging/logger.js'; /** * Central error handler for the Google Drive MCP Server */ export class ErrorHandler { private config: ErrorHandlerConfig; private logger: Logger; constructor(config: Partial<ErrorHandlerConfig> = {}, logger?: Logger) { this.config = { ...DEFAULT_ERROR_CONFIG, ...config }; this.logger = logger || new Logger(); } /** * Handle and classify any error */ handleError(error: unknown, context?: Record<string, any>): GoogleDriveMCPError { // If it's already a GoogleDriveMCPError, just log and return if (error instanceof GoogleDriveMCPError) { this.logError(error, context); return error; } // Classify and convert other error types const classifiedError = this.classifyError(error, context); this.logError(classifiedError, context); return classifiedError; } /** * Classify unknown errors into appropriate GoogleDriveMCPError types */ private classifyError(error: unknown, context?: Record<string, any>): GoogleDriveMCPError { const errorMessage = this.extractErrorMessage(error); const errorContext = { ...context, originalError: this.serializeError(error) }; // Handle Google API errors if (this.isGoogleAPIError(error)) { return this.handleGoogleAPIError(error, errorContext); } // Handle network errors if (this.isNetworkError(error)) { return new NetworkError(errorMessage, errorContext); } // Handle timeout errors if (this.isTimeoutError(error)) { return new TimeoutError(errorMessage, errorContext); } // Handle authentication errors if (this.isAuthenticationError(error)) { return new AuthenticationError(errorMessage, errorContext); } // Handle file processing errors if (this.isFileProcessingError(error)) { return new FileProcessingError(errorMessage, errorContext); } // Handle validation errors if (this.isValidationError(error)) { return new ValidationError(errorMessage, errorContext); } // Default to internal error return new InternalError( `Unhandled error: ${errorMessage}`, errorContext ); } /** * Handle Google API specific errors */ private handleGoogleAPIError(error: any, context: Record<string, any>): GoogleDriveMCPError { const statusCode = this.extractStatusCode(error); const errorMessage = this.extractErrorMessage(error); switch (statusCode) { case 401: if (errorMessage.toLowerCase().includes('token')) { return new TokenExpiredError(errorMessage, context); } return new AuthenticationError(errorMessage, context); case 403: if (errorMessage.toLowerCase().includes('insufficient')) { return new InsufficientScopeError(errorMessage, context); } if (errorMessage.toLowerCase().includes('quota')) { return new QuotaExceededError(errorMessage, context); } return new PermissionDeniedError(errorMessage, context); case 404: return new InvalidFileIdError( `File not found: ${errorMessage}`, context ); case 429: return new RateLimitError(errorMessage, context); default: return new GoogleAPIError(errorMessage, statusCode, context); } } /** * Log error with appropriate level and details */ private logError(error: GoogleDriveMCPError, context?: Record<string, any>): void { if (!this.config.logErrors) return; const logData = { code: error.code, category: error.category, message: error.message, retryable: error.retryable, timestamp: error.timestamp, context: { ...error.context, ...context } }; if (this.config.includeStackTrace && error.stack) { logData.context = { ...logData.context, stack: error.stack }; } // Log with appropriate level based on error category switch (error.category) { case ErrorCategory.INTERNAL: this.logger.error('Internal error occurred', error, logData.context); break; case ErrorCategory.AUTHENTICATION: case ErrorCategory.AUTHORIZATION: this.logger.warn('Authentication/Authorization error', logData); break; case ErrorCategory.CONFIGURATION: this.logger.error('Configuration error', error, logData.context); break; case ErrorCategory.NETWORK: case ErrorCategory.API_ERROR: if (error.retryable) { this.logger.warn('Retryable error occurred', logData); } else { this.logger.error('Non-retryable error occurred', error, logData.context); } break; default: this.logger.info('Error handled', logData); } } /** * Create graceful degradation response */ createGracefulResponse(error: GoogleDriveMCPError, fallbackData?: any): { success: boolean; data?: any; error: ErrorResponse; degraded: boolean; } { return { success: false, data: fallbackData, error: error.toJSON(), degraded: !!fallbackData }; } /** * Check if error should trigger circuit breaker */ shouldTriggerCircuitBreaker(error: GoogleDriveMCPError): boolean { if (!this.config.enableCircuitBreaker) return false; // Trigger circuit breaker for certain error categories const triggerCategories = [ ErrorCategory.API_ERROR, ErrorCategory.NETWORK, ErrorCategory.RATE_LIMIT, ErrorCategory.QUOTA ]; return triggerCategories.includes(error.category) && !error.retryable; } /** * Extract error message from unknown error */ private extractErrorMessage(error: unknown): string { if (error instanceof Error) { return error.message; } if (typeof error === 'string') { return error; } if (error && typeof error === 'object' && 'message' in error) { return String((error as any).message); } return 'Unknown error occurred'; } /** * Extract status code from error */ private extractStatusCode(error: any): number | undefined { // Google API client error format if (error?.response?.status) { return error.response.status; } if (error?.status) { return error.status; } if (error?.code && typeof error.code === 'number') { return error.code; } return undefined; } /** * Serialize error for logging */ private serializeError(error: unknown): any { if (error instanceof Error) { return { name: error.name, message: error.message, stack: this.config.includeStackTrace ? error.stack : undefined }; } return error; } /** * Check if error is from Google API */ private isGoogleAPIError(error: any): boolean { return ( error?.response?.status || error?.status || (error?.message && error.message.includes('googleapis')) || (error?.code && typeof error.code === 'number') ); } /** * Check if error is network-related */ private isNetworkError(error: any): boolean { const networkKeywords = ['network', 'connection', 'econnrefused', 'enotfound', 'etimedout']; const errorMessage = this.extractErrorMessage(error).toLowerCase(); return networkKeywords.some(keyword => errorMessage.includes(keyword)) || error?.code === 'ECONNREFUSED' || error?.code === 'ENOTFOUND' || error?.code === 'ETIMEDOUT'; } /** * Check if error is timeout-related */ private isTimeoutError(error: any): boolean { const timeoutKeywords = ['timeout', 'timed out']; const errorMessage = this.extractErrorMessage(error).toLowerCase(); return timeoutKeywords.some(keyword => errorMessage.includes(keyword)) || error?.code === 'ETIMEDOUT' || error?.code === 'TIMEOUT'; } /** * Check if error is authentication-related */ private isAuthenticationError(error: any): boolean { const authKeywords = ['unauthorized', 'authentication', 'invalid_grant', 'invalid_client']; const errorMessage = this.extractErrorMessage(error).toLowerCase(); return authKeywords.some(keyword => errorMessage.includes(keyword)); } /** * Check if error is file processing-related */ private isFileProcessingError(error: any): boolean { const processingKeywords = ['parse', 'processing', 'corrupt', 'invalid format', 'unsupported']; const errorMessage = this.extractErrorMessage(error).toLowerCase(); return processingKeywords.some(keyword => errorMessage.includes(keyword)); } /** * Check if error is validation-related */ private isValidationError(error: any): boolean { const validationKeywords = ['validation', 'invalid', 'required', 'missing']; const errorMessage = this.extractErrorMessage(error).toLowerCase(); return validationKeywords.some(keyword => errorMessage.includes(keyword)); } } /** * Global error handler instance */ export const globalErrorHandler = new ErrorHandler(); /** * Utility function to handle errors in async operations */ export async function handleAsyncError<T>( operation: () => Promise<T>, context?: Record<string, any>, errorHandler: ErrorHandler = globalErrorHandler ): Promise<{ success: true; data: T } | { success: false; error: GoogleDriveMCPError }> { try { const data = await operation(); return { success: true, data }; } catch (error) { const handledError = errorHandler.handleError(error, context); return { success: false, error: handledError }; } } /** * Utility function to wrap sync operations with error handling */ export function handleSyncError<T>( operation: () => T, context?: Record<string, any>, errorHandler: ErrorHandler = globalErrorHandler ): { success: true; data: T } | { success: false; error: GoogleDriveMCPError } { try { const data = operation(); return { success: true, data }; } catch (error) { const handledError = errorHandler.handleError(error, context); return { success: false, error: handledError }; } }

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/ducla5/gdriver-mcp'

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