UNPKG

@aashari/mcp-server-atlassian-jira

Version:

Node.js/TypeScript MCP server for Atlassian Jira. Equips AI systems (LLMs) with tools to list/get projects, search/get issues (using JQL/ID), and view dev info (commits, PRs). Connects AI capabilities directly into Jira project management and issue tracki

166 lines (165 loc) 8.73 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const logger_util_js_1 = require("../utils/logger.util.js"); const defaults_util_js_1 = require("../utils/defaults.util.js"); const transport_util_js_1 = require("../utils/transport.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 vendor_atlassian_issues_service_js_1 = __importDefault(require("../services/vendor.atlassian.issues.service.js")); const atlassian_comments_formatter_js_1 = require("./atlassian.comments.formatter.js"); const adf_from_markdown_util_js_1 = require("../utils/adf-from-markdown.util.js"); const adf_from_text_util_js_1 = require("../utils/adf-from-text.util.js"); const formatter_util_js_1 = require("../utils/formatter.util.js"); // Create a contextualized logger for this file const controllerLogger = logger_util_js_1.Logger.forContext('controllers/atlassian.comments.controller.ts'); // Log controller initialization controllerLogger.debug('Jira comments controller initialized'); /** * List comments for a specific Jira issue * * @async * @param {object} options - Controller options for listing comments * @param {string} options.issueIdOrKey - The ID or key of the issue to get comments for * @param {number} [options.limit=25] - Maximum number of comments to return (1-100) * @param {number} [options.startAt] - Index of the first comment to return (0-based offset) * @param {string} [options.orderBy] - Field and direction to order results by (e.g., "created DESC") * @returns {Promise<ControllerResponse>} - Promise containing formatted comments list and pagination information * @throws {Error} - Throws standardized error if operation fails */ async function listComments(options) { const methodLogger = logger_util_js_1.Logger.forContext('controllers/atlassian.comments.controller.ts', 'listComments'); try { methodLogger.debug('Listing comments for issue', options); // Check if credentials exist const credentials = (0, transport_util_js_1.getAtlassianCredentials)(); if (!credentials) { throw (0, error_util_js_1.createAuthMissingError)('List issue comments'); } // Apply defaults for pagination const params = (0, defaults_util_js_1.applyDefaults)(options, { limit: defaults_util_js_1.DEFAULT_PAGE_SIZE, startAt: 0, }); // Cast back to original type to preserve optional properties // Extract the required parameters const { issueIdOrKey, limit, startAt, orderBy } = params; // Map controller options to service parameters const serviceParams = { startAt, maxResults: limit, orderBy, expand: ['renderedBody'], // Include rendered content for HTML fallback }; // Call the service to get comments const commentsData = await vendor_atlassian_issues_service_js_1.default.getComments(issueIdOrKey, serviceParams); // Extract pagination information const pagination = (0, pagination_util_js_1.extractPaginationInfo)(commentsData, pagination_util_js_1.PaginationType.OFFSET, 'Issue Comments'); // Construct Jira base URL for linking const baseUrl = credentials ? `https://${credentials.siteName}.atlassian.net` : undefined; // Format comments using the formatter const formattedContent = (0, atlassian_comments_formatter_js_1.formatCommentsList)(commentsData.comments, issueIdOrKey, baseUrl); // Combine formatted content with pagination information let finalContent = formattedContent; if (pagination && (pagination.hasMore || pagination.count !== undefined)) { const paginationString = (0, formatter_util_js_1.formatPagination)(pagination); finalContent += '\n\n' + paginationString; } // Return the controller response return { content: finalContent, }; } catch (error) { // Handle and propagate errors using standard error handler throw (0, error_handler_util_js_1.handleControllerError)(error, (0, error_handler_util_js_1.buildErrorContext)('Issue Comments', 'listing', 'controllers/atlassian.comments.controller.ts@listComments', options.issueIdOrKey, options)); } } /** * Add a comment to a Jira issue * * @async * @param {object} options - Controller options for adding a comment * @param {string} options.issueIdOrKey - The ID or key of the issue to add a comment to * @param {string} options.commentBody - The content of the comment to add (supports Markdown formatting) * @returns {Promise<ControllerResponse>} - Promise containing confirmation message * @throws {Error} - Throws standardized error if operation fails */ async function addComment(options) { const methodLogger = logger_util_js_1.Logger.forContext('controllers/atlassian.comments.controller.ts', 'addComment'); try { methodLogger.debug('Adding comment to issue', { issueIdOrKey: options.issueIdOrKey, commentBodyLength: options.commentBody?.length || 0, }); // Check if credentials exist const credentials = (0, transport_util_js_1.getAtlassianCredentials)(); if (!credentials) { throw (0, error_util_js_1.createAuthMissingError)('Atlassian credentials required to add issue comment'); } // Extract the required parameters const { issueIdOrKey, commentBody } = options; // Validate the comment body is not empty if (!commentBody || commentBody.trim() === '') { throw (0, error_util_js_1.createApiError)('Comment body cannot be empty', 400); } // Convert markdown to ADF format with explicit error handling let adfBody; try { methodLogger.debug('Converting Markdown to ADF format'); adfBody = (0, adf_from_markdown_util_js_1.markdownToAdf)(commentBody); } catch (adfError) { methodLogger.error('ADF conversion failed:', adfError); // Try fallback to simple plain text conversion methodLogger.debug('Falling back to simple text conversion'); try { adfBody = (0, adf_from_text_util_js_1.textToAdf)(commentBody); } catch (textAdfError) { // If even the simple conversion fails, throw a validation error methodLogger.error('Fallback text-to-ADF conversion failed:', textAdfError); throw (0, error_util_js_1.createApiError)('Failed to convert comment to Jira-compatible format. Please simplify the comment content.', 400, { originalError: adfError, fallbackError: textAdfError }); } // Log the fallback success methodLogger.info('Used text fallback for ADF conversion'); } // Prepare the comment data for service const commentData = { body: adfBody, expand: ['renderedBody'], // Include rendered content for HTML fallback }; // Call the service to add the comment const createdComment = await vendor_atlassian_issues_service_js_1.default.addComment(issueIdOrKey, commentData); // Construct Jira base URL for linking const baseUrl = credentials ? `https://${credentials.siteName}.atlassian.net` : undefined; // Format the confirmation message const formattedContent = (0, atlassian_comments_formatter_js_1.formatAddedCommentConfirmation)(createdComment, issueIdOrKey, baseUrl); // Return the controller response return { content: formattedContent, }; } catch (error) { // Handle and propagate errors using standard error handler throw (0, error_handler_util_js_1.handleControllerError)(error, (0, error_handler_util_js_1.buildErrorContext)('Issue Comment', 'adding', 'controllers/atlassian.comments.controller.ts@addComment', options.issueIdOrKey, { commentBodyLength: options.commentBody?.length || 0, commentBodyPreview: options.commentBody ? options.commentBody.length > 100 ? options.commentBody.substring(0, 100) + '...' : options.commentBody : '<empty>', })); } } exports.default = { listComments, addComment, };