Skip to main content
Glama

Stampchain MCP Server

Official
mcp-error-handler.ts15.2 kB
/** * MCP Error Handler - Standardized error handling patterns for MCP tools * Provides consistent error handling, logging, and response formatting */ import { z } from 'zod'; import { McpError, ErrorCode } from '@modelcontextprotocol/sdk/types.js'; import type { ToolResponse, ToolContext } from '../interfaces/tool.js'; import { textResponse } from '../interfaces/tool.js'; import { MCPError, ValidationError, ToolExecutionError, ResourceNotFoundError, NetworkError, TimeoutError, StampchainAPIError, wrapError, } from './errors.js'; import { createMCPErrorContext, ErrorContextPatterns, logErrorContext, formatErrorWithContext, type StandardErrorContext, } from './error-context.js'; import { createLogger } from './logger.js'; const logger = createLogger('MCPErrorHandler'); /** * Configuration for error handling behavior */ export interface ErrorHandlingConfig { /** Whether to include stack traces in development */ includeStackTrace: boolean; /** Whether to log all errors automatically */ autoLog: boolean; /** Maximum retry attempts for retryable errors */ maxRetries: number; /** Base delay for exponential backoff (ms) */ baseRetryDelay: number; } /** * Default error handling configuration */ const DEFAULT_CONFIG: ErrorHandlingConfig = { includeStackTrace: process.env.NODE_ENV === 'development', autoLog: true, maxRetries: 3, baseRetryDelay: 1000, }; /** * Result of error handling operation */ export interface ErrorHandlingResult { /** Whether the error was handled successfully */ handled: boolean; /** The error context that was created */ context: StandardErrorContext; /** Whether the operation should be retried */ shouldRetry: boolean; /** Delay before retry (if applicable) */ retryDelay?: number; /** The tool response to return */ response: ToolResponse; } /** * MCP Error Handler class providing standardized error handling */ export class MCPErrorHandler { private config: ErrorHandlingConfig; constructor(config?: Partial<ErrorHandlingConfig>) { this.config = { ...DEFAULT_CONFIG, ...config }; } /** * Handles errors with standardized context and response formatting */ public handleError( error: unknown, toolName: string, operation: string, params?: Record<string, unknown>, toolContext?: ToolContext ): ErrorHandlingResult { // Create appropriate error context based on error type const context = this.createErrorContext(error, toolName, operation, params, toolContext); // Log the error if auto-logging is enabled if (this.config.autoLog && error instanceof Error) { logErrorContext(context, error); } // Convert to MCP error if needed const mcpError = this.convertToMCPError(error, context); // Create standardized response const response = this.createErrorResponse(mcpError, context); return { handled: true, context, shouldRetry: context.retryable, retryDelay: context.retryable ? this.calculateRetryDelay(1) : undefined, response, }; } /** * Handles validation errors specifically */ public handleValidationError( error: z.ZodError, toolName: string, params: Record<string, unknown>, toolContext?: ToolContext ): ErrorHandlingResult { const issues = error.issues.map((issue) => `${issue.path.join('.')}: ${issue.message}`); const context = ErrorContextPatterns.validation(toolName, issues) .withParameters(params) .build(); const validationError = new ValidationError('Invalid parameters provided', { issues }); if (this.config.autoLog) { logErrorContext(context, validationError); } return { handled: true, context, shouldRetry: false, response: this.createErrorResponse(validationError, context), }; } /** * Handles API call errors with retry logic */ public handleAPIError( error: unknown, toolName: string, endpoint: string, statusCode?: number, toolContext?: ToolContext ): ErrorHandlingResult { const context = ErrorContextPatterns.apiCall(toolName, endpoint, statusCode).build(); let mcpError: MCPError; if (error instanceof Error) { mcpError = new StampchainAPIError(error.message, statusCode, endpoint); } else { mcpError = new StampchainAPIError('API call failed', statusCode, endpoint); } if (this.config.autoLog) { logErrorContext(context, mcpError); } return { handled: true, context, shouldRetry: context.retryable, retryDelay: context.retryable ? this.calculateRetryDelay(1) : undefined, response: this.createErrorResponse(mcpError, context), }; } /** * Handles network errors with retry logic */ public handleNetworkError( error: unknown, toolName: string, operation: string, toolContext?: ToolContext ): ErrorHandlingResult { const context = ErrorContextPatterns.network(toolName, operation).build(); let networkError: NetworkError; if (error instanceof Error) { networkError = new NetworkError(error.message, 'NETWORK_ERROR'); } else { networkError = new NetworkError('Network operation failed', 'UNKNOWN'); } if (this.config.autoLog) { logErrorContext(context, networkError); } return { handled: true, context, shouldRetry: true, retryDelay: this.calculateRetryDelay(1), response: this.createErrorResponse(networkError, context), }; } /** * Handles timeout errors */ public handleTimeoutError( timeoutMs: number, toolName: string, operation: string, toolContext?: ToolContext ): ErrorHandlingResult { const context = ErrorContextPatterns.timeout(toolName, operation, timeoutMs).build(); const timeoutError = new TimeoutError(timeoutMs, operation); if (this.config.autoLog) { logErrorContext(context, timeoutError); } return { handled: true, context, shouldRetry: true, retryDelay: this.calculateRetryDelay(1), response: this.createErrorResponse(timeoutError, context), }; } /** * Handles resource not found errors */ public handleNotFoundError( resourceType: string, resourceId: string, toolName: string, toolContext?: ToolContext ): ErrorHandlingResult { const context = ErrorContextPatterns.notFound(toolName, resourceType, resourceId).build(); const notFoundError = new ResourceNotFoundError(resourceType, resourceId); if (this.config.autoLog) { logErrorContext(context, notFoundError); } return { handled: true, context, shouldRetry: false, response: this.createErrorResponse(notFoundError, context), }; } /** * Creates error context based on error type */ private createErrorContext( error: unknown, toolName: string, operation: string, params?: Record<string, unknown>, toolContext?: ToolContext ): StandardErrorContext { let builder = createMCPErrorContext(toolName, operation, toolContext); if (params) { builder = builder.withParameters(params); } // Determine severity and retryability based on error type if (error instanceof ValidationError) { builder = builder.withSeverity('low').asRetryable(false); } else if (error instanceof ResourceNotFoundError) { builder = builder.withSeverity('low').asRetryable(false); } else if (error instanceof NetworkError || error instanceof TimeoutError) { builder = builder.withSeverity('medium').asRetryable(true); } else if (error instanceof StampchainAPIError) { const statusCode = error.data && typeof error.data === 'object' && 'statusCode' in error.data ? (error.data.statusCode as number) : undefined; const isServerError = statusCode && statusCode >= 500; const isRateLimit = statusCode === 429; builder = builder .withSeverity(isServerError ? 'high' : 'medium') .asRetryable(isServerError || isRateLimit); } else { builder = builder.withSeverity('high').asRetryable(false); } return builder.build(); } /** * Converts unknown error to MCP error */ private convertToMCPError(error: unknown, context: StandardErrorContext): MCPError { if (error instanceof MCPError) { return error; } if (error instanceof z.ZodError) { const issues = error.issues.map((issue) => `${issue.path.join('.')}: ${issue.message}`); return new ValidationError('Invalid parameters provided', { issues }); } if (error instanceof Error) { return new ToolExecutionError( formatErrorWithContext(error, context), context.toolName, error ); } return wrapError(error, `Unknown error in ${context.toolName} during ${context.operation}`); } /** * Creates standardized error response */ private createErrorResponse(error: MCPError, context: StandardErrorContext): ToolResponse { const message = this.config.includeStackTrace && error.stack ? `${error.message}\n\nStack trace:\n${error.stack}` : error.message; const errorInfo: Record<string, unknown> = { error: error.constructor.name, code: error.code, toolName: context.toolName, operation: context.operation, severity: context.severity, retryable: context.retryable, timestamp: context.timestamp, }; if (error.data) { errorInfo.details = error.data; } return textResponse( `Error: ${message}\n\nError Information:\n${JSON.stringify(errorInfo, null, 2)}` ); } /** * Converts internal MCPError to MCP protocol McpError * This ensures proper MCP protocol compliance */ public convertToMcpError(error: MCPError, context: StandardErrorContext): McpError { // Map our internal error codes to MCP protocol error codes let mcpErrorCode: ErrorCode; switch (error.constructor.name) { case 'ValidationError': mcpErrorCode = ErrorCode.InvalidParams; break; case 'ResourceNotFoundError': mcpErrorCode = ErrorCode.InvalidParams; // Resource not found is typically a param issue break; case 'AuthenticationError': mcpErrorCode = ErrorCode.InvalidRequest; // Auth issues are request issues break; case 'RateLimitError': mcpErrorCode = ErrorCode.InternalError; // Rate limiting is server-side break; case 'NetworkError': case 'TimeoutError': case 'StampchainAPIError': mcpErrorCode = ErrorCode.InternalError; // External service issues break; case 'ToolExecutionError': mcpErrorCode = ErrorCode.InternalError; break; default: mcpErrorCode = ErrorCode.InternalError; } // Create detailed error message with context const detailedMessage = this.config.includeStackTrace && error.stack ? `${error.message}\n\nContext: ${context.toolName}/${context.operation}\nSeverity: ${context.severity}\nRetryable: ${context.retryable}\n\nStack trace:\n${error.stack}` : `${error.message}\n\nContext: ${context.toolName}/${context.operation}\nSeverity: ${context.severity}\nRetryable: ${context.retryable}`; return new McpError(mcpErrorCode, detailedMessage); } /** * Calculates retry delay with exponential backoff */ private calculateRetryDelay(attempt: number): number { return this.config.baseRetryDelay * Math.pow(2, attempt - 1); } /** * Updates configuration */ public updateConfig(config: Partial<ErrorHandlingConfig>): void { this.config = { ...this.config, ...config }; } /** * Gets current configuration */ public getConfig(): ErrorHandlingConfig { return { ...this.config }; } } /** * Default error handler instance */ export const defaultErrorHandler = new MCPErrorHandler(); /** * Convenience function for handling errors in MCP tools */ export function handleMCPError( error: unknown, toolName: string, operation: string, params?: Record<string, unknown>, toolContext?: ToolContext ): ErrorHandlingResult { return defaultErrorHandler.handleError(error, toolName, operation, params, toolContext); } /** * Convenience function for handling validation errors */ export function handleValidationError( error: z.ZodError, toolName: string, params: Record<string, unknown>, toolContext?: ToolContext ): ErrorHandlingResult { return defaultErrorHandler.handleValidationError(error, toolName, params, toolContext); } /** * Convenience function for handling API errors */ export function handleAPIError( error: unknown, toolName: string, endpoint: string, statusCode?: number, toolContext?: ToolContext ): ErrorHandlingResult { return defaultErrorHandler.handleAPIError(error, toolName, endpoint, statusCode, toolContext); } /** * Throws an MCP-compliant error for tool execution failures * This should be used in tool execute methods to ensure proper MCP protocol compliance */ export function throwMCPError( error: unknown, toolName: string, operation: string, params?: Record<string, unknown>, toolContext?: ToolContext ): never { const result = defaultErrorHandler.handleError(error, toolName, operation, params, toolContext); const mcpError = defaultErrorHandler.convertToMcpError( result.context.toolName === toolName ? error instanceof MCPError ? error : new ToolExecutionError('Tool execution failed', toolName, error) : new ToolExecutionError('Tool execution failed', toolName, error), result.context ); throw mcpError; } /** * Throws an MCP-compliant validation error */ export function throwMCPValidationError( zodError: z.ZodError, toolName: string, params: Record<string, unknown>, toolContext?: ToolContext ): never { const result = defaultErrorHandler.handleValidationError(zodError, toolName, params, toolContext); const validationError = new ValidationError('Invalid parameters provided', { issues: zodError.issues.map((issue) => `${issue.path.join('.')}: ${issue.message}`), }); const mcpError = defaultErrorHandler.convertToMcpError(validationError, result.context); throw mcpError; } /** * Decorator for automatic error handling in MCP tool methods */ export function withErrorHandling(toolName: string, operation: string) { return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) { const originalMethod = descriptor.value; descriptor.value = async function (...args: any[]) { try { return await originalMethod.apply(this, args); } catch (error) { const params = args[0] || {}; const toolContext = args[1] || undefined; // For decorators, we still return the response rather than throwing // This maintains compatibility with existing tool patterns const result = handleMCPError(error, toolName, operation, params, toolContext); return result.response; } }; return descriptor; }; }

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/stampchain-io/stampchain-mcp'

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