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

347 lines (346 loc) 16.4 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.formatPullRequestsList = formatPullRequestsList; exports.formatPullRequestDetails = formatPullRequestDetails; exports.formatPullRequestComments = formatPullRequestComments; const formatter_util_js_1 = require("../utils/formatter.util.js"); /** * Format a list of pull requests for display * @param pullRequestsData - Raw pull requests data from the API * @returns Formatted string with pull requests information in markdown format */ function formatPullRequestsList(pullRequestsData) { const pullRequests = pullRequestsData.values || []; if (pullRequests.length === 0) { return 'No pull requests found matching your criteria.'; } const lines = [(0, formatter_util_js_1.formatHeading)('Bitbucket Pull Requests', 1), '']; // Format each pull request with its details const formattedList = (0, formatter_util_js_1.formatNumberedList)(pullRequests, (pr, _index) => { const itemLines = []; itemLines.push((0, formatter_util_js_1.formatHeading)(`#${pr.id}: ${pr.title}`, 2)); // Prepare the description (truncated if too long) let description = 'No description provided'; if (pr.summary?.raw && pr.summary.raw.trim() !== '') { description = pr.summary.raw; } else if (pr.summary?.markup && pr.summary.markup.trim() !== '' && pr.summary.markup !== 'markdown') { description = pr.summary.markup; } if (description.length > 150) { description = description.substring(0, 150) + '...'; } // Basic information const properties = { ID: pr.id, State: pr.state, Author: pr.author?.display_name || pr.author?.nickname || 'Unknown', Created: (0, formatter_util_js_1.formatDate)(pr.created_on), Updated: (0, formatter_util_js_1.formatDate)(pr.updated_on), 'Source Branch': pr.source?.branch?.name || 'Unknown', 'Destination Branch': pr.destination?.branch?.name || 'Unknown', Description: description, URL: pr.links?.html?.href ? (0, formatter_util_js_1.formatUrl)(pr.links.html.href, `PR #${pr.id}`) : 'N/A', }; // Format as a bullet list itemLines.push((0, formatter_util_js_1.formatBulletList)(properties, (key) => key)); return itemLines.join('\n'); }); lines.push(formattedList); // Add standard footer with timestamp lines.push('\n\n' + (0, formatter_util_js_1.formatSeparator)()); lines.push(`*Information retrieved at: ${(0, formatter_util_js_1.formatDate)(new Date())}*`); return lines.join('\n'); } /** * Format detailed pull request information for display * @param pullRequest - Raw pull request data from the API * @param diffstat - Optional diffstat data from the API * @param rawDiff - Optional raw diff content from the API * @param comments - Optional comments data from the API * @returns Formatted string with pull request details in markdown format */ function formatPullRequestDetails(pullRequest, diffstat, rawDiff, comments) { const lines = [ (0, formatter_util_js_1.formatHeading)(`Pull Request #${pullRequest.id}: ${pullRequest.title}`, 1), '', (0, formatter_util_js_1.formatHeading)('Basic Information', 2), ]; // Format basic information as a bullet list const basicProperties = { State: pullRequest.state, Repository: pullRequest.destination.repository.full_name, Source: pullRequest.source.branch.name, Destination: pullRequest.destination.branch.name, Author: pullRequest.author?.display_name, Created: (0, formatter_util_js_1.formatDate)(pullRequest.created_on), Updated: (0, formatter_util_js_1.formatDate)(pullRequest.updated_on), 'Comment Count': pullRequest.comment_count ?? 0, 'Task Count': pullRequest.task_count ?? 0, }; lines.push((0, formatter_util_js_1.formatBulletList)(basicProperties, (key) => key)); // Reviewers if (pullRequest.reviewers && pullRequest.reviewers.length > 0) { lines.push(''); lines.push((0, formatter_util_js_1.formatHeading)('Reviewers', 2)); const reviewerLines = []; pullRequest.reviewers.forEach((reviewer) => { reviewerLines.push(`- ${reviewer.display_name}`); }); lines.push(reviewerLines.join('\n')); } // Summary or rendered content for description if available if (pullRequest.summary?.raw) { lines.push(''); lines.push((0, formatter_util_js_1.formatHeading)('Description', 2)); // Optimize the markdown content for better rendering lines.push((0, formatter_util_js_1.optimizeBitbucketMarkdown)(pullRequest.summary.raw)); } else if (pullRequest.rendered?.description?.raw) { lines.push(''); lines.push((0, formatter_util_js_1.formatHeading)('Description', 2)); // Optimize the markdown content for better rendering lines.push((0, formatter_util_js_1.optimizeBitbucketMarkdown)(pullRequest.rendered.description.raw)); } // File Changes Summary from Diffstat if (diffstat && diffstat.values && diffstat.values.length > 0) { lines.push(''); lines.push((0, formatter_util_js_1.formatHeading)('File Changes', 2)); // Calculate summary statistics const totalFiles = diffstat.values.length; let totalAdditions = 0; let totalDeletions = 0; diffstat.values.forEach((file) => { if (file.lines_added) totalAdditions += file.lines_added; if (file.lines_removed) totalDeletions += file.lines_removed; }); // Add summary line lines.push(`${totalFiles} file${totalFiles !== 1 ? 's' : ''} changed with ${totalAdditions} insertion${totalAdditions !== 1 ? 's' : ''} and ${totalDeletions} deletion${totalDeletions !== 1 ? 's' : ''}`); // Add file list (limited to 10 files for brevity) const maxFilesToShow = 10; if (totalFiles > 0) { lines.push(''); diffstat.values.slice(0, maxFilesToShow).forEach((file) => { const changes = []; if (file.lines_added) changes.push(`+${file.lines_added}`); if (file.lines_removed) changes.push(`-${file.lines_removed}`); const changeStr = changes.length > 0 ? ` (${changes.join(', ')})` : ''; lines.push(`- \`${file.old?.path || file.new?.path}\`${changeStr}`); }); if (totalFiles > maxFilesToShow) { lines.push(`- ... and ${totalFiles - maxFilesToShow} more files`); } } } // Detailed Diff Content if (rawDiff) { lines.push(''); lines.push((0, formatter_util_js_1.formatHeading)('Code Changes (Full Diff)', 2)); lines.push((0, formatter_util_js_1.formatDiff)(rawDiff)); } // Comments Section (when included) if (comments && comments.length > 0) { lines.push(''); lines.push((0, formatter_util_js_1.formatHeading)('Comments', 2)); // Group comments by parent (to handle threads) const topLevelComments = []; const childComments = {}; // First pass: organize comments by parent comments.forEach((comment) => { if (comment.parent) { const parentId = comment.parent.id; if (!childComments[parentId]) { childComments[parentId] = []; } childComments[parentId].push(comment); } else { topLevelComments.push(comment); } }); // Format each top-level comment and its replies (limit to 5 comments for conciseness) const maxCommentsToShow = 5; const commentsToShow = topLevelComments.slice(0, maxCommentsToShow); commentsToShow.forEach((comment, index) => { formatComment(comment, lines); // Add replies if any exist (limit to 3 replies per comment for conciseness) const replies = childComments[comment.id] || []; if (replies.length > 0) { lines.push(''); lines.push('**Replies:**'); const maxRepliesToShow = 3; const repliesToShow = replies.slice(0, maxRepliesToShow); repliesToShow.forEach((reply) => { lines.push(''); lines.push(`> **${reply.user.display_name || 'Unknown User'}** (${(0, formatter_util_js_1.formatDate)(reply.created_on)})`); // Optimize the markdown content for replies as well const optimizedReplyContent = (0, formatter_util_js_1.optimizeBitbucketMarkdown)(reply.content.raw); lines.push(`> ${optimizedReplyContent.replace(/\n/g, '\n> ')}`); }); // Show message if more replies were omitted if (replies.length > maxRepliesToShow) { lines.push(''); lines.push(`> *...and ${replies.length - maxRepliesToShow} more replies*`); } } if (index < commentsToShow.length - 1) { lines.push(''); lines.push((0, formatter_util_js_1.formatSeparator)()); } }); // Show message if more comments were omitted if (topLevelComments.length > maxCommentsToShow) { lines.push(''); lines.push(`*...and ${topLevelComments.length - maxCommentsToShow} more comments*`); } // Add link to view all comments if available if (pullRequest.links?.comments?.href) { lines.push(''); lines.push(`[View all comments in browser](${pullRequest.links.comments.href})`); } } else if (comments && comments.length === 0) { lines.push(''); lines.push((0, formatter_util_js_1.formatHeading)('Comments', 2)); lines.push('*No comments found on this pull request.*'); } // Links lines.push(''); lines.push((0, formatter_util_js_1.formatHeading)('Links', 2)); const links = []; if (pullRequest.links.html?.href) { links.push(`- ${(0, formatter_util_js_1.formatUrl)(pullRequest.links.html.href, 'View in Browser')}`); } if (pullRequest.links.commits?.href) { links.push(`- ${(0, formatter_util_js_1.formatUrl)(pullRequest.links.commits.href, 'Commits')}`); } if (pullRequest.links.comments?.href) { links.push(`- ${(0, formatter_util_js_1.formatUrl)(pullRequest.links.comments.href, 'Comments')}`); } if (pullRequest.links.diff?.href) { links.push(`- ${(0, formatter_util_js_1.formatUrl)(pullRequest.links.diff.href, 'Diff')}`); } lines.push(links.join('\n')); // Add standard footer with timestamp lines.push('\n\n' + (0, formatter_util_js_1.formatSeparator)()); lines.push(`*Information retrieved at: ${(0, formatter_util_js_1.formatDate)(new Date())}*`); return lines.join('\n'); } /** * Format pull request comments for display, including code snippets for inline comments. * @param comments - Array of comment objects, potentially enhanced with code snippets. * @param prId - The ID of the pull request to include in the title. * @returns Formatted string with pull request comments in markdown format. */ function formatPullRequestComments(comments, // Accept the array of enhanced comments directly prId) { const lines = []; lines.push((0, formatter_util_js_1.formatHeading)(`Comments on Pull Request #${prId}`, 1)); lines.push(''); if (!comments || comments.length === 0) { lines.push('*No comments found on this pull request.*'); lines.push('\n\n' + (0, formatter_util_js_1.formatSeparator)()); lines.push(`*Information retrieved at: ${(0, formatter_util_js_1.formatDate)(new Date())}*`); return lines.join('\n'); } // Group comments by parent (to handle threads) const topLevelComments = []; const childComments = {}; // First pass: organize comments by parent comments.forEach((comment) => { if (comment.parent) { const parentId = comment.parent.id; if (!childComments[parentId]) { childComments[parentId] = []; } childComments[parentId].push(comment); } else { topLevelComments.push(comment); } }); // Format each top-level comment and its replies topLevelComments.forEach((comment, index) => { formatComment(comment, lines); // Pass the enhanced comment object // Add replies if any exist const replies = childComments[comment.id] || []; if (replies.length > 0) { lines.push(''); lines.push('**Replies:**'); replies.forEach((reply) => { lines.push(''); lines.push(`> **${reply.user.display_name || 'Unknown User'}** (${(0, formatter_util_js_1.formatDate)(reply.created_on)})`); // Optimize the markdown content for replies as well const optimizedReplyContent = (0, formatter_util_js_1.optimizeBitbucketMarkdown)(reply.content.raw); lines.push(`> ${optimizedReplyContent.replace(/\n/g, '\n> ')}`); }); } if (index < topLevelComments.length - 1) { lines.push(''); lines.push((0, formatter_util_js_1.formatSeparator)()); } }); lines.push('\n\n' + (0, formatter_util_js_1.formatSeparator)()); lines.push(`*Information retrieved at: ${(0, formatter_util_js_1.formatDate)(new Date())}*`); return lines.join('\n'); } /** * Helper function to format a single comment, including code snippet if available. * @param comment - The comment object (potentially with codeSnippet). * @param lines - Array of string lines to append to. */ function formatComment(comment, // Use the enhanced type lines) { const author = comment.user.display_name || 'Unknown User'; const headerText = comment.deleted ? `[DELETED] Comment by ${author}` : `Comment by ${author}`; lines.push((0, formatter_util_js_1.formatHeading)(headerText, 3)); lines.push(`*Posted on ${(0, formatter_util_js_1.formatDate)(comment.created_on)}*`); if (comment.updated_on && comment.updated_on !== comment.created_on) { lines.push(`*Updated on ${(0, formatter_util_js_1.formatDate)(comment.updated_on)}*`); } // If it's an inline comment, show file, line info, and snippet if (comment.inline) { const fileInfo = `File: \`${comment.inline.path}\``; let lineInfo = ''; if (comment.inline.from !== undefined && comment.inline.to !== undefined) { lineInfo = `(changed line ${comment.inline.from} -> ${comment.inline.to})`; // Slightly clearer wording } else if (comment.inline.to !== undefined) { lineInfo = `(line ${comment.inline.to})`; } lines.push(`**Inline Comment: ${fileInfo}** ${lineInfo}`); // Add the code snippet if it exists if (comment.codeSnippet) { lines.push(''); lines.push('```diff'); // Use diff language for syntax highlighting lines.push(comment.codeSnippet); lines.push('```'); } else if (comment.links?.code?.href) { // Fallback link if snippet fetch failed or wasn't applicable lines.push(`[View code context in browser](${comment.links.code.href})`); } } lines.push(''); // Show specific message for deleted comments, otherwise show optimized raw content lines.push(comment.deleted ? '*This comment has been deleted.*' : (0, formatter_util_js_1.optimizeBitbucketMarkdown)(comment.content.raw) || '*No content provided.*'); // Add link to view the comment itself in browser if available if (comment.links?.html?.href) { lines.push(''); lines.push(`[View full comment thread in browser](${comment.links.html.href})`); // Clarify link purpose } }