Skip to main content
Glama
error-handling.ts7.08 kB
/** * Standardized error handling patterns and utilities */ import { GrafanaError } from '../types.js'; import { Result } from './interfaces.js'; /** * Standard error categories */ export enum ErrorCategory { Validation = 'validation', Authentication = 'authentication', Authorization = 'authorization', NotFound = 'not_found', Conflict = 'conflict', RateLimit = 'rate_limit', Network = 'network', Server = 'server', Client = 'client', Unknown = 'unknown' } /** * Base application error class */ export class AppError extends Error { constructor( message: string, public readonly code: string, public readonly category: ErrorCategory, public readonly statusCode = 500, public readonly details?: Record<string, any>, ) { super(message); this.name = 'AppError'; Object.setPrototypeOf(this, AppError.prototype); } toGrafanaError(): GrafanaError { return { name: this.name, message: this.message, error: this.message, status: this.statusCode, }; } toJSON() { return { name: this.name, message: this.message, code: this.code, category: this.category, statusCode: this.statusCode, details: this.details, }; } } /** * Validation error */ export class ValidationError extends AppError { constructor(message: string, details?: Record<string, any>) { super(message, 'VALIDATION_ERROR', ErrorCategory.Validation, 400, details); this.name = 'ValidationError'; } } /** * Authentication error */ export class AuthenticationError extends AppError { constructor(message = 'Authentication required') { super(message, 'AUTHENTICATION_ERROR', ErrorCategory.Authentication, 401); this.name = 'AuthenticationError'; } } /** * Authorization error */ export class AuthorizationError extends AppError { constructor(message = 'Insufficient permissions') { super(message, 'AUTHORIZATION_ERROR', ErrorCategory.Authorization, 403); this.name = 'AuthorizationError'; } } /** * Not found error */ export class NotFoundError extends AppError { constructor(resource: string, id?: string) { const message = id ? `${resource} with id '${id}' not found` : `${resource} not found`; super(message, 'NOT_FOUND_ERROR', ErrorCategory.NotFound, 404); this.name = 'NotFoundError'; } } /** * Conflict error */ export class ConflictError extends AppError { constructor(message: string, details?: Record<string, any>) { super(message, 'CONFLICT_ERROR', ErrorCategory.Conflict, 409, details); this.name = 'ConflictError'; } } /** * Rate limit error */ export class RateLimitError extends AppError { constructor(retryAfter?: number) { super('Rate limit exceeded', 'RATE_LIMIT_ERROR', ErrorCategory.RateLimit, 429, { retryAfter }); this.name = 'RateLimitError'; } } /** * Network error */ export class NetworkError extends AppError { constructor(message: string, details?: Record<string, any>) { super(message, 'NETWORK_ERROR', ErrorCategory.Network, 503, details); this.name = 'NetworkError'; } } /** * Error handler utility functions */ export class ErrorHandler { /** * Convert unknown error to AppError */ static normalize(error: unknown, context?: string): AppError { if (error instanceof AppError) { return error; } if (error instanceof Error) { const message = context ? `${context}: ${error.message}` : error.message; // Try to categorize based on error properties if (error.message.includes('network') || error.message.includes('timeout')) { return new NetworkError(message); } if (error.message.includes('not found')) { return new NotFoundError(context || 'Resource'); } if (error.message.includes('unauthorized') || error.message.includes('401')) { return new AuthenticationError(message); } if (error.message.includes('forbidden') || error.message.includes('403')) { return new AuthorizationError(message); } return new AppError(message, 'UNKNOWN_ERROR', ErrorCategory.Unknown); } const message = context ? `${context}: ${String(error)}` : String(error); return new AppError(message, 'UNKNOWN_ERROR', ErrorCategory.Unknown); } /** * Wrap operation with error handling */ static async safeExecute<T>( operation: () => Promise<T>, context?: string, ): Promise<Result<T, AppError>> { try { const data = await operation(); return { success: true, data }; } catch (error) { return { success: false, error: ErrorHandler.normalize(error, context) }; } } /** * Create error response for MCP tools */ static createMCPErrorResponse(error: AppError) { return { content: [ { type: 'text', text: `Error (${error.code}): ${error.message}`, }, ], isError: true, _metadata: { category: error.category, statusCode: error.statusCode, details: error.details, }, }; } /** * Log error with appropriate level */ static logError(error: AppError, context?: string): void { const logData = { error: error.toJSON(), context, timestamp: new Date().toISOString(), }; switch (error.category) { case ErrorCategory.Validation: case ErrorCategory.NotFound: console.warn('Validation/NotFound Error:', logData); break; case ErrorCategory.Authentication: case ErrorCategory.Authorization: console.warn('Auth Error:', logData); break; case ErrorCategory.RateLimit: console.warn('Rate Limit:', logData); break; case ErrorCategory.Network: case ErrorCategory.Server: case ErrorCategory.Unknown: default: console.error('System Error:', logData); break; } } } /** * Error handling middleware for async operations */ export function withErrorHandling<TArgs extends any[], TResult>( fn: (...args: TArgs) => Promise<TResult>, context?: string, ) { return async (...args: TArgs): Promise<TResult> => { try { return await fn(...args); } catch (error) { const appError = ErrorHandler.normalize(error, context); ErrorHandler.logError(appError, context); throw appError; } }; } /** * Decorator for method error handling */ export function HandleErrors(context?: string) { return function (target: any, propertyName: string, descriptor: PropertyDescriptor) { const method = descriptor.value; descriptor.value = async function (...args: any[]) { try { return await method.apply(this, args); } catch (error) { const errorContext = context || `${target.constructor.name}.${propertyName}`; const appError = ErrorHandler.normalize(error, errorContext); ErrorHandler.logError(appError, errorContext); throw appError; } }; return descriptor; }; }

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/quanticsoul4772/grafana-mcp'

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