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

212 lines (211 loc) 9.71 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_issues_service_js_1 = __importDefault(require("../services/vendor.atlassian.issues.service.js")); const vendor_atlassian_devinfo_service_js_1 = __importDefault(require("../services/vendor.atlassian.devinfo.service.js")); const logger_util_js_1 = require("../utils/logger.util.js"); const error_handler_util_js_1 = require("../utils/error-handler.util.js"); const error_util_js_1 = require("../utils/error.util.js"); const pagination_util_js_1 = require("../utils/pagination.util.js"); const transport_util_js_1 = require("../utils/transport.util.js"); const atlassian_issues_formatter_js_1 = require("./atlassian.issues.formatter.js"); const defaults_util_js_1 = require("../utils/defaults.util.js"); /** * Controller for managing Jira issues. * Provides functionality for listing issues and retrieving issue details. */ // Create a contextualized logger for this file const controllerLogger = logger_util_js_1.Logger.forContext('controllers/atlassian.issues.controller.ts'); // Log controller initialization controllerLogger.debug('Jira issues controller initialized'); /** * List Jira issues with optional filtering * @param options - Optional filter options for the issues list * @param options.jql - JQL query to filter issues * @param options.limit - Maximum number of issues to return * @param options.cursor - Pagination cursor for retrieving the next set of results * @returns Promise with formatted issue list content and pagination information */ async function list(options = {}) { const methodLogger = logger_util_js_1.Logger.forContext('controllers/atlassian.issues.controller.ts', 'list'); methodLogger.debug('Listing Jira issues...', options); try { const credentials = (0, transport_util_js_1.getAtlassianCredentials)(); if (!credentials) { throw new Error('Atlassian credentials are required for this operation'); } // Create a defaults object with proper typing const defaults = { limit: defaults_util_js_1.DEFAULT_PAGE_SIZE, jql: '', cursor: '', }; // Apply defaults const mergedOptions = (0, defaults_util_js_1.applyDefaults)(options, defaults); // Set default JQL to sort by updated date if not provided let jql = mergedOptions.jql; if (!jql) { jql = 'ORDER BY updated DESC'; } else if (!jql.toUpperCase().includes('ORDER BY')) { // Append default sorting to existing JQL if it doesn't include ORDER BY jql += ' ORDER BY updated DESC'; } // Set default filters and hardcoded values const filters = { // Optional filters with defaults jql: jql, // Always include all fields fields: [ 'summary', 'description', 'status', 'issuetype', 'priority', 'project', 'assignee', 'reporter', 'creator', 'created', 'updated', 'timetracking', 'comment', 'attachment', 'worklog', 'issuelinks', ], // Pagination maxResults: mergedOptions.limit, startAt: mergedOptions.cursor ? parseInt(mergedOptions.cursor, 10) : 0, }; methodLogger.debug('Using filters:', filters); const issuesData = await vendor_atlassian_issues_service_js_1.default.search(filters); // Log only the count of issues returned instead of the entire response const issuesCount = issuesData.issues?.length || 0; methodLogger.debug(`Retrieved ${issuesCount} issues`); // Extract pagination information using the utility const pagination = (0, pagination_util_js_1.extractPaginationInfo)(issuesData, pagination_util_js_1.PaginationType.OFFSET); // Ensure pagination count is set to the actual number of issues retrieved pagination.count = issuesCount; methodLogger.debug(`Next cursor: ${pagination.nextCursor}`); // Format the issues data for display using the formatter const formattedIssues = (0, atlassian_issues_formatter_js_1.formatIssuesList)({ issues: issuesData.issues || [], baseUrl: `https://${credentials.siteName}.atlassian.net`, }, pagination); return { content: formattedIssues, pagination, }; } catch (error) { // Use the standardized error handler return (0, error_handler_util_js_1.handleControllerError)(error, { entityType: 'Issues', operation: 'listing', source: 'controllers/atlassian.issues.controller.ts@list', additionalInfo: { options, jql: options.jql }, }); } } /** * Get details of a specific Jira issue * @param identifier - Object containing the ID or key of the issue to retrieve * @param identifier.issueIdOrKey - The ID or key of the issue (e.g., "PROJ-123" or "10001") * @param _options - Options for retrieving the issue (currently not used, but maintained for future extensibility) * @returns Promise with formatted issue details content * @throws Error if issue retrieval fails */ async function get(identifier, _options = {}) { const { issueIdOrKey } = identifier; const methodLogger = logger_util_js_1.Logger.forContext('controllers/atlassian.issues.controller.ts', 'get'); methodLogger.debug(`Getting Jira issue with ID/key: ${issueIdOrKey}...`); // Validate issue ID format if (!issueIdOrKey || issueIdOrKey === 'invalid') { throw (0, error_util_js_1.createApiError)('Invalid issue ID', 400); } try { // Always include all fields const fields = [ 'summary', 'description', 'status', 'issuetype', 'priority', 'project', 'assignee', 'reporter', 'creator', 'created', 'updated', 'timetracking', 'comment', 'attachment', 'worklog', 'issuelinks', ]; // Get issue details const issueData = await vendor_atlassian_issues_service_js_1.default.get(issueIdOrKey, { fields, }); methodLogger.debug(`Retrieved issue: ${issueIdOrKey}`); // Format the issue data for display using the formatter const formattedIssue = (0, atlassian_issues_formatter_js_1.formatIssueDetails)(issueData); // Get development information if available let devInfoSummary = null; let devInfoCommits = null; let devInfoBranches = null; let devInfoPullRequests = null; try { // Use the issue ID to get development information methodLogger.debug(`Getting development information for issue ID: ${issueData.id}...`); // Get summary first to check if there's any dev info available devInfoSummary = await vendor_atlassian_devinfo_service_js_1.default.getSummary(issueData.id); // If there's any development information available, get the details if (devInfoSummary?.summary?.repository?.overall?.count || devInfoSummary?.summary?.branch?.overall?.count || devInfoSummary?.summary?.pullrequest?.overall?.count) { // Fetch detailed development information methodLogger.debug('Development information available, fetching details...'); // Run these in parallel for better performance [devInfoCommits, devInfoBranches, devInfoPullRequests] = await Promise.all([ vendor_atlassian_devinfo_service_js_1.default.getCommits(issueData.id), vendor_atlassian_devinfo_service_js_1.default.getBranches(issueData.id), vendor_atlassian_devinfo_service_js_1.default.getPullRequests(issueData.id), ]); methodLogger.debug('Successfully retrieved development information'); } else { methodLogger.debug('No development information available for this issue'); } } catch (error) { // Log the error but don't fail the whole request methodLogger.warn('Failed to retrieve development information:', error); } // Format development information if available const formattedDevInfo = (0, atlassian_issues_formatter_js_1.formatDevelopmentInfo)(devInfoSummary, devInfoCommits, devInfoBranches, devInfoPullRequests); // Combine the formatted issue details with the formatted development information const combinedContent = formattedDevInfo ? `${formattedIssue}\n${formattedDevInfo}` : formattedIssue; return { content: combinedContent, }; } catch (error) { // Use the standardized error handler return (0, error_handler_util_js_1.handleControllerError)(error, { entityType: 'Issue', entityId: identifier, operation: 'retrieving', source: 'controllers/atlassian.issues.controller.ts@get', }); } } exports.default = { list, get };