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

353 lines (352 loc) 16.6 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const vendor_atlassian_pullrequests_service_js_1 = __importDefault(require("../services/vendor.atlassian.pullrequests.service.js")); const logger_util_js_1 = require("../utils/logger.util.js"); const error_util_js_1 = require("../utils/error.util.js"); const error_handler_util_js_1 = require("../utils/error-handler.util.js"); const pagination_util_js_1 = require("../utils/pagination.util.js"); const atlassian_pullrequests_formatter_js_1 = require("./atlassian.pullrequests.formatter.js"); const query_util_js_1 = require("../utils/query.util.js"); const defaults_util_js_1 = require("../utils/defaults.util.js"); /** * Controller for managing Bitbucket pull requests. * Provides functionality for listing pull requests and retrieving pull request details. */ // Create a contextualized logger for this file const controllerLogger = logger_util_js_1.Logger.forContext('controllers/atlassian.pullrequests.controller.ts'); // Log controller initialization controllerLogger.debug('Bitbucket pull requests controller initialized'); /** * List Bitbucket pull requests with optional filtering * @param options - Options for listing pull requests * @param options.workspaceSlug - The workspace slug containing the repository * @param options.repoSlug - The repository slug to list pull requests from * @param options.state - Pull request state filter * @param options.limit - Maximum number of pull requests to return * @param options.cursor - Pagination cursor for retrieving the next set of results * @returns Promise with formatted pull request list content and pagination information */ async function list(options) { const methodLogger = logger_util_js_1.Logger.forContext('controllers/atlassian.pullrequests.controller.ts', 'list'); methodLogger.debug('Listing Bitbucket pull requests...', options); try { if (!options.workspaceSlug || !options.repoSlug) { throw (0, error_util_js_1.createApiError)('Both workspaceSlug and repoSlug parameters are required'); } // Create defaults object with proper typing const defaults = { limit: defaults_util_js_1.DEFAULT_PAGE_SIZE, }; // Apply defaults const mergedOptions = (0, defaults_util_js_1.applyDefaults)(options, defaults); // Process the query parameter let queryParam; // State filter takes precedence over free-text query if (mergedOptions.state) { queryParam = `state="${mergedOptions.state}"`; } else if (mergedOptions.query) { // Format the free-text query using the utility function queryParam = (0, query_util_js_1.formatBitbucketQuery)(mergedOptions.query, 'title'); } // Map controller filters to service params const serviceParams = { // Required parameters workspace: mergedOptions.workspaceSlug, repo_slug: mergedOptions.repoSlug, // Optional parameters ...(queryParam && { q: queryParam }), pagelen: mergedOptions.limit, page: mergedOptions.cursor ? parseInt(mergedOptions.cursor, 10) : undefined, }; methodLogger.debug('Using filters:', serviceParams); // Call the service to get the pull requests data const pullRequestsData = await vendor_atlassian_pullrequests_service_js_1.default.list(serviceParams); // Log the count of pull requests retrieved const count = pullRequestsData.values?.length || 0; methodLogger.debug(`Retrieved ${count} pull requests`); // Extract pagination information using the utility const pagination = (0, pagination_util_js_1.extractPaginationInfo)(pullRequestsData, pagination_util_js_1.PaginationType.PAGE); // Format the pull requests data for display using the formatter const formattedPullRequests = (0, atlassian_pullrequests_formatter_js_1.formatPullRequestsList)(pullRequestsData); return { content: formattedPullRequests, pagination, }; } catch (error) { // Use the standardized error handler (0, error_handler_util_js_1.handleControllerError)(error, { entityType: 'Pull Requests', operation: 'listing', source: 'controllers/atlassian.pullrequests.controller.ts@list', additionalInfo: { options, workspaceSlug: options.workspaceSlug, repoSlug: options.repoSlug, }, }); } } /** * Get details of a specific Bitbucket pull request, including code changes * @param identifier - Object containing pull request identifiers * @param identifier.workspaceSlug - The workspace slug containing the repository * @param identifier.repoSlug - The repository slug containing the pull request * @param identifier.prId - The pull request ID * @returns Promise with formatted pull request details content, including code changes * @throws Error if pull request retrieval fails */ async function get(identifier) { const { workspaceSlug, repoSlug, prId } = identifier; const methodLogger = logger_util_js_1.Logger.forContext('controllers/atlassian.pullrequests.controller.ts', 'get'); methodLogger.debug(`Getting pull request details for ${workspaceSlug}/${repoSlug}/${prId}...`); try { // Set up parameters for API calls const params = { workspace: workspaceSlug, repo_slug: repoSlug, pull_request_id: parseInt(prId, 10), }; // Fetch pull request details, diffstat, and raw diff in parallel for better performance const [pullRequestData, diffstatData, rawDiff] = await Promise.all([ vendor_atlassian_pullrequests_service_js_1.default.get(params), vendor_atlassian_pullrequests_service_js_1.default.getDiffstat(params).catch((error) => { // Log but don't fail if diffstat can't be retrieved methodLogger.warn(`Failed to retrieve diffstat: ${error.message}`); return null; }), vendor_atlassian_pullrequests_service_js_1.default.getRawDiff(params).catch((error) => { // Log but don't fail if diff can't be retrieved methodLogger.warn(`Failed to retrieve raw diff: ${error.message}`); return null; }), ]); methodLogger.debug(`Retrieved pull request: ${pullRequestData.id}`); // Format the pull request data with diff information const formattedPullRequest = (0, atlassian_pullrequests_formatter_js_1.formatPullRequestDetails)(pullRequestData, diffstatData, rawDiff); return { content: formattedPullRequest, }; } catch (error) { // Use the standardized error handler (0, error_handler_util_js_1.handleControllerError)(error, { entityType: 'Pull Request', entityId: identifier, operation: 'retrieving', source: 'controllers/atlassian.pullrequests.controller.ts@get', }); } } /** * List comments on a specific Bitbucket pull request * @param options - Options for listing pull request comments * @param options.workspaceSlug - The workspace slug containing the repository * @param options.repoSlug - The repository slug containing the pull request * @param options.prId - The pull request ID * @param options.limit - Maximum number of comments to return * @param options.cursor - Pagination cursor for retrieving the next set of results * @returns Promise with formatted pull request comments content and pagination information */ async function listComments(options) { const methodLogger = logger_util_js_1.Logger.forContext('controllers/atlassian.pullrequests.controller.ts', 'listComments'); methodLogger.debug('Listing Bitbucket pull request comments...', options); try { if (!options.workspaceSlug || !options.repoSlug || !options.prId) { throw (0, error_util_js_1.createApiError)('workspaceSlug, repoSlug, and prId parameters are all required'); } // Validate pull request ID const prId = parseInt(options.prId, 10); if (isNaN(prId) || prId <= 0) { throw (0, error_util_js_1.createApiError)('Pull request ID must be a positive integer'); } // Create defaults object with proper typing const defaults = { limit: defaults_util_js_1.DEFAULT_PAGE_SIZE, sort: '-updated_on', }; // Apply defaults const mergedOptions = (0, defaults_util_js_1.applyDefaults)(options, defaults); // Map controller options to service params const serviceParams = { workspace: mergedOptions.workspaceSlug, repo_slug: mergedOptions.repoSlug, pull_request_id: prId, pagelen: mergedOptions.limit, page: mergedOptions.cursor ? parseInt(mergedOptions.cursor, 10) : undefined, sort: mergedOptions.sort, }; methodLogger.debug('Using service parameters:', serviceParams); // Call the service to get the pull request comments const commentsData = await vendor_atlassian_pullrequests_service_js_1.default.getComments(serviceParams); // Log the count of comments retrieved const count = commentsData.values?.length || 0; methodLogger.debug(`Retrieved ${count} pull request comments`); // Extract pagination information using the utility const pagination = (0, pagination_util_js_1.extractPaginationInfo)(commentsData, pagination_util_js_1.PaginationType.PAGE); // Format the comments data for display using the formatter const formattedComments = (0, atlassian_pullrequests_formatter_js_1.formatPullRequestComments)(commentsData); return { content: formattedComments, pagination, }; } catch (error) { // Use the standardized error handler (0, error_handler_util_js_1.handleControllerError)(error, { entityType: 'Pull Request Comments', operation: 'listing', source: 'controllers/atlassian.pullrequests.controller.ts@listComments', additionalInfo: { options, workspaceSlug: options.workspaceSlug, repoSlug: options.repoSlug, prId: options.prId, }, }); } } /** * Add a comment to a specific Bitbucket pull request * @param options - Options for adding a comment to a pull request * @param options.workspaceSlug - The workspace slug containing the repository * @param options.repoSlug - The repository slug containing the pull request * @param options.prId - The pull request ID * @param options.content - The content of the comment * @param options.inline - Optional inline comment location * @returns Promise with the result of adding the comment */ async function addComment(options) { const methodLogger = logger_util_js_1.Logger.forContext('controllers/atlassian.pullrequests.controller.ts', 'addComment'); methodLogger.debug('Adding comment to Bitbucket pull request...', options); try { if (!options.workspaceSlug || !options.repoSlug || !options.prId) { throw (0, error_util_js_1.createApiError)('workspaceSlug, repoSlug, and prId parameters are all required'); } if (!options.content) { throw (0, error_util_js_1.createApiError)('Comment content is required'); } // Validate pull request ID const prId = parseInt(options.prId, 10); if (isNaN(prId) || prId <= 0) { throw (0, error_util_js_1.createApiError)('Pull request ID must be a positive integer'); } // Map controller options to service params const serviceParams = { workspace: options.workspaceSlug, repo_slug: options.repoSlug, pull_request_id: prId, content: { raw: options.content, }, }; // Add inline comment parameters if provided if (options.inline && options.inline.path) { serviceParams['inline'] = { path: options.inline.path, to: options.inline.line, }; } methodLogger.debug('Using service parameters:', serviceParams); // Call the service to add the comment const commentData = await vendor_atlassian_pullrequests_service_js_1.default.addComment(serviceParams); methodLogger.debug(`Successfully added comment: ${commentData.id}`); return { content: `Comment added successfully to pull request #${options.prId}.`, }; } catch (error) { // Use the standardized error handler (0, error_handler_util_js_1.handleControllerError)(error, { entityType: 'Pull Request Comment', operation: 'adding', source: 'controllers/atlassian.pullrequests.controller.ts@addComment', additionalInfo: { options, workspaceSlug: options.workspaceSlug, repoSlug: options.repoSlug, prId: options.prId, }, }); } } /** * Create a new pull request * @param options - Options for creating a new pull request * @param options.workspaceSlug - Workspace slug containing the repository * @param options.repoSlug - Repository slug to create the pull request in * @param options.title - Title of the pull request * @param options.sourceBranch - Source branch name * @param options.destinationBranch - Destination branch name (defaults to the repository's main branch) * @param options.description - Optional description for the pull request * @param options.closeSourceBranch - Whether to close the source branch after merge * @returns Promise with formatted pull request details content */ async function create(options) { const methodLogger = logger_util_js_1.Logger.forContext('controllers/atlassian.pullrequests.controller.ts', 'create'); methodLogger.debug('Creating new pull request...', options); try { if (!options.workspaceSlug || !options.repoSlug) { throw (0, error_util_js_1.createApiError)('workspaceSlug and repoSlug parameters are required'); } if (!options.title) { throw (0, error_util_js_1.createApiError)('Pull request title is required'); } if (!options.sourceBranch) { throw (0, error_util_js_1.createApiError)('Source branch is required'); } // The API requires a destination branch, use default if not provided const destinationBranch = options.destinationBranch || 'main'; // Map controller options to service params const serviceParams = { workspace: options.workspaceSlug, repo_slug: options.repoSlug, title: options.title, source: { branch: { name: options.sourceBranch, }, }, destination: { branch: { name: destinationBranch, }, }, }; // Add optional parameters if provided if (options.description) { serviceParams.description = options.description; } if (options.closeSourceBranch !== undefined) { serviceParams.close_source_branch = options.closeSourceBranch; } // Call the service to create the pull request const pullRequest = await vendor_atlassian_pullrequests_service_js_1.default.create(serviceParams); // Format the created pull request details for response return { content: (0, atlassian_pullrequests_formatter_js_1.formatPullRequestDetails)(pullRequest), }; } catch (error) { throw (0, error_handler_util_js_1.handleControllerError)(error, { entityType: 'Pull Request', operation: 'create', source: 'controllers/atlassian.pullrequests.controller.ts', }); } } exports.default = { list, get, listComments, addComment, create, };