UNPKG

infura-mcp-server

Version:

Model Context Protocol (MCP) server providing comprehensive read-only Ethereum blockchain access through Infura's infrastructure. Connect Claude Desktop, VS Code, and Cursor to 29 secure Ethereum JSON-RPC tools across 30+ networks including Ethereum, Poly

197 lines (193 loc) 7.82 kB
/** * Structured Error Classes for Infura MCP Server * * This module provides custom error classes and utilities for handling * and classifying errors from the Infura API. It enables: * - Distinguishing between transient and permanent failures * - Automatic retry decisions based on error classification * - User-friendly, actionable error messages * * @module errors * * @example * import { InfuraApiError, classifyHttpError, createActionableMessage } from './lib/errors.js'; * * // Create and classify an API error * const { isTransient, category } = classifyHttpError(429); * const error = new InfuraApiError('Rate limited', { httpStatus: 429, isTransient }); * * if (error.isTransient) { * // Retry the request * } else { * // Report to user * const message = createActionableMessage(error, { network: 'mainnet' }); * } */ /** * Custom error class for Infura API failures with error classification. * * InfuraApiError extends the standard Error with additional properties that * enable intelligent error handling: * - `httpStatus`: The HTTP status code (for HTTP-level errors) * - `rpcCode`: The JSON-RPC error code (for RPC-level errors) * - `isTransient`: Whether the error is likely to succeed on retry * * @class InfuraApiError * @extends Error * * @property {string} name - Always 'InfuraApiError' * @property {string} message - Human-readable error description * @property {number|null} httpStatus - HTTP status code (e.g., 429, 500, 401) * @property {number|null} rpcCode - JSON-RPC error code (e.g., -32600, -32601) * @property {boolean} isTransient - True if the error may succeed on retry * * @example * // Rate limit error (transient) * const rateLimitError = new InfuraApiError( * 'Rate limit exceeded', * { httpStatus: 429, isTransient: true } * ); * * @example * // Authentication error (permanent) * const authError = new InfuraApiError( * 'Invalid API key', * { httpStatus: 401, isTransient: false } * ); * * @example * // JSON-RPC error * const rpcError = new InfuraApiError( * 'Method not found', * { rpcCode: -32601, isTransient: false } * ); */ export class InfuraApiError extends Error { /** * Creates a new InfuraApiError instance. * * @param {string} message - A descriptive error message * @param {Object} [options={}] - Error classification options * @param {number|null} [options.httpStatus=null] - HTTP status code if applicable * @param {number|null} [options.rpcCode=null] - JSON-RPC error code if applicable * @param {boolean} [options.isTransient=false] - Whether the error is transient */ constructor(message, { httpStatus = null, rpcCode = null, isTransient = false } = {}) { super(message); this.name = 'InfuraApiError'; this.httpStatus = httpStatus; this.rpcCode = rpcCode; this.isTransient = isTransient; } } /** * Classifies an HTTP status code for appropriate error handling. * * This function categorizes HTTP errors to determine: * 1. Whether the error is transient (may succeed on retry) * 2. The error category for logging and user messaging * * Error classifications: * - **429 (Rate Limit)**: Transient - retry after delay * - **5xx (Server Error)**: Transient - server-side issues may resolve * - **401/403 (Auth Error)**: Permanent - requires credential fix * - **404 (Not Found)**: Permanent - resource doesn't exist * - **Other 4xx**: Permanent - client-side errors * * @function classifyHttpError * @param {number} status - The HTTP status code to classify * @returns {{ isTransient: boolean, category: string }} Classification result * @returns {boolean} .isTransient - True if the error may succeed on retry * @returns {string} .category - Error category: 'rate_limit', 'server_error', 'auth_error', 'not_found', or 'client_error' * * @example * // Rate limiting - should retry * classifyHttpError(429); * // Returns: { isTransient: true, category: 'rate_limit' } * * @example * // Server error - should retry * classifyHttpError(503); * // Returns: { isTransient: true, category: 'server_error' } * * @example * // Authentication error - don't retry * classifyHttpError(401); * // Returns: { isTransient: false, category: 'auth_error' } * * @example * // Usage with retry logic * const { isTransient, category } = classifyHttpError(response.status); * if (isTransient && attempt < maxRetries) { * await sleep(calculateRetryDelay(attempt)); * continue; * } */ export function classifyHttpError(status) { if (status === 429) return { isTransient: true, category: 'rate_limit' }; if (status >= 500) return { isTransient: true, category: 'server_error' }; if (status === 401 || status === 403) return { isTransient: false, category: 'auth_error' }; if (status === 404) return { isTransient: false, category: 'not_found' }; return { isTransient: false, category: 'client_error' }; } /** * Creates user-friendly error messages with actionable guidance. * * This function transforms technical API errors into helpful messages that: * 1. Explain what went wrong in plain language * 2. Provide specific steps the user can take to resolve the issue * 3. Include relevant links to documentation or pricing pages * * Message types: * - **Rate limit (429)**: Suggests waiting or upgrading plan * - **Auth errors (401/403)**: Points to API key configuration * - **Server errors (5xx)**: Explains transient nature, suggests retry * - **Timeout**: Suggests retry or simpler queries * - **Other**: Returns original error message * * @function createActionableMessage * @param {InfuraApiError|Error} error - The error to create a message for * @param {Object} [context={}] - Additional context for the error message * @param {string} [context.network] - The network being accessed (for auth error messages) * @returns {string} A user-friendly, actionable error message * * @example * // Rate limit error * const error = new InfuraApiError('', { httpStatus: 429 }); * createActionableMessage(error); * // Returns: "Rate limit exceeded. Wait 60 seconds before retrying, or upgrade your Infura plan at https://infura.io/pricing" * * @example * // Authentication error with network context * const error = new InfuraApiError('', { httpStatus: 401 }); * createActionableMessage(error, { network: 'polygon-mainnet' }); * // Returns: "Authentication failed. Verify your INFURA_API_KEY is correct and has access to polygon-mainnet." * * @example * // Server error * const error = new InfuraApiError('', { httpStatus: 503 }); * createActionableMessage(error); * // Returns: "Infura service temporarily unavailable (HTTP 503). This is a transient error - retry in a few seconds." * * @example * // Timeout error * const error = new Error('Request timed out'); * error.name = 'AbortError'; * createActionableMessage(error); * // Returns: "Request timed out. The network may be congested - try again or use a simpler query." */ export function createActionableMessage(error, context = {}) { if (error.httpStatus === 429) { return `Rate limit exceeded. Wait 60 seconds before retrying, or upgrade your Infura plan at https://infura.io/pricing`; } if (error.httpStatus === 401 || error.httpStatus === 403) { return `Authentication failed. Verify your INFURA_API_KEY is correct and has access to ${context.network || 'this network'}.`; } if (error.httpStatus >= 500) { return `Infura service temporarily unavailable (HTTP ${error.httpStatus}). This is a transient error - retry in a few seconds.`; } if (error.name === 'AbortError' || error.message?.includes('timeout')) { return `Request timed out. The network may be congested - try again or use a simpler query.`; } return error.message || 'An unexpected error occurred'; }