Skip to main content
Glama

Git MCP Server

errorHandler.ts9.52 kB
/** * @fileoverview Main ErrorHandler implementation with logging and telemetry integration. * @module src/utils/internal/error-handler/errorHandler */ import { SpanStatusCode, trace } from '@opentelemetry/api'; import { JsonRpcErrorCode, McpError } from '@/types-global/errors.js'; import { generateUUID, sanitizeInputForLogging } from '@/utils/index.js'; import { logger } from '@/utils/internal/logger.js'; import type { RequestContext } from '@/utils/internal/requestContext.js'; import { COMMON_ERROR_PATTERNS, ERROR_TYPE_MAPPINGS } from './mappings.js'; import { createSafeRegex, getErrorMessage, getErrorName } from './helpers.js'; import type { ErrorHandlerOptions, ErrorMapping } from './types.js'; /** * A utility class providing static methods for comprehensive error handling. */ export class ErrorHandler { /** * Determines an appropriate `JsonRpcErrorCode` for a given error. * Checks `McpError` instances, `ERROR_TYPE_MAPPINGS`, and `COMMON_ERROR_PATTERNS`. * Defaults to `JsonRpcErrorCode.InternalError`. * @param error - The error instance or value to classify. * @returns The determined error code. */ public static determineErrorCode(error: unknown): JsonRpcErrorCode { if (error instanceof McpError) { return error.code; } const errorName = getErrorName(error); const errorMessage = getErrorMessage(error); const mappedFromType = ( ERROR_TYPE_MAPPINGS as Record<string, JsonRpcErrorCode> )[errorName]; if (mappedFromType) { return mappedFromType; } for (const mapping of COMMON_ERROR_PATTERNS) { const regex = createSafeRegex(mapping.pattern); if (regex.test(errorMessage) || regex.test(errorName)) { return mapping.errorCode; } } // Special-case common platform errors if ( typeof error === 'object' && error !== null && 'name' in error && (error as { name?: string }).name === 'AbortError' ) { return JsonRpcErrorCode.Timeout; } return JsonRpcErrorCode.InternalError; } /** * Handles an error with consistent logging and optional transformation. * Sanitizes input, determines error code, logs details, and can rethrow. * @param error - The error instance or value that occurred. * @param options - Configuration for handling the error. * @returns The handled (and potentially transformed) error instance. */ public static handleError( error: unknown, options: ErrorHandlerOptions, ): Error { // --- OpenTelemetry Integration --- const activeSpan = trace.getActiveSpan(); if (activeSpan) { if (error instanceof Error) { activeSpan.recordException(error); } activeSpan.setStatus({ code: SpanStatusCode.ERROR, message: error instanceof Error ? error.message : String(error), }); } // --- End OpenTelemetry Integration --- const { context = {}, operation, input, rethrow = false, errorCode: explicitErrorCode, includeStack = true, critical = false, errorMapper, } = options; const sanitizedInput = input !== undefined ? sanitizeInputForLogging(input) : undefined; const originalErrorName = getErrorName(error); const originalErrorMessage = getErrorMessage(error); const originalStack = error instanceof Error ? error.stack : undefined; let finalError: Error; let loggedErrorCode: JsonRpcErrorCode; const errorDataSeed = error instanceof McpError && typeof error.data === 'object' && error.data !== null ? { ...error.data } : {}; const consolidatedData: Record<string, unknown> = { ...errorDataSeed, ...context, originalErrorName, originalMessage: originalErrorMessage, }; if ( originalStack && !(error instanceof McpError && error.data?.originalStack) ) { consolidatedData.originalStack = originalStack; } const cause = error instanceof Error ? error : undefined; const rootCause = (() => { let current: unknown = cause; let depth = 0; while ( current && current instanceof Error && current.cause && depth < 5 ) { current = current.cause as unknown; depth += 1; } return current instanceof Error ? { name: current.name, message: current.message } : undefined; })(); if (rootCause) { consolidatedData['rootCause'] = rootCause; } if (error instanceof McpError) { loggedErrorCode = error.code; finalError = errorMapper ? errorMapper(error) : new McpError(error.code, error.message, consolidatedData, { cause, }); } else { loggedErrorCode = explicitErrorCode || ErrorHandler.determineErrorCode(error); const message = `Error in ${operation}: ${originalErrorMessage}`; finalError = errorMapper ? errorMapper(error) : new McpError(loggedErrorCode, message, consolidatedData, { cause, }); } if ( finalError !== error && error instanceof Error && finalError instanceof Error && !finalError.stack && error.stack ) { finalError.stack = error.stack; } const logRequestId = typeof context.requestId === 'string' && context.requestId ? context.requestId : generateUUID(); const logTimestamp = typeof context.timestamp === 'string' && context.timestamp ? context.timestamp : new Date().toISOString(); const stack = finalError instanceof Error ? finalError.stack : originalStack; const logContext: RequestContext = { requestId: logRequestId, timestamp: logTimestamp, operation, input: sanitizedInput, critical, errorCode: loggedErrorCode, originalErrorType: originalErrorName, finalErrorType: getErrorName(finalError), ...Object.fromEntries( Object.entries(context).filter( ([key]) => key !== 'requestId' && key !== 'timestamp', ), ), errorData: finalError instanceof McpError && finalError.data ? finalError.data : consolidatedData, ...(includeStack && stack ? { stack } : {}), }; logger.error( `Error in ${operation}: ${finalError.message || originalErrorMessage}`, logContext, ); if (rethrow) { throw finalError; } return finalError; } /** * Maps an error to a specific error type `T` based on `ErrorMapping` rules. * Returns original/default error if no mapping matches. * @template T The target error type, extending `Error`. * @param error - The error instance or value to map. * @param mappings - An array of mapping rules to apply. * @param defaultFactory - Optional factory for a default error if no mapping matches. * @returns The mapped error of type `T`, or the original/defaulted error. */ public static mapError<T extends Error>( error: unknown, mappings: ReadonlyArray<ErrorMapping<T>>, defaultFactory?: (error: unknown, context?: Record<string, unknown>) => T, ): T | Error { const errorMessage = getErrorMessage(error); const errorName = getErrorName(error); for (const mapping of mappings) { const regex = createSafeRegex(mapping.pattern); if (regex.test(errorMessage) || regex.test(errorName)) { // c8 ignore next return mapping.factory(error, mapping.additionalContext); } } if (defaultFactory) { return defaultFactory(error); } return error instanceof Error ? error : new Error(String(error)); } /** * Formats an error into a consistent object structure for API responses or structured logging. * @param error - The error instance or value to format. * @returns A structured representation of the error. */ public static formatError(error: unknown): Record<string, unknown> { if (error instanceof McpError) { return { code: error.code, message: error.message, data: typeof error.data === 'object' && error.data !== null ? error.data : {}, }; } if (error instanceof Error) { return { code: ErrorHandler.determineErrorCode(error), message: error.message, data: { errorType: error.name || 'Error' }, }; } return { code: JsonRpcErrorCode.UnknownError, message: getErrorMessage(error), data: { errorType: getErrorName(error) }, }; } /** * Safely executes a function (sync or async) and handles errors using `ErrorHandler.handleError`. * The error is always rethrown. * @template T The expected return type of the function `fn`. * @param fn - The function to execute. * @param options - Error handling options (excluding `rethrow`). * @returns A promise resolving with the result of `fn` if successful. * @throws {McpError | Error} The error processed by `ErrorHandler.handleError`. */ public static async tryCatch<T>( fn: () => Promise<T> | T, options: Omit<ErrorHandlerOptions, 'rethrow'>, ): Promise<T> { try { return await Promise.resolve(fn()); } catch (caughtError) { // ErrorHandler.handleError will return the error to be thrown. throw ErrorHandler.handleError(caughtError, { ...options, rethrow: true, }); } } }

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/cyanheads/git-mcp-server'

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