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

374 lines (373 loc) 17.6 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.formatIssuesList = formatIssuesList; exports.formatIssueDetails = formatIssueDetails; exports.formatDevelopmentInfo = formatDevelopmentInfo; const adf_util_js_1 = require("../utils/adf.util.js"); const formatter_util_js_1 = require("../utils/formatter.util.js"); /** * Format a list of issues for display * @param issuesData - Raw issues data from the API * @param _pagination - Pagination information including count and next cursor (handled by CLI layer) * @returns Formatted string with issues information in markdown format */ function formatIssuesList(issuesData, _pagination) { const { issues } = issuesData; if (!issues || issues.length === 0) { return 'No issues found.'; } const lines = [(0, formatter_util_js_1.formatHeading)('Jira Issues', 1), '']; const formattedIssues = (0, formatter_util_js_1.formatNumberedList)(issues, (issue) => { const lines = []; lines.push((0, formatter_util_js_1.formatHeading)(`${issue.key}: ${issue.fields.summary}`, 2)); const properties = { Key: issue.key, Summary: issue.fields.summary, Type: issue.fields.issuetype?.name, Status: issue.fields.status?.name, Priority: issue.fields.priority?.name, Project: issue.fields.project?.name, Assignee: issue.fields.assignee?.displayName, Reporter: issue.fields.reporter?.displayName, 'Created On': issue.fields.created, 'Updated On': issue.fields.updated, URL: { url: `${issuesData.baseUrl}/browse/${issue.key}`, title: issue.key, }, }; lines.push((0, formatter_util_js_1.formatBulletList)(properties)); return lines.join('\n'); }); lines.push(formattedIssues); return lines.join('\n'); } /** * Format detailed issue information for display * @param issueData - Raw issue data from the API * @returns Formatted string with issue details in markdown format */ function formatIssueDetails(issueData) { // Prepare URL const issueUrl = issueData.self.replace('/rest/api/3/issue/', '/browse/'); const lines = [ (0, formatter_util_js_1.formatHeading)(`Jira Issue: ${issueData.fields.summary}`, 1), '', ]; // Add a brief summary line if (issueData.fields.status) { const summary = `> A ${issueData.fields.status.name.toLowerCase()} issue in the ${issueData.fields.project?.name} project.`; lines.push(summary); lines.push(''); } // Basic Information section lines.push((0, formatter_util_js_1.formatHeading)('Basic Information', 2)); const basicProperties = { ID: issueData.id, Key: issueData.key, Project: issueData.fields.project ? `${issueData.fields.project.name} (${issueData.fields.project.key})` : undefined, Type: issueData.fields.issuetype?.name, Status: issueData.fields.status?.name, Priority: issueData.fields.priority?.name, }; lines.push((0, formatter_util_js_1.formatBulletList)(basicProperties, (key) => key)); // Add issue type description if available if (issueData.fields.issuetype?.description) { lines.push(` *${issueData.fields.issuetype.description}*`); } // Description if (issueData.fields.description) { lines.push(''); lines.push((0, formatter_util_js_1.formatHeading)('Description', 2)); // Handle different description formats if (typeof issueData.fields.description === 'string') { lines.push(issueData.fields.description); } else if (typeof issueData.fields.description === 'object') { lines.push((0, adf_util_js_1.adfToMarkdown)(issueData.fields.description)); } else { lines.push('*Description format not supported*'); } } // People lines.push(''); lines.push((0, formatter_util_js_1.formatHeading)('People', 2)); const peopleProperties = { Assignee: issueData.fields.assignee?.displayName || 'Unassigned', Reporter: issueData.fields.reporter?.displayName, }; // Add creator only if different from reporter if (issueData.fields.creator && (!issueData.fields.reporter || issueData.fields.creator.displayName !== issueData.fields.reporter?.displayName)) { peopleProperties['Creator'] = issueData.fields.creator.displayName; } lines.push((0, formatter_util_js_1.formatBulletList)(peopleProperties, (key) => key)); // Additional people details if (issueData.fields.assignee && issueData.fields.assignee.active !== undefined) { lines.push(` - **Active**: ${issueData.fields.assignee.active ? 'Yes' : 'No'}`); } if (issueData.fields.reporter && issueData.fields.reporter.active !== undefined) { lines.push(` - **Active**: ${issueData.fields.reporter.active ? 'Yes' : 'No'}`); } if (issueData.fields.creator && (!issueData.fields.reporter || issueData.fields.creator.displayName !== issueData.fields.reporter?.displayName) && issueData.fields.creator.active !== undefined) { lines.push(` - **Active**: ${issueData.fields.creator.active ? 'Yes' : 'No'}`); } // Dates lines.push(''); lines.push((0, formatter_util_js_1.formatHeading)('Dates', 2)); const dateProperties = { Created: issueData.fields.created, Updated: issueData.fields.updated, }; lines.push((0, formatter_util_js_1.formatBulletList)(dateProperties, (key) => key)); // Time tracking if (issueData.fields.timetracking && (issueData.fields.timetracking.originalEstimate || issueData.fields.timetracking.remainingEstimate || issueData.fields.timetracking.timeSpent)) { lines.push(''); lines.push((0, formatter_util_js_1.formatHeading)('Time Tracking', 2)); const timeTrackingProperties = { 'Original Estimate': issueData.fields.timetracking.originalEstimate, 'Remaining Estimate': issueData.fields.timetracking.remainingEstimate, 'Time Spent': issueData.fields.timetracking.timeSpent, }; lines.push((0, formatter_util_js_1.formatBulletList)(timeTrackingProperties, (key) => key)); } // Attachments if (issueData.fields.attachment && issueData.fields.attachment.length > 0) { lines.push(''); lines.push((0, formatter_util_js_1.formatHeading)('Attachments', 2)); issueData.fields.attachment.forEach((attachment, index) => { lines.push((0, formatter_util_js_1.formatHeading)(attachment.filename, 3)); const attachmentProperties = { 'Content Type': attachment.mimeType, Size: formatFileSize(attachment.size), 'Created At': attachment.created, Author: attachment.author?.displayName, }; lines.push((0, formatter_util_js_1.formatBulletList)(attachmentProperties, (key) => key)); if (attachment.content) { lines.push(`[Download](${attachment.content})`); } // Add separator between attachments if (index < issueData.fields.attachment.length - 1) { lines.push(''); } }); } // Comments if (issueData.fields.comment) { const comments = Array.isArray(issueData.fields.comment) ? issueData.fields.comment : issueData.fields.comment.comments || []; if (comments.length > 0) { lines.push(''); lines.push((0, formatter_util_js_1.formatHeading)('Comments', 2)); comments.forEach((comment, index) => { lines.push((0, formatter_util_js_1.formatHeading)(`${comment.author?.displayName || 'Anonymous'} - ${(0, formatter_util_js_1.formatDate)(comment.created)}`, 3)); // Format comment body if (typeof comment.body === 'string') { lines.push(comment.body); } else if (typeof comment.body === 'object') { lines.push((0, adf_util_js_1.adfToMarkdown)(comment.body)); } // Add separator between comments if (index < comments.length - 1) { lines.push(''); lines.push((0, formatter_util_js_1.formatSeparator)()); lines.push(''); } }); } } // Issue Links if (issueData.fields.issuelinks && issueData.fields.issuelinks.length > 0) { lines.push(''); lines.push((0, formatter_util_js_1.formatHeading)('Issue Links', 2)); const issueLinks = issueData.fields.issuelinks; issueLinks.forEach((link) => { const relatedIssue = link.outwardIssue || link.inwardIssue; if (relatedIssue) { const linkType = link.outwardIssue ? link.type.outward : link.type.inward; const relatedIssueUrl = relatedIssue.self.replace('/rest/api/3/issue/', '/browse/'); lines.push(`- ${linkType} ${(0, formatter_util_js_1.formatUrl)(relatedIssueUrl, relatedIssue.key)}: ${relatedIssue.fields?.summary || 'No summary'}`); } }); } // Links section lines.push(''); lines.push((0, formatter_util_js_1.formatHeading)('Links', 2)); lines.push(`- ${(0, formatter_util_js_1.formatUrl)(issueUrl, 'Open in Jira')}`); // Footer lines.push(''); lines.push((0, formatter_util_js_1.formatSeparator)()); lines.push(`*Issue information retrieved at ${(0, formatter_util_js_1.formatDate)(new Date())}*`); lines.push(`*To view this issue in Jira, visit: ${issueUrl}*`); return lines.join('\n'); } /** * Format file size in human-readable format * @param bytes - File size in bytes * @returns Formatted file size string */ function formatFileSize(bytes) { if (bytes === 0) return '0 Bytes'; const k = 1024; const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; } /** * Format development information for display * @param devInfoSummary - Development information summary * @param devInfoCommits - Development information commits * @param devInfoBranches - Development information branches * @param devInfoPullRequests - Development information pull requests * @returns Formatted string with development information in markdown format */ function formatDevelopmentInfo(devInfoSummary, devInfoCommits, devInfoBranches, devInfoPullRequests) { const lines = []; // Check if there's any development info available if (!devInfoSummary || !devInfoSummary.summary || (!devInfoSummary.summary.repository?.overall?.count && !devInfoSummary.summary.branch?.overall?.count && !devInfoSummary.summary.pullrequest?.overall?.count)) { return ''; // No development info, return empty string } lines.push(''); lines.push((0, formatter_util_js_1.formatHeading)('Development Information', 2)); // Development Summary if (devInfoSummary.summary) { const summary = devInfoSummary.summary; lines.push(''); lines.push((0, formatter_util_js_1.formatHeading)('Development Summary', 3)); const summaryProps = {}; if (summary.repository?.overall?.count) { const lastUpdated = summary.repository.overall.lastUpdated || 'Unknown'; summaryProps['Repositories'] = `${summary.repository.overall.count} (Last updated: ${lastUpdated !== 'Unknown' ? (0, formatter_util_js_1.formatDate)(lastUpdated) : 'Unknown'})`; } if (summary.branch?.overall?.count) { const lastUpdated = summary.branch.overall.lastUpdated || 'Unknown'; summaryProps['Branches'] = `${summary.branch.overall.count} (Last updated: ${lastUpdated !== 'Unknown' ? (0, formatter_util_js_1.formatDate)(lastUpdated) : 'Unknown'})`; } if (summary.pullrequest?.overall?.count) { const lastUpdated = summary.pullrequest.overall.lastUpdated || 'Unknown'; summaryProps['Pull Requests'] = `${summary.pullrequest.overall.count} (Last updated: ${lastUpdated !== 'Unknown' ? (0, formatter_util_js_1.formatDate)(lastUpdated) : 'Unknown'}, Status: ${summary.pullrequest.overall.state || 'Unknown'})`; } lines.push((0, formatter_util_js_1.formatBulletList)(summaryProps)); } // Commits if (devInfoCommits?.detail && devInfoCommits.detail.length > 0) { lines.push(''); lines.push((0, formatter_util_js_1.formatHeading)('Commits', 3)); devInfoCommits.detail.forEach((detail) => { if (detail.repositories && detail.repositories.length > 0) { detail.repositories.forEach((repo) => { lines.push(`**Repository**: ${repo.name}`); if (repo.commits && repo.commits.length > 0) { lines.push(''); repo.commits.forEach((commit, index) => { lines.push(`${index + 1}. **${commit.displayId}** - ${commit.message.split('\n')[0]}`); lines.push(` Author: ${commit.author?.name || 'Unknown'}, Date: ${(0, formatter_util_js_1.formatDate)(commit.authorTimestamp)}`); if (commit.url) { lines.push(` ${(0, formatter_util_js_1.formatUrl)('View Commit', commit.url)}`); } if (index < repo.commits.length - 1) { lines.push(''); } }); } else { lines.push(' No commits found'); } }); } }); } // Branches if (devInfoBranches?.detail && devInfoBranches.detail.length > 0) { lines.push(''); lines.push((0, formatter_util_js_1.formatHeading)('Branches', 3)); devInfoBranches.detail.forEach((detail) => { if (detail.branches && detail.branches.length > 0) { detail.branches.forEach((branch) => { lines.push(`**Branch**: ${branch.name}`); lines.push(`**Repository**: ${branch.repository?.name || 'Unknown'}`); if (branch.lastCommit) { lines.push(`**Last Commit**: ${branch.lastCommit.displayId} - ${branch.lastCommit.message.split('\n')[0]}`); lines.push(`**Author**: ${branch.lastCommit.author?.name || 'Unknown'}, **Date**: ${(0, formatter_util_js_1.formatDate)(branch.lastCommit.authorTimestamp)}`); } if (branch.url) { lines.push(`${(0, formatter_util_js_1.formatUrl)('View Branch', branch.url)}`); } lines.push(''); }); } else { lines.push('No branches found'); } }); } // Pull Requests if (devInfoPullRequests?.detail && devInfoPullRequests.detail.length > 0) { lines.push(''); lines.push((0, formatter_util_js_1.formatHeading)('Pull Requests', 3)); devInfoPullRequests.detail.forEach((detail) => { if (detail.pullRequests && detail.pullRequests.length > 0) { detail.pullRequests.forEach((pr) => { lines.push(`**${pr.name}** (${pr.status})`); lines.push(`**Repository**: ${pr.repositoryName}`); lines.push(`**Author**: ${pr.author?.name || 'Unknown'}`); if (pr.source?.branch && pr.destination?.branch) { lines.push(`**Source**: ${pr.source.branch} → **Destination**: ${pr.destination.branch}`); } if (pr.reviewers && pr.reviewers.length > 0) { const approved = pr.reviewers .filter((r) => r.approved) .map((r) => r.name) .join(', '); const notApproved = pr.reviewers .filter((r) => !r.approved) .map((r) => r.name) .join(', '); if (approved) { lines.push(`**Approved by**: ${approved}`); } if (notApproved) { lines.push(`**Awaiting approval from**: ${notApproved}`); } } lines.push(`**Last Updated**: ${(0, formatter_util_js_1.formatDate)(pr.lastUpdate)}`); if (pr.url) { lines.push(`${(0, formatter_util_js_1.formatUrl)('View Pull Request', pr.url)}`); } lines.push(''); }); } else { lines.push('No pull requests found'); } }); } return lines.join('\n'); }