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

243 lines (242 loc) 12.1 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); const logger_util_js_1 = require("../utils/logger.util.js"); const error_handler_util_js_1 = require("../utils/error-handler.util.js"); const defaults_util_js_1 = require("../utils/defaults.util.js"); const pagination_util_js_1 = require("../utils/pagination.util.js"); const formatter_util_js_1 = require("../utils/formatter.util.js"); const diffService = __importStar(require("../services/vendor.atlassian.repositories.diff.service.js")); const atlassian_diff_formatter_js_1 = require("./atlassian.diff.formatter.js"); const workspace_util_js_1 = require("../utils/workspace.util.js"); const controllerLogger = logger_util_js_1.Logger.forContext('controllers/atlassian.diff.controller.ts'); controllerLogger.debug('Bitbucket diff controller initialized'); /** * Compare two branches and return the differences * * @param options - Options for branch comparison * @returns Promise with formatted diff content and pagination */ async function branchDiff(options) { const methodLogger = controllerLogger.forMethod('branchDiff'); try { methodLogger.debug('Comparing branches', options); // Apply defaults const defaults = { limit: defaults_util_js_1.DEFAULT_PAGE_SIZE, includeFullDiff: true, destinationBranch: 'main', // Default to main if not provided topic: false, // Default to topic=false which shows all changes between branches }; // Explicitly cast the result of applyDefaults to preserve the original types const params = (0, defaults_util_js_1.applyDefaults)(options, defaults); // Handle optional workspaceSlug if (!params.workspaceSlug) { methodLogger.debug('No workspace provided, fetching default workspace'); const defaultWorkspace = await (0, workspace_util_js_1.getDefaultWorkspace)(); if (!defaultWorkspace) { throw new Error('Could not determine a default workspace. Please provide a workspaceSlug.'); } params.workspaceSlug = defaultWorkspace; methodLogger.debug(`Using default workspace: ${params.workspaceSlug}`); } // Construct the spec (e.g., "main..feature") // NOTE: Bitbucket API expects the destination branch first, then the source branch // This is the opposite of what some Git tools use (e.g., git diff source..destination) // The diff shows changes that would need to be applied to destination to match source // // IMPORTANT: This behavior is counterintuitive in two ways: // 1. The parameter names "sourceBranch" and "destinationBranch" suggest a certain direction, // but the output is displayed as "destinationBranch → sourceBranch" // 2. When comparing branches with newer content in the feature branch (source), full diffs // might only show when using parameters in one order, and only summaries in the other order // // We document this behavior clearly in the CLI and Tool interfaces const spec = `${params.destinationBranch}..${params.sourceBranch}`; methodLogger.debug(`Using diff spec: ${spec}`); try { // Fetch diffstat for the branches const diffstat = await diffService.getDiffstat({ workspace: params.workspaceSlug, repo_slug: params.repoSlug, spec, pagelen: params.limit, cursor: params.cursor, topic: params.topic, }); // Extract pagination info const pagination = (0, pagination_util_js_1.extractPaginationInfo)(diffstat, pagination_util_js_1.PaginationType.PAGE); // Fetch full diff if requested let rawDiff = null; if (params.includeFullDiff) { rawDiff = await diffService.getRawDiff({ workspace: params.workspaceSlug, repo_slug: params.repoSlug, spec, }); } // Format the results let content = params.includeFullDiff && rawDiff ? (0, atlassian_diff_formatter_js_1.formatFullDiff)(diffstat, rawDiff, params.destinationBranch, params.sourceBranch) : (0, atlassian_diff_formatter_js_1.formatDiffstat)(diffstat, params.destinationBranch, params.sourceBranch); // Add pagination information if available if (pagination && (pagination.hasMore || pagination.count !== undefined)) { const paginationString = (0, formatter_util_js_1.formatPagination)(pagination); content += '\n\n' + paginationString; } return { content, }; } catch (error) { // Enhance error handling for common diff-specific errors if (error instanceof Error && error.message.includes('source or destination could not be found')) { // Create a more user-friendly error message throw new Error(`Unable to generate diff between '${params.sourceBranch}' and '${params.destinationBranch}'. ` + `One or both of these branches may not exist in the repository. ` + `Please verify both branch names and ensure you have access to view them.`); } // Re-throw other errors to be handled by the outer catch block throw error; } } catch (error) { throw (0, error_handler_util_js_1.handleControllerError)(error, { entityType: 'Branch Diff', operation: 'comparing branches', source: 'controllers/atlassian.diff.controller.ts@branchDiff', additionalInfo: options, }); } } /** * Compare two commits and return the differences * * @param options - Options for commit comparison * @returns Promise with formatted diff content and pagination */ async function commitDiff(options) { const methodLogger = controllerLogger.forMethod('commitDiff'); try { methodLogger.debug('Comparing commits', options); // Apply defaults const defaults = { limit: defaults_util_js_1.DEFAULT_PAGE_SIZE, includeFullDiff: true, topic: false, // Default to topic=false which shows all changes between commits }; // Explicitly cast the result of applyDefaults to preserve the original types const params = (0, defaults_util_js_1.applyDefaults)(options, defaults); // Handle optional workspaceSlug if (!params.workspaceSlug) { methodLogger.debug('No workspace provided, fetching default workspace'); const defaultWorkspace = await (0, workspace_util_js_1.getDefaultWorkspace)(); if (!defaultWorkspace) { throw new Error('Could not determine a default workspace. Please provide a workspaceSlug.'); } params.workspaceSlug = defaultWorkspace; methodLogger.debug(`Using default workspace: ${params.workspaceSlug}`); } // Construct the spec (e.g., "a1b2c3d..e4f5g6h") // NOTE: Bitbucket API expects the base/since commit first, then the target/until commit // The diff shows changes that would need to be applied to base to match target // // IMPORTANT: The parameter names are counterintuitive to how they must be used: // 1. For proper results with full code changes, sinceCommit should be the NEWER commit, // and untilCommit should be the OLDER commit (reverse chronological order) // 2. If used with chronological order (older → newer), the result may show "No changes detected" // // We document this behavior clearly in the CLI and Tool interfaces const spec = `${params.sinceCommit}..${params.untilCommit}`; methodLogger.debug(`Using diff spec: ${spec}`); try { // Fetch diffstat for the commits const diffstat = await diffService.getDiffstat({ workspace: params.workspaceSlug, repo_slug: params.repoSlug, spec, pagelen: params.limit, cursor: params.cursor, }); // Extract pagination info const pagination = (0, pagination_util_js_1.extractPaginationInfo)(diffstat, pagination_util_js_1.PaginationType.PAGE); // Fetch full diff if requested let rawDiff = null; if (params.includeFullDiff) { rawDiff = await diffService.getRawDiff({ workspace: params.workspaceSlug, repo_slug: params.repoSlug, spec, }); } // Format the results let content = params.includeFullDiff && rawDiff ? (0, atlassian_diff_formatter_js_1.formatFullDiff)(diffstat, rawDiff, params.sinceCommit, params.untilCommit) : (0, atlassian_diff_formatter_js_1.formatDiffstat)(diffstat, params.sinceCommit, params.untilCommit); // Add pagination information if available if (pagination && (pagination.hasMore || pagination.count !== undefined)) { const paginationString = (0, formatter_util_js_1.formatPagination)(pagination); content += '\n\n' + paginationString; } return { content, }; } catch (error) { // Enhance error handling for common diff-specific errors if (error instanceof Error && error.message.includes('source or destination could not be found')) { // Create a more user-friendly error message throw new Error(`Unable to generate diff between commits '${params.sinceCommit}' and '${params.untilCommit}'. ` + `One or both of these commits may not exist in the repository or may be in the wrong order. ` + `Please verify both commit hashes and ensure you have access to view them.`); } // Re-throw other errors to be handled by the outer catch block throw error; } } catch (error) { throw (0, error_handler_util_js_1.handleControllerError)(error, { entityType: 'Commit Diff', operation: 'comparing commits', source: 'controllers/atlassian.diff.controller.ts@commitDiff', additionalInfo: options, }); } } exports.default = { branchDiff, commitDiff };