UNPKG

@bjoaquinc/mcp-error-formatter

Version:

Simple utility to format MCP tool errors like Cursor

190 lines 6.49 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.formatMCPError = formatMCPError; const uuid_1 = require("uuid"); const types_1 = require("./types"); function formatMCPError(error, options = {}) { // Safely handle null/undefined options const safeOptions = options || {}; // Generate request ID const requestId = safeOptions.requestId || (0, uuid_1.v4)(); // Normalize error to ensure we have a consistent object const normalizedError = normalizeError(error); // Auto-detect error type or use provided const errorType = safeOptions.errorType || detectErrorType(normalizedError); // Build error object exactly like Cursor's format const formattedError = { error: errorType, details: { title: safeOptions.title || normalizedError.name || 'Error occurred', detail: safeOptions.detail || normalizedError.message || 'An unexpected error occurred', isRetryable: safeOptions.isRetryable ?? isRetryableError(errorType), additionalInfo: safeOptions.additionalInfo || {}, }, isExpected: safeOptions.isExpected ?? errorType === types_1.ErrorType.USER_ABORTED, }; // Build structured content (either user-provided or auto-generated) const rawStructuredContent = buildStructuredContent(formattedError, safeOptions); const structuredContent = createSafeStructuredContent(rawStructuredContent); // Format text exactly like current Cursor style (UNCHANGED) const cursorStyleText = formatCursorStyle(requestId, formattedError, normalizedError); // Return enhanced result with optional structuredContent const result = { isError: true, content: [ { type: 'text', text: cursorStyleText, }, ], }; // Add structuredContent only if it was successfully created if (structuredContent) { result.structuredContent = structuredContent; } return result; } function buildStructuredContent(formattedError, options) { // If user provided explicit structured content, use it if (options.structured) { return options.structured; } // Auto-generate structured content from formattedError return { errorType: formattedError.error, title: formattedError.details.title, detail: formattedError.details.detail, retryable: formattedError.details.isRetryable, expected: formattedError.isExpected, info: formattedError.details.additionalInfo, }; } function createSafeStructuredContent(structuredContent) { try { // Test if the object can be serialized (detect circular refs) JSON.stringify(structuredContent); return structuredContent; } catch { // If structured content has circular refs or other issues, // return undefined to fall back to text-only mode return undefined; } } function normalizeError(error) { // Handle null/undefined if (error === null || error === undefined) { return { name: 'Error', message: 'Unknown error', }; } // Handle strings if (typeof error === 'string') { return { name: 'Error', message: error, }; } // Handle numbers if (typeof error === 'number') { return { name: 'Error', message: error.toString(), }; } // Handle booleans if (typeof error === 'boolean') { return { name: 'Error', message: error.toString(), }; } // Handle Error objects if (error instanceof Error) { return { name: error.name || 'Error', message: error.message || 'Unknown error', stack: error.stack, }; } // Handle objects with message property if (typeof error === 'object' && error.message) { return { name: error.name || 'Error', message: String(error.message), stack: error.stack, }; } // Fallback for any other type return { name: 'Error', message: 'Unknown error occurred', }; } function formatCursorStyle(requestId, formattedError, originalError) { const parts = [ `Request ID: ${requestId}`, safeJsonStringify(formattedError), // Safe JSON stringify to handle circular refs '', formatStackTrace(originalError), ]; return parts.join('\n'); } function safeJsonStringify(obj) { try { return JSON.stringify(obj, null, 0); } catch { // Handle circular references by using a replacer const seen = new WeakSet(); return JSON.stringify(obj, (key, value) => { if (typeof value === 'object' && value !== null) { if (seen.has(value)) { return '[Circular Reference]'; } seen.add(value); } return value; }, 0); } } function formatStackTrace(error) { if (!error.stack) { return `${error.name}: ${error.message}`; } return error.stack; } function detectErrorType(error) { const message = (error.message || '').toLowerCase(); const name = (error.name || '').toLowerCase(); // Check timeout patterns first (before abort) since some timeout messages contain "abort" if (name.includes('timeout') || message.includes('timeout') || message.includes('timed out') || message.includes('etimedout')) { return types_1.ErrorType.TIMEOUT; } if (name.includes('abort') || message.includes('abort')) { return types_1.ErrorType.USER_ABORTED; } if (message.includes('network') || message.includes('fetch') || message.includes('connection') || message.includes('enotfound') || message.includes('econnrefused') || message.includes('status code') || name.includes('axios')) { return types_1.ErrorType.NETWORK_ERROR; } if (message.includes('invalid') || message.includes('validation')) { return types_1.ErrorType.INVALID_INPUT; } return types_1.ErrorType.INTERNAL_ERROR; } function isRetryableError(errorType) { return [types_1.ErrorType.TIMEOUT, types_1.ErrorType.NETWORK_ERROR].includes(errorType); } //# sourceMappingURL=formatter.js.map