UNPKG

@pluggedin/pluggedin-mcp-proxy

Version:

Unified MCP proxy that aggregates all your MCP servers (STDIO, SSE, Streamable HTTP) into one powerful interface. Access any tool through a single connection, search across unified documents with built-in RAG, and receive notifications from any model. Tes

151 lines (150 loc) 4.97 kB
/** * Centralized error handling for MCP proxy */ import { debugError } from './debug-log.js'; import axios from 'axios'; /** * Error types for categorization */ export var ErrorType; (function (ErrorType) { ErrorType["VALIDATION"] = "VALIDATION"; ErrorType["NETWORK"] = "NETWORK"; ErrorType["TIMEOUT"] = "TIMEOUT"; ErrorType["AUTHENTICATION"] = "AUTHENTICATION"; ErrorType["AUTHORIZATION"] = "AUTHORIZATION"; ErrorType["RATE_LIMIT"] = "RATE_LIMIT"; ErrorType["RESOURCE_NOT_FOUND"] = "RESOURCE_NOT_FOUND"; ErrorType["SERVER_ERROR"] = "SERVER_ERROR"; ErrorType["CLIENT_ERROR"] = "CLIENT_ERROR"; ErrorType["UNKNOWN"] = "UNKNOWN"; })(ErrorType || (ErrorType = {})); /** * Custom error class with additional context */ export class McpProxyError extends Error { type; statusCode; details; originalError; constructor(type, message, statusCode, details, originalError) { super(message); this.type = type; this.statusCode = statusCode; this.details = details; this.originalError = originalError; this.name = 'McpProxyError'; } } /** * Categorizes errors based on their type */ export function categorizeError(error) { if (axios.isAxiosError(error)) { const status = error.response?.status; if (!status) { return error.code === 'ECONNABORTED' ? ErrorType.TIMEOUT : ErrorType.NETWORK; } switch (status) { case 400: return ErrorType.VALIDATION; case 401: return ErrorType.AUTHENTICATION; case 403: return ErrorType.AUTHORIZATION; case 404: return ErrorType.RESOURCE_NOT_FOUND; case 429: return ErrorType.RATE_LIMIT; case 500: case 502: case 503: case 504: return ErrorType.SERVER_ERROR; default: return status >= 400 && status < 500 ? ErrorType.CLIENT_ERROR : ErrorType.SERVER_ERROR; } } if (error instanceof Error) { if (error.message.includes('validation') || error.message.includes('invalid')) { return ErrorType.VALIDATION; } if (error.message.includes('timeout')) { return ErrorType.TIMEOUT; } if (error.message.includes('auth')) { return ErrorType.AUTHENTICATION; } } return ErrorType.UNKNOWN; } /** * Sanitizes error messages to prevent information disclosure */ export function sanitizeErrorMessage(error, context) { const errorType = categorizeError(error); switch (errorType) { case ErrorType.VALIDATION: return 'Invalid request parameters'; case ErrorType.AUTHENTICATION: return 'Authentication failed'; case ErrorType.AUTHORIZATION: return 'Access denied'; case ErrorType.RATE_LIMIT: return 'Rate limit exceeded. Please try again later'; case ErrorType.TIMEOUT: return 'Request timed out'; case ErrorType.NETWORK: return 'Network error occurred'; case ErrorType.RESOURCE_NOT_FOUND: return context?.toolName ? `Tool '${context.toolName}' not found` : context?.resourceUri ? `Resource '${context.resourceUri}' not found` : 'Resource not found'; case ErrorType.SERVER_ERROR: return 'Service temporarily unavailable'; default: return 'An unexpected error occurred'; } } /** * Logs error with context and returns sanitized error */ export function handleError(error, context, includeDetails = false) { const errorType = categorizeError(error); const sanitizedMessage = sanitizeErrorMessage(error, context); // Log detailed error for debugging debugError(`[${context.action}] Error:`, { type: errorType, context, error: error instanceof Error ? error.message : String(error), stack: error instanceof Error ? error.stack : undefined }); // Prepare details for response (if allowed) let details; if (includeDetails) { details = { action: context.action, errorType }; if (axios.isAxiosError(error)) { details.statusCode = error.response?.status; } } return new McpProxyError(errorType, sanitizedMessage, axios.isAxiosError(error) ? error.response?.status : undefined, details, error); } /** * Wraps async functions with error handling */ export function withErrorHandling(fn, context) { return (async (...args) => { const requestId = Math.random().toString(36).substring(7); try { return await fn(...args); } catch (error) { throw handleError(error, { ...context, requestId }); } }); }