Skip to main content
Glama
hiltonbrown

Next.js MCP Server Template

by hiltonbrown
error-handler.ts14.3 kB
// Centralized error handling system for Xero MCP integration export enum ErrorType { // User-facing errors USER_INPUT = 'USER_INPUT', AUTHENTICATION = 'AUTHENTICATION', AUTHORIZATION = 'AUTHORIZATION', VALIDATION = 'VALIDATION', // System errors DATABASE = 'DATABASE', NETWORK = 'NETWORK', EXTERNAL_API = 'EXTERNAL_API', CONFIGURATION = 'CONFIGURATION', // Recoverable errors RATE_LIMIT = 'RATE_LIMIT', TEMPORARY = 'TEMPORARY', RETRYABLE = 'RETRYABLE', // Critical errors SECURITY = 'SECURITY', SYSTEM = 'SYSTEM', UNKNOWN = 'UNKNOWN' } export enum ErrorSeverity { LOW = 'LOW', MEDIUM = 'MEDIUM', HIGH = 'HIGH', CRITICAL = 'CRITICAL' } export interface AppError { type: ErrorType; severity: ErrorSeverity; code: string; message: string; details?: any; userMessage?: string; retryable: boolean; statusCode: number; timestamp: Date; requestId?: string; userId?: string; stack?: string; } export class ErrorHandler { private static instance: ErrorHandler; private errorLog: AppError[] = []; static getInstance(): ErrorHandler { if (!ErrorHandler.instance) { ErrorHandler.instance = new ErrorHandler(); } return ErrorHandler.instance; } // Create standardized error objects createError( type: ErrorType, code: string, message: string, options: { severity?: ErrorSeverity; details?: any; userMessage?: string; retryable?: boolean; statusCode?: number; requestId?: string; userId?: string; originalError?: Error; } = {} ): AppError { const { severity = ErrorSeverity.MEDIUM, details, userMessage, retryable = false, statusCode = 500, requestId, userId, originalError } = options; const error: AppError = { type, severity, code, message, details, userMessage: userMessage || this.getDefaultUserMessage(type), retryable, statusCode, timestamp: new Date(), requestId, userId, stack: originalError?.stack }; this.logError(error); return error; } // Handle Xero API errors handleXeroError(error: any, context?: { requestId?: string; userId?: string }): AppError { if (error.response) { const { status, data } = error.response; switch (status) { case 401: return this.createError( ErrorType.AUTHENTICATION, 'XERO_UNAUTHORIZED', 'Xero API authentication failed', { severity: ErrorSeverity.HIGH, details: data, retryable: false, statusCode: 401, ...context } ); case 403: return this.createError( ErrorType.AUTHORIZATION, 'XERO_FORBIDDEN', 'Xero API access forbidden', { severity: ErrorSeverity.HIGH, details: data, retryable: false, statusCode: 403, ...context } ); case 429: return this.createError( ErrorType.RATE_LIMIT, 'XERO_RATE_LIMIT', 'Xero API rate limit exceeded', { severity: ErrorSeverity.MEDIUM, details: data, retryable: true, statusCode: 429, ...context } ); case 400: return this.createError( ErrorType.VALIDATION, 'XERO_BAD_REQUEST', 'Invalid request to Xero API', { severity: ErrorSeverity.MEDIUM, details: data, retryable: false, statusCode: 400, ...context } ); default: return this.createError( ErrorType.EXTERNAL_API, 'XERO_API_ERROR', `Xero API error: ${status}`, { severity: ErrorSeverity.MEDIUM, details: data, retryable: status >= 500, statusCode: status, ...context } ); } } // Network or other errors return this.createError( ErrorType.NETWORK, 'XERO_CONNECTION_ERROR', 'Failed to connect to Xero API', { severity: ErrorSeverity.HIGH, details: error, retryable: true, statusCode: 503, ...context } ); } // Handle database errors handleDatabaseError(error: any, context?: { requestId?: string; userId?: string }): AppError { // Check for specific Prisma error codes if (error.code) { switch (error.code) { case 'P1001': return this.createError( ErrorType.DATABASE, 'DB_CONNECTION_ERROR', 'Database connection failed', { severity: ErrorSeverity.CRITICAL, details: error, retryable: true, statusCode: 503, ...context } ); case 'P2002': return this.createError( ErrorType.VALIDATION, 'DB_UNIQUE_CONSTRAINT', 'Database unique constraint violation', { severity: ErrorSeverity.MEDIUM, details: error, retryable: false, statusCode: 409, ...context } ); case 'P2028': return this.createError( ErrorType.DATABASE, 'DB_TRANSACTION_TIMEOUT', 'Database transaction timeout', { severity: ErrorSeverity.MEDIUM, details: error, retryable: true, statusCode: 504, ...context } ); } } return this.createError( ErrorType.DATABASE, 'DB_UNKNOWN_ERROR', 'Database operation failed', { severity: ErrorSeverity.HIGH, details: error, retryable: true, statusCode: 500, ...context } ); } // Handle OAuth errors handleOAuthError(error: any, context?: { requestId?: string; userId?: string }): AppError { const errorCode = error.code || error.error; switch (errorCode) { case 'access_denied': return this.createError( ErrorType.AUTHENTICATION, 'OAUTH_ACCESS_DENIED', 'User denied OAuth authorization', { severity: ErrorSeverity.LOW, details: error, retryable: false, statusCode: 403, ...context } ); case 'invalid_request': return this.createError( ErrorType.VALIDATION, 'OAUTH_INVALID_REQUEST', 'Invalid OAuth request', { severity: ErrorSeverity.MEDIUM, details: error, retryable: false, statusCode: 400, ...context } ); case 'unauthorized_client': return this.createError( ErrorType.AUTHENTICATION, 'OAUTH_UNAUTHORIZED_CLIENT', 'OAuth client not authorized', { severity: ErrorSeverity.HIGH, details: error, retryable: false, statusCode: 401, ...context } ); default: return this.createError( ErrorType.AUTHENTICATION, 'OAUTH_UNKNOWN_ERROR', 'OAuth authentication failed', { severity: ErrorSeverity.MEDIUM, details: error, retryable: true, statusCode: 500, ...context } ); } } // Handle MCP protocol errors handleMCPError(error: any, context?: { requestId?: string; userId?: string }): AppError { if (error.code) { switch (error.code) { case -32600: return this.createError( ErrorType.VALIDATION, 'MCP_INVALID_REQUEST', 'Invalid MCP request format', { severity: ErrorSeverity.LOW, details: error, retryable: false, statusCode: 400, ...context } ); case -32601: return this.createError( ErrorType.VALIDATION, 'MCP_METHOD_NOT_FOUND', 'MCP method not found', { severity: ErrorSeverity.LOW, details: error, retryable: false, statusCode: 404, ...context } ); case -32603: return this.createError( ErrorType.SYSTEM, 'MCP_INTERNAL_ERROR', 'MCP internal server error', { severity: ErrorSeverity.HIGH, details: error, retryable: true, statusCode: 500, ...context } ); } } return this.createError( ErrorType.UNKNOWN, 'MCP_UNKNOWN_ERROR', 'Unknown MCP error', { severity: ErrorSeverity.MEDIUM, details: error, retryable: false, statusCode: 500, ...context } ); } // Convert error to HTTP response toHTTPResponse(error: AppError) { return { status: error.statusCode, json: { error: { code: error.code, message: error.userMessage || error.message, type: error.type, retryable: error.retryable }, ...(process.env.NODE_ENV === 'development' && { details: error.details, stack: error.stack }) } }; } // Convert error to MCP response toMCPResponse(error: AppError, requestId: string | number) { return { jsonrpc: '2.0', error: { code: this.mapToMCPErrorCode(error), message: error.userMessage || error.message, data: { type: error.type, retryable: error.retryable, ...(process.env.NODE_ENV === 'development' && { details: error.details }) } }, id: requestId }; } private mapToMCPErrorCode(error: AppError): number { switch (error.type) { case ErrorType.VALIDATION: return -32602; // Invalid params case ErrorType.AUTHENTICATION: case ErrorType.AUTHORIZATION: return -32001; // Application error case ErrorType.SYSTEM: return -32603; // Internal error default: return -32000; // Server error } } private getDefaultUserMessage(type: ErrorType): string { switch (type) { case ErrorType.AUTHENTICATION: return 'Authentication failed. Please sign in again.'; case ErrorType.AUTHORIZATION: return 'You do not have permission to perform this action.'; case ErrorType.VALIDATION: return 'Please check your input and try again.'; case ErrorType.RATE_LIMIT: return 'Too many requests. Please wait and try again.'; case ErrorType.DATABASE: return 'Database temporarily unavailable. Please try again.'; case ErrorType.NETWORK: return 'Network connection issue. Please check your connection.'; case ErrorType.EXTERNAL_API: return 'External service temporarily unavailable. Please try again.'; default: return 'An unexpected error occurred. Please try again.'; } } private logError(error: AppError) { // Keep only last 1000 errors in memory this.errorLog.unshift(error); if (this.errorLog.length > 1000) { this.errorLog = this.errorLog.slice(0, 1000); } // Log to console with appropriate level const logData = { timestamp: error.timestamp.toISOString(), type: error.type, severity: error.severity, code: error.code, message: error.message, statusCode: error.statusCode, retryable: error.retryable, requestId: error.requestId, userId: error.userId }; switch (error.severity) { case ErrorSeverity.CRITICAL: console.error('🚨 CRITICAL ERROR:', logData); break; case ErrorSeverity.HIGH: console.error('🔴 HIGH ERROR:', logData); break; case ErrorSeverity.MEDIUM: console.warn('🟡 MEDIUM ERROR:', logData); break; case ErrorSeverity.LOW: console.info('🔵 LOW ERROR:', logData); break; } // In production, send to monitoring service if (process.env.NODE_ENV === 'production') { // Send to monitoring service (Sentry, etc.) // this.sendToMonitoring(error); } } // Get error statistics getErrorStats() { const stats = { total: this.errorLog.length, byType: {} as Record<ErrorType, number>, bySeverity: {} as Record<ErrorSeverity, number>, recent: this.errorLog.slice(0, 10) }; this.errorLog.forEach(error => { stats.byType[error.type] = (stats.byType[error.type] || 0) + 1; stats.bySeverity[error.severity] = (stats.bySeverity[error.severity] || 0) + 1; }); return stats; } // Clear error log clearErrorLog() { this.errorLog = []; } } // Custom error classes export class TokenRefreshError extends Error { constructor(message: string, public originalError?: any) { super(message); this.name = 'TokenRefreshError'; } } // Export singleton instance export const errorHandler = ErrorHandler.getInstance(); // Utility functions export function withErrorHandling<T extends any[], R>( fn: (...args: T) => Promise<R>, context?: { requestId?: string; userId?: string } ) { return async (...args: T): Promise<R> => { try { return await fn(...args); } catch (error) { const appError = errorHandler.createError( ErrorType.UNKNOWN, 'UNKNOWN_ERROR', error instanceof Error ? error.message : 'Unknown error', { severity: ErrorSeverity.MEDIUM, details: error, originalError: error instanceof Error ? error : undefined, ...context } ); throw appError; } }; }

Latest Blog Posts

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/hiltonbrown/xero-mcp-with-next-js'

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