UNPKG

@mseep/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

372 lines (371 loc) 17.1 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 options - Options for retrieving the 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.fullDiff - Optional flag to retrieve the full diff * @returns Promise with formatted pull request details content, including code changes * @throws Error if pull request retrieval fails */ async function get(options) { const { workspaceSlug, repoSlug, prId, fullDiff } = options; const methodLogger = logger_util_js_1.Logger.forContext('controllers/atlassian.pullrequests.controller.ts', 'get'); methodLogger.debug(`Getting pull request details for ${workspaceSlug}/${repoSlug}/${prId}...`, options); try { // Set up parameters for API calls const params = { workspace: workspaceSlug, repo_slug: repoSlug, pull_request_id: parseInt(prId, 10), }; // Fetch PR details first const pullRequestData = await vendor_atlassian_pullrequests_service_js_1.default.get(params); // Conditionally fetch diffstat or full diff let diffstatData = null; let rawDiff = null; if (fullDiff) { try { rawDiff = await vendor_atlassian_pullrequests_service_js_1.default.getRawDiff(params); methodLogger.debug('Retrieved full raw diff.'); } catch (error) { const message = error instanceof Error ? error.message : String(error); methodLogger.warn(`Failed to retrieve raw diff: ${message}`); } } else { try { diffstatData = await vendor_atlassian_pullrequests_service_js_1.default.getDiffstat(params); methodLogger.debug('Retrieved diffstat.'); } catch (error) { const message = error instanceof Error ? error.message : String(error); methodLogger.warn(`Failed to retrieve diffstat: ${message}`); } } 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: prId, operation: 'retrieving', source: 'controllers/atlassian.pullrequests.controller.ts@get', additionalInfo: { workspaceSlug, repoSlug, prId, fullDiff: !!fullDiff, }, }); } } /** * 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, }, }); } } /** * Create a comment on a specific Bitbucket pull request * @param options - Options for creating a comment on 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 createComment(options) { const methodLogger = logger_util_js_1.Logger.forContext('controllers/atlassian.pullrequests.controller.ts', 'createComment'); methodLogger.debug('Creating comment on 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 using the renamed type 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 renamed service function const commentData = await vendor_atlassian_pullrequests_service_js_1.default.createComment(serviceParams); methodLogger.debug(`Successfully created comment: ${commentData.id}`); return { content: `Comment created successfully for pull request #${options.prId}.`, }; } catch (error) { // Use the standardized error handler (0, error_handler_util_js_1.handleControllerError)(error, { entityType: 'Pull Request Comment', operation: 'creating', source: 'controllers/atlassian.pullrequests.controller.ts@createComment', 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, createComment, create, };