UNPKG

v0-mcp-node

Version:

Vercel v0 MCP Server using Node.js and TypeScript - Generate UI components through Claude Code

220 lines (194 loc) 6.19 kB
/** * Enhanced error handling utilities for v0-mcp */ import OpenAI from 'openai'; import { logError } from './logger.js'; export enum ErrorType { // eslint-disable-next-line no-unused-vars API_ERROR = 'API_ERROR', // eslint-disable-next-line no-unused-vars VALIDATION_ERROR = 'VALIDATION_ERROR', // eslint-disable-next-line no-unused-vars TIMEOUT_ERROR = 'TIMEOUT_ERROR', // eslint-disable-next-line no-unused-vars RATE_LIMIT_ERROR = 'RATE_LIMIT_ERROR', // eslint-disable-next-line no-unused-vars AUTHENTICATION_ERROR = 'AUTHENTICATION_ERROR', // eslint-disable-next-line no-unused-vars NETWORK_ERROR = 'NETWORK_ERROR', // eslint-disable-next-line no-unused-vars UNKNOWN_ERROR = 'UNKNOWN_ERROR', } export interface ErrorDetails { type: ErrorType; message: string; statusCode?: number; retryable: boolean; context?: string; originalError?: Error; } export class V0McpError extends Error { public readonly type: ErrorType; public readonly statusCode?: number; public readonly retryable: boolean; public readonly context?: string; public readonly originalError?: Error; constructor(details: ErrorDetails) { super(details.message); this.name = 'V0McpError'; this.type = details.type; this.statusCode = details.statusCode; this.retryable = details.retryable; this.context = details.context; this.originalError = details.originalError; } toJSON() { return { name: this.name, type: this.type, message: this.message, statusCode: this.statusCode, retryable: this.retryable, context: this.context, stack: this.stack, }; } } /** * Enhanced error handler that categorizes and logs errors appropriately */ export class ErrorHandler { static handleError(error: unknown, context: string): V0McpError { let mcpError: V0McpError; if (error instanceof V0McpError) { mcpError = error; } else if (error instanceof OpenAI.APIError) { mcpError = this.handleOpenAIError(error, context); } else if (error instanceof Error) { mcpError = this.handleGenericError(error, context); } else { mcpError = new V0McpError({ type: ErrorType.UNKNOWN_ERROR, message: String(error) || 'An unknown error occurred', retryable: false, context, }); } // Log the error with context logError(mcpError.originalError || mcpError, context, { errorType: mcpError.type, statusCode: mcpError.statusCode, retryable: mcpError.retryable, }); return mcpError; } private static handleOpenAIError(error: any, context: string): V0McpError { const statusCode = error.status; let type: ErrorType; let retryable = false; // Categorize based on status code switch (statusCode) { case 401: type = ErrorType.AUTHENTICATION_ERROR; break; case 429: type = ErrorType.RATE_LIMIT_ERROR; retryable = true; break; case 408: case 504: type = ErrorType.TIMEOUT_ERROR; retryable = true; break; case 500: case 502: case 503: type = ErrorType.API_ERROR; retryable = true; break; default: type = ErrorType.API_ERROR; retryable = statusCode >= 500; } return new V0McpError({ type, message: `v0 API Error (${statusCode}): ${error.message}`, statusCode, retryable, context, originalError: error, }); } private static handleGenericError(error: Error, context: string): V0McpError { let type: ErrorType; let retryable = false; // Check for network-related errors if ( error.message.includes('ECONNREFUSED') || error.message.includes('ENOTFOUND') || error.message.includes('ETIMEDOUT') || error.message.includes('ECONNRESET') ) { type = ErrorType.NETWORK_ERROR; retryable = true; } else if ( error.message.includes('timeout') || error.message.includes('ETIMEDOUT') ) { type = ErrorType.TIMEOUT_ERROR; retryable = true; } else if (error.name === 'ValidationError' || error.message.includes('validation')) { type = ErrorType.VALIDATION_ERROR; retryable = false; } else { type = ErrorType.UNKNOWN_ERROR; retryable = false; } return new V0McpError({ type, message: error.message, retryable, context, originalError: error, }); } /** * Create a user-friendly error message */ static createUserMessage(error: V0McpError): string { switch (error.type) { case ErrorType.AUTHENTICATION_ERROR: return 'Authentication failed. Please check your v0 API key.'; case ErrorType.RATE_LIMIT_ERROR: return 'Rate limit exceeded. Please try again later.'; case ErrorType.TIMEOUT_ERROR: return 'Request timed out. Please try again.'; case ErrorType.NETWORK_ERROR: return 'Network error occurred. Please check your connection and try again.'; case ErrorType.VALIDATION_ERROR: return `Invalid input: ${error.message}`; case ErrorType.API_ERROR: if (error.retryable) { return 'v0 API is temporarily unavailable. Please try again later.'; } return `API error: ${error.message}`; default: return 'An unexpected error occurred. Please try again.'; } } /** * Determine if an error should be retried */ static shouldRetry(error: V0McpError, attemptCount: number, maxRetries = 3): boolean { if (attemptCount >= maxRetries) { return false; } return error.retryable; } /** * Calculate retry delay with exponential backoff */ static getRetryDelay(attemptCount: number, baseDelay = 1000): number { return Math.min(baseDelay * Math.pow(2, attemptCount), 30000); // Max 30 seconds } }