Skip to main content
Glama
zqushair
by zqushair
webhookRetry.ts5.97 kB
import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import logger from './logger.js'; import ErrorLogger from './errorLogger.js'; /** * Retry configuration for webhook handlers */ export interface RetryConfig { /** Maximum number of retry attempts */ maxRetries: number; /** Initial delay between retries in milliseconds */ initialDelay: number; /** Whether to use exponential backoff for retries */ useExponentialBackoff: boolean; /** Maximum delay between retries in milliseconds */ maxDelay: number; /** List of error types that should be retried */ retryableErrors: string[]; } /** * Default retry configuration */ const DEFAULT_RETRY_CONFIG: RetryConfig = { maxRetries: 3, initialDelay: 1000, // 1 second useExponentialBackoff: true, maxDelay: 60000, // 1 minute retryableErrors: [ 'ECONNRESET', 'ETIMEDOUT', 'ECONNREFUSED', 'ENOTFOUND', 'ENETUNREACH', 'EHOSTUNREACH', 'EAI_AGAIN', 'socket hang up', 'socket timeout', 'timeout', 'network error', 'Network Error', 'read ECONNRESET', 'getaddrinfo ENOTFOUND', '429', // Too Many Requests '500', // Internal Server Error '502', // Bad Gateway '503', // Service Unavailable '504', // Gateway Timeout ], }; /** * Webhook retry manager * This utility provides retry functionality for webhook handlers */ export class WebhookRetryManager { private config: RetryConfig; /** * Create a new webhook retry manager * @param config Retry configuration */ constructor(config: Partial<RetryConfig> = {}) { this.config = { ...DEFAULT_RETRY_CONFIG, ...config, }; } /** * Execute a function with retry logic * @param fn The function to execute * @param webhookType The type of webhook * @param webhookId The ID of the webhook * @returns The result of the function * @throws The error from the function if it fails after all retries */ public async executeWithRetry<T>( fn: () => Promise<T>, webhookType: string, webhookId: string ): Promise<T> { let lastError: Error | null = null; let attempt = 0; while (attempt <= this.config.maxRetries) { try { // If this is a retry attempt, log it if (attempt > 0) { logger.info(`Retrying webhook processing`, { webhookType, webhookId, attempt, maxRetries: this.config.maxRetries, }); } // Execute the function return await fn(); } catch (error: any) { lastError = error; attempt++; // Check if we've reached the maximum number of retries if (attempt > this.config.maxRetries) { ErrorLogger.logWebhookError( `Failed to process webhook after ${this.config.maxRetries} retries`, error, { webhookType, webhookId, attempts: attempt, } ); break; } // Check if the error is retryable const isRetryable = this.isRetryableError(error); if (!isRetryable) { ErrorLogger.logWebhookError(`Non-retryable error processing webhook`, error, { webhookType, webhookId, attempt, }); break; } // Calculate the delay for the next retry const delay = this.calculateDelay(attempt); // Log the retry logger.warn(`Error processing webhook, will retry`, { webhookType, webhookId, attempt, maxRetries: this.config.maxRetries, delayMs: delay, error: error.message, }); // Wait before retrying await new Promise((resolve) => setTimeout(resolve, delay)); } } // If we've reached this point, all retries have failed throw lastError || new Error('Unknown error'); } /** * Check if an error is retryable * @param error The error to check * @returns Whether the error is retryable */ private isRetryableError(error: any): boolean { // If the error has a code, check if it's in the list of retryable errors if (error.code && this.config.retryableErrors.includes(error.code)) { return true; } // If the error has a status, check if it's in the list of retryable errors if (error.status && this.config.retryableErrors.includes(error.status.toString())) { return true; } // If the error has a response with a status, check if it's in the list of retryable errors if ( error.response && error.response.status && this.config.retryableErrors.includes(error.response.status.toString()) ) { return true; } // If the error message contains a retryable error string for (const retryableError of this.config.retryableErrors) { if (error.message && error.message.includes(retryableError)) { return true; } } // Default to not retryable return false; } /** * Calculate the delay for a retry attempt * @param attempt The current attempt number (1-based) * @returns The delay in milliseconds */ private calculateDelay(attempt: number): number { if (this.config.useExponentialBackoff) { // Calculate delay with exponential backoff: initialDelay * 2^(attempt-1) const delay = this.config.initialDelay * Math.pow(2, attempt - 1); // Add jitter to prevent thundering herd problem (±10%) const jitter = delay * 0.1 * (Math.random() * 2 - 1); // Ensure the delay doesn't exceed the maximum return Math.min(delay + jitter, this.config.maxDelay); } else { // Use a fixed delay return this.config.initialDelay; } } } // Export a singleton instance with default configuration export const webhookRetryManager = new WebhookRetryManager(); // Export default export default webhookRetryManager;

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/zqushair/Frontapp-MCP'

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