@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
255 lines (254 loc) • 11.3 kB
JavaScript
;
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)(new Date(pr.created_on)),
Updated: (0, formatter_util_js_1.formatDate)(new Date(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));
// Add separator between pull requests except for the last one
if (index < pullRequests.length - 1) {
itemLines.push('');
itemLines.push((0, formatter_util_js_1.formatSeparator)());
}
return itemLines.join('\n');
});
lines.push(formattedList);
// Add timestamp for when this information was retrieved
lines.push('');
lines.push(`*Pull request 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
* @returns Formatted string with pull request details in markdown format
*/
function formatPullRequestDetails(pullRequest, diffstat, rawDiff) {
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: new Date(pullRequest.created_on),
Updated: new Date(pullRequest.updated_on),
};
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));
lines.push(pullRequest.summary.raw);
}
else if (pullRequest.rendered?.description?.raw) {
lines.push('');
lines.push((0, formatter_util_js_1.formatHeading)('Description', 2));
lines.push(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));
}
// 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'));
return lines.join('\n');
}
/**
* Format pull request comments for display
* @param commentsData - Raw pull request comments data from the API
* @returns Formatted string with pull request comments in markdown format
*/
function formatPullRequestComments(commentsData) {
const lines = [];
// Main heading
const prId = commentsData.pullrequest?.id || '';
lines.push((0, formatter_util_js_1.formatHeading)(`Comments on Pull Request #${prId}`, 1));
lines.push('');
if (!commentsData.values || commentsData.values.length === 0) {
lines.push('*No comments found on this pull request.*');
return lines.join('\n');
}
// Group comments by parent (to handle threads)
const topLevelComments = [];
const childComments = {};
// First pass: organize comments by parent
commentsData.values.forEach((comment) => {
if (comment.parent) {
// This is a reply to another comment
const parentId = comment.parent.id;
if (!childComments[parentId]) {
childComments[parentId] = [];
}
childComments[parentId].push(comment);
}
else {
// This is a top-level comment
topLevelComments.push(comment);
}
});
// Format each top-level comment and its replies
topLevelComments.forEach((comment) => {
formatComment(comment, lines);
// 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)(new Date(reply.created_on))})`);
lines.push(`> ${reply.content.raw.replace(/\n/g, '\n> ')}`);
});
}
lines.push('');
lines.push((0, formatter_util_js_1.formatSeparator)());
});
// Add timestamp for when this information was retrieved
lines.push('');
lines.push(`*Comment information retrieved at ${(0, formatter_util_js_1.formatDate)(new Date())}*`);
return lines.join('\n');
}
/**
* Helper function to format a single comment
* @param comment - The comment to format
* @param lines - Array of string lines to append to
*/
function formatComment(comment, lines) {
lines.push((0, formatter_util_js_1.formatHeading)(`Comment by ${comment.user.display_name || 'Unknown User'}`, 3));
lines.push(`*Posted on ${(0, formatter_util_js_1.formatDate)(new Date(comment.created_on))}*`);
if (comment.updated_on && comment.updated_on !== comment.created_on) {
lines.push(`*Updated on ${(0, formatter_util_js_1.formatDate)(new Date(comment.updated_on))}*`);
}
// If it's an inline comment, show file and line information
if (comment.inline) {
const fileInfo = `File: \`${comment.inline.path}\``;
let lineInfo = '';
if (comment.inline.from !== undefined &&
comment.inline.to !== undefined) {
lineInfo = `(changed from line ${comment.inline.from} to line ${comment.inline.to})`;
}
else if (comment.inline.to !== undefined) {
lineInfo = `(line ${comment.inline.to})`;
}
lines.push(`**${fileInfo}** ${lineInfo}`);
}
lines.push('');
lines.push(comment.content.raw || 'No content');
// Add link to view in browser if available
if (comment.links?.html?.href) {
lines.push('');
lines.push(`[View comment in browser](${comment.links.html.href})`);
}
}