UNPKG

lokalise-mcp

Version:

The Lokalise MCP Server brings Lokalise's localization power to Claude and AI assistants—manage projects, keys, and translations by chat.

269 lines (268 loc) • 9.82 kB
import { Logger } from "./logger.util.js"; /** * Error types for classification */ export var ErrorType; (function (ErrorType) { ErrorType["AUTH_MISSING"] = "AUTH_MISSING"; ErrorType["AUTH_INVALID"] = "AUTH_INVALID"; ErrorType["API_ERROR"] = "API_ERROR"; ErrorType["NETWORK_ERROR"] = "NETWORK_ERROR"; ErrorType["RATE_LIMIT_EXCEEDED"] = "RATE_LIMIT_EXCEEDED"; ErrorType["NOT_FOUND"] = "NOT_FOUND"; ErrorType["INVALID_PROJECT_ID"] = "INVALID_PROJECT_ID"; ErrorType["INSUFFICIENT_PERMISSIONS"] = "INSUFFICIENT_PERMISSIONS"; ErrorType["VALIDATION_ERROR"] = "VALIDATION_ERROR"; ErrorType["TIMEOUT_ERROR"] = "TIMEOUT_ERROR"; ErrorType["UNEXPECTED_ERROR"] = "UNEXPECTED_ERROR"; })(ErrorType || (ErrorType = {})); /** * Custom error class with type classification */ export class McpError extends Error { constructor(message, type, statusCode, originalError) { super(message); this.name = "McpError"; this.type = type; this.statusCode = statusCode; this.originalError = originalError; } } /** * Create an authentication missing error */ export function createAuthMissingError(message = "Authentication credentials are missing") { return new McpError(message, ErrorType.AUTH_MISSING); } /** * Create an authentication invalid error */ export function createAuthInvalidError(message = "Authentication credentials are invalid") { return new McpError(message, ErrorType.AUTH_INVALID, 401); } /** * Create an API error */ export function createApiError(message, statusCode, originalError) { return new McpError(message, ErrorType.API_ERROR, statusCode, originalError); } /** * Create a network error */ export function createNetworkError(message = "Network error occurred", originalError) { return new McpError(message, ErrorType.NETWORK_ERROR, undefined, originalError); } /** * Create a rate limit exceeded error */ export function createRateLimitError(message = "Rate limit exceeded. Please try again later.", retryAfter) { return new McpError(message, ErrorType.RATE_LIMIT_EXCEEDED, 429, { retryAfter, }); } /** * Create a not found error */ export function createNotFoundError(message = "Resource not found", originalError) { return new McpError(message, ErrorType.NOT_FOUND, 404, originalError); } /** * Create an invalid project ID error */ export function createInvalidProjectIdError(projectId, originalError) { return new McpError(`Invalid project ID: ${projectId}`, ErrorType.INVALID_PROJECT_ID, 400, originalError); } /** * Create an insufficient permissions error */ export function createInsufficientPermissionsError(message = "Insufficient permissions for this operation", originalError) { return new McpError(message, ErrorType.INSUFFICIENT_PERMISSIONS, 403, originalError); } /** * Create a validation error */ export function createValidationError(message = "Validation failed", originalError) { return new McpError(message, ErrorType.VALIDATION_ERROR, 400, originalError); } /** * Create a timeout error */ export function createTimeoutError(message = "Request timed out", originalError) { return new McpError(message, ErrorType.TIMEOUT_ERROR, 408, originalError); } /** * Create an unexpected error */ export function createUnexpectedError(message = "An unexpected error occurred", originalError) { return new McpError(message, ErrorType.UNEXPECTED_ERROR, undefined, originalError); } /** * Ensure an error is an McpError */ export function ensureMcpError(error) { if (error instanceof McpError) { return error; } if (error instanceof Error) { return createUnexpectedError(error.message, error); } return createUnexpectedError(String(error)); } /** * Classify an error based on status code and message content * @param error The error to classify * @param statusCode HTTP status code if available * @param message Error message * @returns Appropriate McpError with correct type */ export function classifyApiError(error, statusCode, message) { const errorMessage = message || (error instanceof Error ? error.message : String(error)); // Classify by status code first switch (statusCode) { case 401: return createAuthInvalidError("Authentication failed. Check your API token."); case 403: return createInsufficientPermissionsError("Access denied. Insufficient permissions."); case 404: return createNotFoundError("Resource not found."); case 408: return createTimeoutError("Request timed out."); case 429: return createRateLimitError("Rate limit exceeded. Please try again later."); case 500: case 502: case 503: case 504: return createApiError(`Server error: ${errorMessage}`, statusCode, error); } // Classify by message content const lowerMessage = errorMessage.toLowerCase(); if (lowerMessage.includes("network") || lowerMessage.includes("connection")) { return createNetworkError(errorMessage, error); } if (lowerMessage.includes("timeout")) { return createTimeoutError(errorMessage, error); } if (lowerMessage.includes("rate limit") || lowerMessage.includes("too many requests")) { return createRateLimitError(errorMessage); } if (lowerMessage.includes("not found") || lowerMessage.includes("does not exist")) { return createNotFoundError(errorMessage, error); } if (lowerMessage.includes("invalid") && lowerMessage.includes("project")) { return createInvalidProjectIdError("Unknown", error); } if (lowerMessage.includes("validation") || lowerMessage.includes("invalid")) { return createValidationError(errorMessage, error); } if (lowerMessage.includes("permission") || lowerMessage.includes("access denied")) { return createInsufficientPermissionsError(errorMessage, error); } // Default to API error return createApiError(errorMessage, statusCode, error); } /** * Get the deepest original error from an error chain * @param error The error to extract the original cause from * @returns The deepest original error or the error itself */ export function getDeepOriginalError(error) { if (!error) { return error; } let current = error; let depth = 0; const maxDepth = 10; // Prevent infinite recursion while (depth < maxDepth && current instanceof Error && "originalError" in current && current.originalError) { current = current.originalError; depth++; } return current; } /** * Format error for MCP tool response */ export function formatErrorForMcpTool(error) { const methodLogger = Logger.forContext("utils/error.util.ts", "formatErrorForMcpTool"); const mcpError = ensureMcpError(error); methodLogger.error(`${mcpError.type} error`, mcpError); // Get the deep original error for additional context const originalError = getDeepOriginalError(mcpError.originalError); // Safely extract details from the original error const errorDetails = originalError instanceof Error ? { message: originalError.message } : originalError; return { content: [ { type: "text", text: `Error: ${mcpError.message}`, }, ], metadata: { errorType: mcpError.type, statusCode: mcpError.statusCode, errorDetails, }, }; } /** * Format error for MCP resource response */ export function formatErrorForMcpResource(error, uri) { const methodLogger = Logger.forContext("utils/error.util.ts", "formatErrorForMcpResource"); const mcpError = ensureMcpError(error); methodLogger.error(`${mcpError.type} error`, mcpError); return { contents: [ { uri, text: `Error: ${mcpError.message}`, mimeType: "text/plain", description: `Error: ${mcpError.type}`, }, ], }; } /** * Handle error in CLI context with improved user feedback */ export function handleCliError(error) { const methodLogger = Logger.forContext("utils/error.util.ts", "handleCliError"); const mcpError = ensureMcpError(error); methodLogger.error(`${mcpError.type} error`, mcpError); // Get the deep original error for more context const originalError = getDeepOriginalError(mcpError.originalError); // Print the error message console.error(`Error: ${mcpError.message}`); // Provide helpful context based on error type if (mcpError.type === ErrorType.AUTH_MISSING) { console.error("\nTip: Make sure to set up your API token in the configuration file or environment variables."); } else if (mcpError.type === ErrorType.AUTH_INVALID) { console.error("\nTip: Check that your API token is correct and has not expired."); } else if (mcpError.type === ErrorType.API_ERROR) { if (mcpError.statusCode === 429) { console.error("\nTip: You may have exceeded your API rate limits. Try again later or upgrade your API plan."); } // Add ip-api.com specific context if available if (originalError && typeof originalError === "object") { const origErr = originalError; if (origErr.status === "fail" && origErr.message) { console.error(`\nAPI returned failure: ${String(origErr.message)}`); } } } // Display DEBUG tip if (process.env.DEBUG !== "mcp:*") { console.error("\nFor more detailed error information, run with DEBUG=mcp:* environment variable."); } process.exit(1); }