Skip to main content
Glama
response-formatter.ts6.72 kB
/** * Response Formatter * * Formats errors for different output contexts: * - JSON-RPC response format (for MCP protocol) * - Claude-friendly text format (for display) */ import { MCPError } from "./types.js"; /** * Format MCPError for JSON-RPC 2.0 error response * * Creates a properly formatted JSON-RPC error response object * that can be sent over the MCP protocol. * * @param error - The normalized MCP error * @param requestId - Optional request ID (null for notifications) * @returns JSON-RPC error response object * * @example * ```typescript * const response = formatErrorResponse(mcpError, "req-123"); * // Returns: * // { * // jsonrpc: "2.0", * // id: "req-123", * // error: { * // code: -32005, * // message: "Resource not found", * // data: { ... } * // } * // } * ``` */ export function formatErrorResponse( error: MCPError, requestId?: string | number | null ): object { return { jsonrpc: "2.0", id: requestId ?? null, error: { code: error.code, message: error.message, data: error.data, }, }; } /** * Format error for Claude-friendly display * * Creates a markdown-formatted, human-readable error message * that Claude can present to the user with actionable guidance. * * @param error - The normalized MCP error * @returns Formatted error message with suggestions * * @example * ```typescript * const display = formatErrorForClaude(mcpError); * // Returns: * // **Error:** Resource not found * // * // **Suggestion:** Verify the ID is correct... * // * // *This error cannot be recovered by retrying.* * ``` */ export function formatErrorForClaude(error: MCPError): string { const parts: string[] = [`**Error:** ${error.message}`]; // Add suggestion if available if (error.data.suggestion) { parts.push(`**Suggestion:** ${error.data.suggestion}`); } // Add field information for validation errors if (error.data.freshbooksError?.field) { parts.push(`**Field:** ${error.data.freshbooksError.field}`); } // Add validation error details if (error.data.validationErrors && error.data.validationErrors.length > 0) { const validationDetails = error.data.validationErrors .map((ve) => `- ${ve.path}: ${ve.message}`) .join("\n"); parts.push(`**Validation Errors:**\n${validationDetails}`); } // Add retry information for rate limiting if (error.data.retryAfter) { parts.push(`**Retry After:** ${error.data.retryAfter} seconds`); } // Add auth URL for authentication errors if (error.data.authUrl) { parts.push(`**Authorization URL:** ${error.data.authUrl}`); } // Add recovery guidance if (!error.data.recoverable) { parts.push("*This error cannot be recovered by retrying.*"); } else { parts.push("*This error may be resolved by retrying the operation.*"); } // Add context information for debugging (if available) if (error.data.context?.tool) { parts.push(`\n*Tool:* \`${error.data.context.tool}\``); } if (error.data.context?.requestId) { parts.push(`*Request ID:* \`${error.data.context.requestId}\``); } return parts.join("\n\n"); } /** * Format error for logging * * Creates a structured log entry suitable for stderr logging. * Omits sensitive information like tokens or PII. * * @param error - The normalized MCP error * @returns Structured log object * * @example * ```typescript * const logEntry = formatErrorForLogging(mcpError); * console.error(JSON.stringify(logEntry)); * ``` */ export function formatErrorForLogging(error: MCPError): object { return { level: "error", code: error.code, message: error.message, tool: error.data.context?.tool, requestId: error.data.context?.requestId, accountId: error.data.context?.accountId ? maskAccountId(error.data.context.accountId as string) : undefined, recoverable: error.data.recoverable, freshbooksCode: error.data.freshbooksError?.code, freshbooksErrno: error.data.freshbooksError?.errno, field: error.data.freshbooksError?.field, timestamp: new Date().toISOString(), }; } /** * Format error details for debugging * * Creates a comprehensive error dump including all details * for debugging purposes. Should only be used in development. * * @param error - The normalized MCP error * @returns Detailed error information */ export function formatErrorForDebug(error: MCPError): object { return { mcpError: { code: error.code, message: error.message, name: error.name, stack: error.stack, }, data: error.data, timestamp: new Date().toISOString(), }; } /** * Get error summary * * Returns a brief one-line summary of the error. * * @param error - The normalized MCP error * @returns Brief error summary */ export function getErrorSummary(error: MCPError): string { const parts = [error.message]; if (error.data.freshbooksError?.field) { parts.push(`(field: ${error.data.freshbooksError.field})`); } if (error.data.context?.tool) { parts.push(`[${error.data.context.tool}]`); } return parts.join(" "); } /** * Check if error is retryable * * Determines if the error can be resolved by retrying. * * @param error - The normalized MCP error * @returns True if error is retryable */ export function isRetryable(error: MCPError): boolean { return error.data.recoverable; } /** * Get suggested retry delay * * Returns the suggested delay before retrying (in milliseconds). * * @param error - The normalized MCP error * @returns Delay in milliseconds, or undefined if not retryable */ export function getRetryDelay(error: MCPError): number | undefined { if (!error.data.recoverable) { return undefined; } // If explicit retry-after is provided (in seconds), convert to ms if (error.data.retryAfter) { return error.data.retryAfter * 1000; } // Default retry delays based on error type switch (error.code) { case -32004: // RATE_LIMITED return 60000; // 60 seconds case -32008: // SERVICE_UNAVAILABLE return 30000; // 30 seconds case -32603: // INTERNAL_ERROR return 5000; // 5 seconds default: return 1000; // 1 second } } /** * Mask account ID for logging * * Masks the account ID to prevent logging full account identifiers. * Shows first 3 and last 3 characters only. * * @param accountId - Account ID to mask * @returns Masked account ID */ function maskAccountId(accountId: string): string { if (accountId.length <= 6) { return "***"; } const start = accountId.substring(0, 3); const end = accountId.substring(accountId.length - 3); return `${start}...${end}`; }

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/Good-Samaritan-Software-LLC/freshbooks-mcp'

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