@gohcltech/bitbucket-mcp
Version:
Bitbucket integration for Claude via Model Context Protocol
461 lines • 18.3 kB
JavaScript
/**
* @fileoverview Consolidated pull request management tool for v2.1.
*
* Replaces v2.0 tools: list_pull_requests, get_pull_request, create_pull_request,
* approve_pull_request, unapprove_pull_request, merge_pull_request, decline_pull_request
* Consolidates 7 tools into 1 with action-based routing.
*/
import { ConsolidatedBaseTool } from './consolidated-base-tool.js';
import { ToolCategory } from '../auth-capabilities.js';
import { ManagePullRequestsArgsSchema, } from '../types/consolidated-tools.js';
import { validateWithSchemaOrThrow, validateRequired } from '../utils/index.js';
/**
* Consolidated pull request management tool for v2.1.
*
* Provides unified pull request operations with action-based routing.
*
* **Replaces v2.0 tools:**
* - list_pull_requests
* - get_pull_request
* - create_pull_request
* - approve_pull_request
* - unapprove_pull_request
* - merge_pull_request
* - decline_pull_request
*
* **Actions:**
* - `list`: List pull requests with optional state filtering
* - `get`: Get detailed pull request information
* - `create`: Create a new pull request
* - `approve`: Approve a pull request
* - `unapprove`: Remove approval from a pull request
* - `merge`: Merge an approved pull request
* - `decline`: Decline a pull request
*
* @example
* ```typescript
* // List open pull requests
* const response = await tool.execute({
* action: 'list',
* workspaceSlug: 'my-workspace',
* repoSlug: 'my-repo',
* state: 'OPEN'
* });
*
* // Create a new pull request
* const response = await tool.execute({
* action: 'create',
* workspaceSlug: 'my-workspace',
* repoSlug: 'my-repo',
* title: 'Add feature X',
* sourceBranch: 'feature/x',
* destinationBranch: 'develop'
* });
*
* // Approve and merge a PR
* await tool.execute({
* action: 'approve',
* workspaceSlug: 'my-workspace',
* repoSlug: 'my-repo',
* pullRequestId: 42
* });
*
* await tool.execute({
* action: 'merge',
* workspaceSlug: 'my-workspace',
* repoSlug: 'my-repo',
* pullRequestId: 42
* });
* ```
*/
export class ManagePullRequestsTool extends ConsolidatedBaseTool {
getTools() {
return [
{
name: 'manage_pull_requests',
description: 'Manage pull requests: list, get, create, update, approve, unapprove, merge, decline, or comment',
inputSchema: {
type: 'object',
properties: {
action: {
type: 'string',
enum: ['list', 'get', 'create', 'update', 'approve', 'unapprove', 'merge', 'decline', 'list_comments', 'add_comment'],
description: 'Action to perform: list, get, create, update, approve, unapprove, merge, decline, list_comments, or add_comment',
},
workspaceSlug: {
type: 'string',
description: 'Workspace slug (required)',
},
repoSlug: {
type: 'string',
description: 'Repository slug (required)',
},
state: {
type: 'string',
enum: ['OPEN', 'MERGED', 'DECLINED', 'SUPERSEDED'],
description: 'PR state filter (optional for list)',
},
pullRequestId: {
type: 'number',
description: 'Pull request ID (required for get/update/approve/unapprove/merge/decline)',
},
title: {
type: 'string',
description: 'PR title (required for create)',
},
sourceBranch: {
type: 'string',
description: 'Source branch (required for create)',
},
destinationBranch: {
type: 'string',
description: 'Destination branch (optional for create, defaults to main)',
},
description: {
type: 'string',
description: 'PR description (optional for create)',
},
closeSourceBranch: {
type: 'boolean',
description: 'Auto-close source branch on merge (optional for create)',
},
content: {
type: 'string',
description: 'Comment content (required for add_comment)',
},
},
required: ['action', 'workspaceSlug', 'repoSlug'],
},
},
];
}
getToolCategory() {
return ToolCategory.PULL_REQUEST;
}
/**
* Gets the handler function for a specific action.
*
* Maps action names to their corresponding handler functions.
* This is used internally by handleAction() for action-based routing.
*
* @param action - The action to get a handler for
* @returns The handler function for this action, or null if action is unknown
*/
getActionHandler(action) {
const actionHandlers = {
'list': this.handleListPullRequests.bind(this),
'get': this.handleGetPullRequest.bind(this),
'create': this.handleCreatePullRequest.bind(this),
'update': this.handleUpdatePullRequest.bind(this),
'approve': this.handleApprovePullRequest.bind(this),
'unapprove': this.handleUnapprovePullRequest.bind(this),
'merge': this.handleMergePullRequest.bind(this),
'decline': this.handleDeclinePullRequest.bind(this),
'list_comments': this.handleListPRComments.bind(this),
'add_comment': this.handleAddPRComment.bind(this),
};
return actionHandlers[action] || null;
}
validateActionParams(action, args) {
// All actions require workspace and repo
validateRequired(args, ['workspaceSlug', 'repoSlug'], `manage_pull_requests.${action}`);
switch (action) {
case 'list':
case 'list_comments':
// Only requires workspace and repo (and pullRequestId for list_comments)
if (action === 'list_comments') {
validateRequired(args, ['pullRequestId'], `manage_pull_requests.${action}`);
}
break;
case 'get':
case 'approve':
case 'unapprove':
case 'merge':
case 'decline':
validateRequired(args, ['pullRequestId'], `manage_pull_requests.${action}`);
break;
case 'create':
validateRequired(args, ['title', 'sourceBranch'], `manage_pull_requests.${action}`);
break;
case 'update':
validateRequired(args, ['pullRequestId'], `manage_pull_requests.${action}`);
// At least title or description must be provided
if (!args.title && !args.description) {
throw new Error('At least one of title or description must be provided for update action');
}
break;
case 'add_comment':
validateRequired(args, ['pullRequestId', 'content'], `manage_pull_requests.${action}`);
break;
}
}
/**
* Handler for listing pull requests in a repository.
*
* @param args - Tool arguments (workspace, repo, optional state filter)
* @returns List of pull requests with metadata
*/
async handleListPullRequests(args) {
return this.createToolResponse(async () => {
// Validate arguments
validateWithSchemaOrThrow(ManagePullRequestsArgsSchema, args, 'manage_pull_requests.list');
const typedArgs = args;
// Build query parameters from optional filters
const params = {};
if (typedArgs.state) {
params.state = typedArgs.state;
}
// Fetch pull requests
const result = await this.client.getPullRequests(typedArgs.workspaceSlug || '', typedArgs.repoSlug || '', params);
return {
pullRequests: result.values,
count: result.values.length,
page: result.page || 1,
pageLen: result.pagelen || result.values.length,
nextUrl: result.next,
};
}, {
toolName: 'manage_pull_requests',
operationName: 'list_pull_requests',
args,
});
}
/**
* Handler for getting detailed information about a pull request.
*
* @param args - Tool arguments (must include pullRequestId)
* @returns Detailed PR information
*/
async handleGetPullRequest(args) {
return this.createToolResponse(async () => {
// Validate arguments
validateWithSchemaOrThrow(ManagePullRequestsArgsSchema, args, 'manage_pull_requests.get');
const typedArgs = args;
// Fetch PR details
const pr = await this.client.getPullRequest(typedArgs.workspaceSlug, typedArgs.repoSlug, typedArgs.pullRequestId);
return pr;
}, {
toolName: 'manage_pull_requests',
operationName: 'get_pull_request',
args,
});
}
/**
* Handler for creating a new pull request.
*
* @param args - Tool arguments (must include title and sourceBranch)
* @returns Created PR information
*/
async handleCreatePullRequest(args) {
return this.createToolResponse(async () => {
// Validate arguments
validateWithSchemaOrThrow(ManagePullRequestsArgsSchema, args, 'manage_pull_requests.create');
const typedArgs = args;
// Build PR creation payload
const payload = {
title: typedArgs.title,
source: {
branch: {
name: typedArgs.sourceBranch,
},
},
};
if (typedArgs.destinationBranch) {
payload.destination = {
branch: {
name: typedArgs.destinationBranch,
},
};
}
if (typedArgs.description) {
payload.description = typedArgs.description;
}
if (typedArgs.closeSourceBranch !== undefined) {
payload.close_source_on_merge = typedArgs.closeSourceBranch;
}
// Create PR
const pr = await this.client.createPullRequest(typedArgs.workspaceSlug, typedArgs.repoSlug, payload);
return pr;
}, {
toolName: 'manage_pull_requests',
operationName: 'create_pull_request',
args,
});
}
/**
* Handler for updating a pull request.
*
* @param args - Tool arguments (must include pullRequestId, and title or description)
* @returns Updated PR information
*/
async handleUpdatePullRequest(args) {
return this.createToolResponse(async () => {
// Validate arguments
validateWithSchemaOrThrow(ManagePullRequestsArgsSchema, args, 'manage_pull_requests.update');
const typedArgs = args;
// Build update payload with provided fields
const payload = {};
if (typedArgs.title) {
payload.title = typedArgs.title;
}
if (typedArgs.description) {
payload.description = typedArgs.description;
}
// Update PR
const pr = await this.client.updatePullRequest(typedArgs.workspaceSlug, typedArgs.repoSlug, typedArgs.pullRequestId, payload);
return pr;
}, {
toolName: 'manage_pull_requests',
operationName: 'update_pull_request',
args,
});
}
/**
* Handler for approving a pull request.
*
* @param args - Tool arguments (must include pullRequestId)
* @returns Approval confirmation
*/
async handleApprovePullRequest(args) {
return this.createToolResponse(async () => {
// Validate arguments
validateWithSchemaOrThrow(ManagePullRequestsArgsSchema, args, 'manage_pull_requests.approve');
const typedArgs = args;
// Approve PR
await this.client.approvePullRequest(typedArgs.workspaceSlug, typedArgs.repoSlug, typedArgs.pullRequestId);
return {
status: 'approved',
pullRequestId: typedArgs.pullRequestId,
message: `Pull request ${typedArgs.pullRequestId} approved successfully`,
};
}, {
toolName: 'manage_pull_requests',
operationName: 'approve_pull_request',
args,
});
}
/**
* Handler for removing approval from a pull request.
*
* @param args - Tool arguments (must include pullRequestId)
* @returns Unapproval confirmation
*/
async handleUnapprovePullRequest(args) {
return this.createToolResponse(async () => {
// Validate arguments
validateWithSchemaOrThrow(ManagePullRequestsArgsSchema, args, 'manage_pull_requests.unapprove');
const typedArgs = args;
// Unapprove PR
await this.client.unapprovePullRequest(typedArgs.workspaceSlug, typedArgs.repoSlug, typedArgs.pullRequestId);
return {
status: 'unapproved',
pullRequestId: typedArgs.pullRequestId,
message: `Approval removed from pull request ${typedArgs.pullRequestId}`,
};
}, {
toolName: 'manage_pull_requests',
operationName: 'unapprove_pull_request',
args,
});
}
/**
* Handler for merging a pull request.
*
* @param args - Tool arguments (must include pullRequestId)
* @returns Merge confirmation
*/
async handleMergePullRequest(args) {
return this.createToolResponse(async () => {
// Validate arguments
validateWithSchemaOrThrow(ManagePullRequestsArgsSchema, args, 'manage_pull_requests.merge');
const typedArgs = args;
// Merge PR
await this.client.mergePullRequest(typedArgs.workspaceSlug, typedArgs.repoSlug, typedArgs.pullRequestId);
return {
status: 'merged',
pullRequestId: typedArgs.pullRequestId,
message: `Pull request ${typedArgs.pullRequestId} merged successfully`,
};
}, {
toolName: 'manage_pull_requests',
operationName: 'merge_pull_request',
args,
});
}
/**
* Handler for declining a pull request.
*
* @param args - Tool arguments (must include pullRequestId)
* @returns Decline confirmation
*/
async handleDeclinePullRequest(args) {
return this.createToolResponse(async () => {
// Validate arguments
validateWithSchemaOrThrow(ManagePullRequestsArgsSchema, args, 'manage_pull_requests.decline');
const typedArgs = args;
// Decline PR
await this.client.declinePullRequest(typedArgs.workspaceSlug, typedArgs.repoSlug, typedArgs.pullRequestId);
return {
status: 'declined',
pullRequestId: typedArgs.pullRequestId,
message: `Pull request ${typedArgs.pullRequestId} declined`,
};
}, {
toolName: 'manage_pull_requests',
operationName: 'decline_pull_request',
args,
});
}
/**
* Handler for listing comments on a pull request.
*
* @param args - Tool arguments (must include pullRequestId)
* @returns List of comments on the PR
*/
async handleListPRComments(args) {
return this.createToolResponse(async () => {
// Validate arguments
validateWithSchemaOrThrow(ManagePullRequestsArgsSchema, args, 'manage_pull_requests.list_comments');
const typedArgs = args;
// Fetch PR comments
const result = await this.client.getComments(typedArgs.workspaceSlug, typedArgs.repoSlug, typedArgs.pullRequestId);
return {
comments: result.values || [],
count: (result.values || []).length,
page: result.page || 1,
pageLen: result.pagelen || (result.values || []).length,
nextUrl: result.next,
};
}, {
toolName: 'manage_pull_requests',
operationName: 'list_pr_comments',
args,
});
}
/**
* Handler for adding a comment to a pull request.
*
* @param args - Tool arguments (must include pullRequestId and content)
* @returns Created comment information
*/
async handleAddPRComment(args) {
return this.createToolResponse(async () => {
// Validate arguments
validateWithSchemaOrThrow(ManagePullRequestsArgsSchema, args, 'manage_pull_requests.add_comment');
const typedArgs = args;
// Add comment with proper payload format
const comment = await this.client.addComment(typedArgs.workspaceSlug, typedArgs.repoSlug, typedArgs.pullRequestId, {
content: {
raw: typedArgs.content,
markup: 'markdown'
}
});
return comment;
}, {
toolName: 'manage_pull_requests',
operationName: 'add_pr_comment',
args,
});
}
}
//# sourceMappingURL=pull-request-tools.js.map