@zhanglc77/bitbucket-mcp-server
Version:
MCP server for Bitbucket API integration - supports both Cloud and Server
252 lines • 11.9 kB
JavaScript
import { ErrorCode, McpError } from '@modelcontextprotocol/sdk/types.js';
import { isGetPullRequestDiffArgs, isApprovePullRequestArgs, isRequestChangesArgs } from '../types/guards.js';
import { DiffParser } from '../utils/diff-parser.js';
export class ReviewHandlers {
apiClient;
username;
constructor(apiClient, username) {
this.apiClient = apiClient;
this.username = username;
}
async handleGetPullRequestDiff(args) {
if (!isGetPullRequestDiffArgs(args)) {
throw new McpError(ErrorCode.InvalidParams, 'Invalid arguments for get_pull_request_diff');
}
const { workspace, repository, pull_request_id, context_lines = 3, include_patterns, exclude_patterns, file_path } = args;
try {
let apiPath;
let config = {};
if (this.apiClient.getIsServer()) {
// Bitbucket Server API
apiPath = `/rest/api/1.0/projects/${workspace}/repos/${repository}/pull-requests/${pull_request_id}/diff`;
config.params = { contextLines: context_lines };
}
else {
// Bitbucket Cloud API
apiPath = `/repositories/${workspace}/${repository}/pullrequests/${pull_request_id}/diff`;
config.params = { context: context_lines };
}
// For diff, we want the raw text response
config.headers = { 'Accept': 'text/plain' };
const rawDiff = await this.apiClient.makeRequest('get', apiPath, undefined, config);
// Check if filtering is needed
const needsFiltering = file_path || include_patterns || exclude_patterns;
if (!needsFiltering) {
// Return raw diff without filtering
return {
content: [
{
type: 'text',
text: JSON.stringify({
message: 'Pull request diff retrieved successfully',
pull_request_id,
diff: rawDiff
}, null, 2),
},
],
};
}
// Apply filtering
const diffParser = new DiffParser();
const sections = diffParser.parseDiffIntoSections(rawDiff);
const filterOptions = {
includePatterns: include_patterns,
excludePatterns: exclude_patterns,
filePath: file_path
};
const filteredResult = diffParser.filterSections(sections, filterOptions);
const filteredDiff = diffParser.reconstructDiff(filteredResult.sections);
// Build response with filtering metadata
const response = {
message: 'Pull request diff retrieved successfully',
pull_request_id,
diff: filteredDiff
};
// Add filter metadata
if (filteredResult.metadata.excludedFiles > 0 || file_path || include_patterns || exclude_patterns) {
response.filter_metadata = {
total_files: filteredResult.metadata.totalFiles,
included_files: filteredResult.metadata.includedFiles,
excluded_files: filteredResult.metadata.excludedFiles
};
if (filteredResult.metadata.excludedFileList.length > 0) {
response.filter_metadata.excluded_file_list = filteredResult.metadata.excludedFileList;
}
response.filter_metadata.filters_applied = {};
if (file_path) {
response.filter_metadata.filters_applied.file_path = file_path;
}
if (include_patterns) {
response.filter_metadata.filters_applied.include_patterns = include_patterns;
}
if (exclude_patterns) {
response.filter_metadata.filters_applied.exclude_patterns = exclude_patterns;
}
}
return {
content: [
{
type: 'text',
text: JSON.stringify(response, null, 2),
},
],
};
}
catch (error) {
return this.apiClient.handleApiError(error, `getting diff for pull request ${pull_request_id} in ${workspace}/${repository}`);
}
}
async handleApprovePullRequest(args) {
if (!isApprovePullRequestArgs(args)) {
throw new McpError(ErrorCode.InvalidParams, 'Invalid arguments for approve_pull_request');
}
const { workspace, repository, pull_request_id } = args;
try {
let apiPath;
if (this.apiClient.getIsServer()) {
// Bitbucket Server API - use participants endpoint
// Convert email format: @ to _ for the API
const username = this.username.replace('@', '_');
apiPath = `/rest/api/latest/projects/${workspace}/repos/${repository}/pull-requests/${pull_request_id}/participants/${username}`;
await this.apiClient.makeRequest('put', apiPath, { status: 'APPROVED' });
}
else {
// Bitbucket Cloud API
apiPath = `/repositories/${workspace}/${repository}/pullrequests/${pull_request_id}/approve`;
await this.apiClient.makeRequest('post', apiPath);
}
return {
content: [
{
type: 'text',
text: JSON.stringify({
message: 'Pull request approved successfully',
pull_request_id,
approved_by: this.username
}, null, 2),
},
],
};
}
catch (error) {
return this.apiClient.handleApiError(error, `approving pull request ${pull_request_id} in ${workspace}/${repository}`);
}
}
async handleUnapprovePullRequest(args) {
if (!isApprovePullRequestArgs(args)) {
throw new McpError(ErrorCode.InvalidParams, 'Invalid arguments for unapprove_pull_request');
}
const { workspace, repository, pull_request_id } = args;
try {
let apiPath;
if (this.apiClient.getIsServer()) {
// Bitbucket Server API - use participants endpoint
const username = this.username.replace('@', '_');
apiPath = `/rest/api/latest/projects/${workspace}/repos/${repository}/pull-requests/${pull_request_id}/participants/${username}`;
await this.apiClient.makeRequest('put', apiPath, { status: 'UNAPPROVED' });
}
else {
// Bitbucket Cloud API
apiPath = `/repositories/${workspace}/${repository}/pullrequests/${pull_request_id}/approve`;
await this.apiClient.makeRequest('delete', apiPath);
}
return {
content: [
{
type: 'text',
text: JSON.stringify({
message: 'Pull request approval removed successfully',
pull_request_id,
unapproved_by: this.username
}, null, 2),
},
],
};
}
catch (error) {
return this.apiClient.handleApiError(error, `removing approval from pull request ${pull_request_id} in ${workspace}/${repository}`);
}
}
async handleRequestChanges(args) {
if (!isRequestChangesArgs(args)) {
throw new McpError(ErrorCode.InvalidParams, 'Invalid arguments for request_changes');
}
const { workspace, repository, pull_request_id, comment } = args;
try {
if (this.apiClient.getIsServer()) {
// Bitbucket Server API - use needs-work status
const username = this.username.replace('@', '_');
const apiPath = `/rest/api/latest/projects/${workspace}/repos/${repository}/pull-requests/${pull_request_id}/participants/${username}`;
await this.apiClient.makeRequest('put', apiPath, { status: 'NEEDS_WORK' });
// Add comment if provided
if (comment) {
const commentPath = `/rest/api/1.0/projects/${workspace}/repos/${repository}/pull-requests/${pull_request_id}/comments`;
await this.apiClient.makeRequest('post', commentPath, { text: comment });
}
}
else {
// Bitbucket Cloud API - use request-changes status
const apiPath = `/repositories/${workspace}/${repository}/pullrequests/${pull_request_id}/request-changes`;
await this.apiClient.makeRequest('post', apiPath);
// Add comment if provided
if (comment) {
const commentPath = `/repositories/${workspace}/${repository}/pullrequests/${pull_request_id}/comments`;
await this.apiClient.makeRequest('post', commentPath, {
content: { raw: comment }
});
}
}
return {
content: [
{
type: 'text',
text: JSON.stringify({
message: 'Changes requested on pull request',
pull_request_id,
requested_by: this.username,
comment: comment || 'No comment provided'
}, null, 2),
},
],
};
}
catch (error) {
return this.apiClient.handleApiError(error, `requesting changes on pull request ${pull_request_id} in ${workspace}/${repository}`);
}
}
async handleRemoveRequestedChanges(args) {
if (!isApprovePullRequestArgs(args)) {
throw new McpError(ErrorCode.InvalidParams, 'Invalid arguments for remove_requested_changes');
}
const { workspace, repository, pull_request_id } = args;
try {
if (this.apiClient.getIsServer()) {
// Bitbucket Server API - remove needs-work status
const username = this.username.replace('@', '_');
const apiPath = `/rest/api/latest/projects/${workspace}/repos/${repository}/pull-requests/${pull_request_id}/participants/${username}`;
await this.apiClient.makeRequest('put', apiPath, { status: 'UNAPPROVED' });
}
else {
// Bitbucket Cloud API
const apiPath = `/repositories/${workspace}/${repository}/pullrequests/${pull_request_id}/request-changes`;
await this.apiClient.makeRequest('delete', apiPath);
}
return {
content: [
{
type: 'text',
text: JSON.stringify({
message: 'Change request removed from pull request',
pull_request_id,
removed_by: this.username
}, null, 2),
},
],
};
}
catch (error) {
return this.apiClient.handleApiError(error, `removing change request from pull request ${pull_request_id} in ${workspace}/${repository}`);
}
}
}
//# sourceMappingURL=review-handlers.js.map