UNPKG

@aashari/mcp-server-atlassian-bitbucket

Version:

Node.js/TypeScript MCP server for Atlassian Bitbucket. Enables AI systems (LLMs) to interact with workspaces, repositories, and pull requests via tools (list, get, comment, search). Connects AI directly to version control workflows through the standard MC

236 lines (235 loc) 9 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.McpError = exports.ErrorType = void 0; exports.getDeepOriginalError = getDeepOriginalError; exports.createAuthMissingError = createAuthMissingError; exports.createAuthInvalidError = createAuthInvalidError; exports.createApiError = createApiError; exports.createUnexpectedError = createUnexpectedError; exports.ensureMcpError = ensureMcpError; exports.formatErrorForMcpTool = formatErrorForMcpTool; exports.formatErrorForMcpResource = formatErrorForMcpResource; exports.handleCliError = handleCliError; const logger_util_js_1 = require("./logger.util.js"); const formatter_util_js_1 = require("./formatter.util.js"); /** * Error types for classification */ var ErrorType; (function (ErrorType) { ErrorType["AUTH_MISSING"] = "AUTH_MISSING"; ErrorType["AUTH_INVALID"] = "AUTH_INVALID"; ErrorType["API_ERROR"] = "API_ERROR"; ErrorType["UNEXPECTED_ERROR"] = "UNEXPECTED_ERROR"; })(ErrorType || (exports.ErrorType = ErrorType = {})); /** * Custom error class with type classification */ class McpError extends Error { constructor(message, type, statusCode, originalError) { super(message); this.name = 'McpError'; this.type = type; this.statusCode = statusCode; this.originalError = originalError; // Set errorType based on type switch (type) { case ErrorType.AUTH_MISSING: case ErrorType.AUTH_INVALID: this.errorType = 'AUTHENTICATION_REQUIRED'; break; case ErrorType.API_ERROR: this.errorType = statusCode === 404 ? 'NOT_FOUND' : statusCode === 429 ? 'RATE_LIMIT_EXCEEDED' : 'API_ERROR'; break; case ErrorType.UNEXPECTED_ERROR: default: this.errorType = 'UNEXPECTED_ERROR'; break; } } } exports.McpError = McpError; /** * Helper to unwrap nested McpErrors and return the deepest original error. * This is useful when an McpError contains another McpError as `originalError` * which in turn may wrap the vendor (Bitbucket) error text or object. */ 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; } /** * Create an authentication missing error */ function createAuthMissingError(message = 'Authentication credentials are missing', originalError) { return new McpError(message, ErrorType.AUTH_MISSING, undefined, originalError); } /** * Create an authentication invalid error */ function createAuthInvalidError(message = 'Authentication credentials are invalid', originalError) { return new McpError(message, ErrorType.AUTH_INVALID, 401, originalError); } /** * Create an API error */ function createApiError(message, statusCode, originalError) { return new McpError(message, ErrorType.API_ERROR, statusCode, originalError); } /** * Create an unexpected error */ function createUnexpectedError(message = 'An unexpected error occurred', originalError) { return new McpError(message, ErrorType.UNEXPECTED_ERROR, undefined, originalError); } /** * Ensure an error is an McpError */ function ensureMcpError(error) { if (error instanceof McpError) { return error; } if (error instanceof Error) { return createUnexpectedError(error.message, error); } return createUnexpectedError(String(error)); } /** * Format error for MCP tool response * Includes raw error details in the text content so AI can see the full context */ function formatErrorForMcpTool(error) { const methodLogger = logger_util_js_1.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); // Build error text with full details visible to AI let errorText = `Error: ${mcpError.message}`; // Add status code if available if (mcpError.statusCode) { errorText += `\nHTTP Status: ${mcpError.statusCode}`; } // Add raw error details if available (this is the actual Bitbucket API response) if (originalError && originalError !== mcpError.message) { if (typeof originalError === 'object') { errorText += `\n\nRaw API Response:\n${JSON.stringify(originalError, null, 2)}`; } else if (typeof originalError === 'string') { errorText += `\n\nRaw API Response:\n${originalError}`; } } return { content: [ { type: 'text', text: errorText, }, ], isError: true, }; } /** * Format error for MCP resource response */ function formatErrorForMcpResource(error, uri) { const methodLogger = logger_util_js_1.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 */ function handleCliError(error) { const methodLogger = logger_util_js_1.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); // Build a well-formatted CLI output using markdown-style helpers const cliLines = []; // Primary error headline cliLines.push(`❌ ${mcpError.message}`); // Status code (if any) if (mcpError.statusCode) { cliLines.push(`HTTP Status: ${mcpError.statusCode}`); } // Separator cliLines.push((0, formatter_util_js_1.formatSeparator)()); // Provide helpful context based on error type if (mcpError.type === ErrorType.AUTH_MISSING) { cliLines.push('Tip: Make sure to set up your Atlassian credentials in the configuration file or environment variables:'); cliLines.push('- ATLASSIAN_SITE_NAME, ATLASSIAN_USER_EMAIL, and ATLASSIAN_API_TOKEN; or'); cliLines.push('- ATLASSIAN_BITBUCKET_USERNAME and ATLASSIAN_BITBUCKET_APP_PASSWORD'); } else if (mcpError.type === ErrorType.AUTH_INVALID) { cliLines.push('Tip: Check that your Atlassian API token or app password is correct and has not expired.'); cliLines.push('Also verify that the configured user has access to the requested resource.'); } else if (mcpError.type === ErrorType.API_ERROR) { if (mcpError.statusCode === 429) { cliLines.push('Tip: You may have exceeded your Bitbucket API rate limits. Try again later.'); } } // Vendor error details (if available) if (originalError) { cliLines.push('Bitbucket API Error:'); cliLines.push('```'); if (typeof originalError === 'object' && originalError !== null) { // Try to extract the most useful parts of Bitbucket's error response const origErr = originalError; if (origErr.error && typeof origErr.error === 'object') { // Format {"error": {"message": "..."}} structure const bitbucketError = origErr.error; cliLines.push(`Message: ${bitbucketError.message || 'Unknown error'}`); if (bitbucketError.detail) cliLines.push(`Detail: ${bitbucketError.detail}`); } else if (origErr.message) { // Simple message cliLines.push(`${String(origErr.message)}`); } else { // Fall back to JSON representation for anything else cliLines.push(JSON.stringify(originalError, null, 2)); } } else { cliLines.push(String(originalError).trim()); } cliLines.push('```'); } // Display DEBUG tip if (!process.env.DEBUG || !process.env.DEBUG.includes('mcp:')) { cliLines.push('For more detailed error information, run with DEBUG=mcp:* environment variable.'); } console.error(cliLines.join('\n')); process.exit(1); }