Skip to main content
Glama
IBM

IBM i MCP Server

Official
by IBM
errorHandler.ts9.96 kB
/** * @fileoverview This module provides utilities for robust error handling. * It defines structures for error context, options for handling errors, * and mappings for classifying errors. The main `ErrorHandler` class * offers static methods for consistent error processing, logging, and transformation. * @module src/utils/internal/errorHandler */ import { SpanStatusCode, trace } from "@opentelemetry/api"; import { JsonRpcErrorCode, McpError } from "../../types-global/errors.js"; import { requestContextService, sanitizeInputForLogging } from "../index.js"; import { logOperationError } from "./logging-helpers.js"; import { RequestContext } from "./requestContext.js"; /** * Defines a generic structure for providing context with errors. * This context can include identifiers like `requestId` or any other relevant * key-value pairs that aid in debugging or understanding the error's circumstances. */ export type ErrorContext = RequestContext; /** * Configuration options for the `ErrorHandler.handleError` method. * These options control how an error is processed, logged, and whether it's rethrown. */ export interface ErrorHandlerOptions { /** * The context of the operation that caused the error. * This can include `requestId` and other relevant debugging information. */ context?: ErrorContext; /** * A descriptive name of the operation being performed when the error occurred. * This helps in identifying the source or nature of the error in logs. * Example: "UserLogin", "ProcessPayment", "FetchUserProfile". */ operation: string; /** * The input data or parameters that were being processed when the error occurred. * This input will be sanitized before logging to prevent sensitive data exposure. */ input?: unknown; /** * If true, the (potentially transformed) error will be rethrown after handling. * Defaults to `false`. */ rethrow?: boolean; /** * A specific `JsonRpcErrorCode` to assign to the error, overriding any * automatically determined error code. */ errorCode?: JsonRpcErrorCode; /** * A custom function to map or transform the original error into a new `Error` instance. * If provided, this function is used instead of the default `McpError` creation. * @param error - The original error that occurred. * @returns The transformed error. */ errorMapper?: (error: unknown) => Error; /** * If true, indicates that the error is critical and might require immediate attention * or could lead to system instability. This is primarily for logging and alerting. * Defaults to `false`. */ critical?: boolean; } /** * Defines a rule for mapping error patterns to a specific JSON-RPC error code. */ export interface ErrorMappingRule { /** * A string or regular expression to match against the error message. */ pattern: string | RegExp; /** * The `JsonRpcErrorCode` to assign if the pattern matches. */ errorCode: JsonRpcErrorCode; } /** * Maps standard JavaScript error constructor names to `JsonRpcErrorCode` values. * @private */ const ERROR_TYPE_MAPPINGS: Readonly<Record<string, JsonRpcErrorCode>> = { SyntaxError: JsonRpcErrorCode.InvalidParams, TypeError: JsonRpcErrorCode.InvalidParams, ReferenceError: JsonRpcErrorCode.InternalError, RangeError: JsonRpcErrorCode.InvalidParams, URIError: JsonRpcErrorCode.InvalidParams, EvalError: JsonRpcErrorCode.InternalError, }; /** * Array of `ErrorMappingRule` to classify errors by message/name patterns. * Order matters: more specific patterns should precede generic ones. * @private */ const COMMON_ERROR_PATTERNS: ReadonlyArray<Readonly<ErrorMappingRule>> = [ { pattern: /auth|unauthorized|unauthenticated|invalid.*token/i, errorCode: JsonRpcErrorCode.Unauthorized, }, { pattern: /permission|forbidden|access.*denied/i, errorCode: JsonRpcErrorCode.Forbidden, }, { pattern: /not found|missing|no such/i, errorCode: JsonRpcErrorCode.NotFound, }, { pattern: /invalid|validation|malformed|bad request|wrong format|missing required/i, errorCode: JsonRpcErrorCode.InvalidParams, // Use the standard code for invalid parameters }, { pattern: /conflict|already exists|duplicate/i, errorCode: JsonRpcErrorCode.Conflict, }, { pattern: /rate limit|too many requests/i, errorCode: JsonRpcErrorCode.RateLimited, }, { pattern: /timeout|timed out/i, errorCode: JsonRpcErrorCode.Timeout, }, { pattern: /service unavailable|bad gateway/i, errorCode: JsonRpcErrorCode.ServiceUnavailable, }, ]; /** * Creates a "safe" RegExp for testing error messages. * @param pattern - The string or RegExp pattern. * @returns A new RegExp instance. * @private */ function createSafeRegex(pattern: string | RegExp): RegExp { if (pattern instanceof RegExp) { let flags = pattern.flags.replace("g", ""); if (!flags.includes("i")) flags += "i"; return new RegExp(pattern.source, flags); } return new RegExp(pattern, "i"); } function getErrorName(error: unknown): string { if (error instanceof Error) return error.name || "Error"; return "NonError"; } function getErrorMessage(error: unknown): string { if (error instanceof Error) return error.message; if (typeof error === "string") return error; return "An unknown error occurred."; } /** * A utility class providing static methods for comprehensive error handling. */ export class ErrorHandler { /** * Determines an appropriate `JsonRpcErrorCode` for a given error. * @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[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; } } return JsonRpcErrorCode.InternalError; } public static handleError( error: unknown, options: ErrorHandlerOptions, ): Error { this._recordOtelError(error); const finalError = this._createFinalError(error, options); const logPayload = this._buildLogPayload(finalError, error, options); // Ensure a valid context exists for logging. const contextForLogging = options.context ?? requestContextService.createRequestContext({ operation: options.operation, }); // Use the centralized logging helper for consistent error logging. logOperationError( contextForLogging, finalError.message, finalError, logPayload, ); if (options.rethrow ?? false) { throw finalError; } return finalError; } private static _recordOtelError(error: unknown): void { const activeSpan = trace.getActiveSpan(); if (!activeSpan) return; if (error instanceof Error) { activeSpan.recordException(error); } activeSpan.setStatus({ code: SpanStatusCode.ERROR, message: getErrorMessage(error), }); } private static _createFinalError( error: unknown, options: ErrorHandlerOptions, ): Error { if (options.errorMapper) { return options.errorMapper(error); } const determinedCode = options.errorCode || this.determineErrorCode(error); const originalMessage = getErrorMessage(error); if (error instanceof McpError) { error.code = determinedCode; // Allow override return error; } return new McpError( determinedCode, `Error in ${options.operation}: ${originalMessage}`, { originalErrorName: getErrorName(error), originalMessage, }, { cause: error instanceof Error ? error : undefined }, ); } private static _buildLogPayload( finalError: Error, originalError: unknown, options: ErrorHandlerOptions, ): Record<string, unknown> { const { context = {}, operation, input, critical = false } = options; const payload: Record<string, unknown> = { ...context, operation, input: input !== undefined ? sanitizeInputForLogging(input) : undefined, critical, errorCode: finalError instanceof McpError ? finalError.code : this.determineErrorCode(finalError), originalErrorType: getErrorName(originalError), finalErrorType: getErrorName(finalError), }; // The Pino error serializer will handle the stack trace automatically. return payload; } /** * Formats an error into a consistent object structure for API responses. * @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, details: error.details ?? {}, }; } return { code: this.determineErrorCode(error), message: getErrorMessage(error), details: { errorType: getErrorName(error) }, }; } /** * Safely executes a function and handles errors using `ErrorHandler.handleError`. * @param fn - The function to execute. * @param options - Error handling options. * @returns A promise resolving with the result of `fn`. */ public static async tryCatch<T>( fn: () => Promise<T> | T, options: Omit<ErrorHandlerOptions, "rethrow">, ): Promise<T> { try { return await Promise.resolve(fn()); } catch (error) { throw this.handleError(error, { ...options, rethrow: true }); } } }

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/IBM/ibmi-mcp-server'

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