/** * MCP tool router that maps tool calls to Bitbucket API operations. */ import { BitbucketClient } from './bitbucket-client.js'; import { DefaultConfigLoader } from './config.js'; interface ToolCallParams { [key: string]: any; } export interface ToolResult { success: boolean; data?: any; error?: string; } /** * Router that maps MCP tool calls to Bitbucket API operations. */ export class BitbucketRouter { private client: BitbucketClient; constructor() { this.client = new BitbucketClient(); } private getDefaultParams(params: ToolCallParams): { workspace: string | undefined; repoSlug: string | undefined } { const defaults = DefaultConfigLoader.load(); return { workspace: params.workspace || defaults.workspace || undefined, repoSlug: params.repository || params.repo || defaults.repo || undefined }; } /** * Validate the Bitbucket token. */ private async validateToken(): Promise { try { const result = await this.client.validateToken(); return { success: result.valid, data: result }; } catch (error) { return { success: false, error: `Token validation failed: ${String(error)}` }; } } /** * Execute a tool call and return MCP-compatible result. */ async executeTool( toolName: string, params: ToolCallParams ): Promise { switch (toolName) { case 'validate_token': return this.validateToken(); case 'list_pull_requests': return this.listPullRequests(params); case 'get_pull_request': return this.getPullRequest(params); case 'get_pull_request_activities': return this.getPullRequestActivities(params); case 'get_pull_request_changes': return this.getPullRequestChanges(params); case 'get_pull_request_comments': return this.getPullRequestComments(params); case 'get_pull_request_comment': return this.getPullRequestComment(params); case 'get_pull_request_commits': return this.getPullRequestCommits(params); case 'get_pull_request_diff': return this.getPullRequestDiff(params); case 'get_pull_request_patch': return this.getPullRequestPatch(params); case 'get_pull_request_participants': return this.getPullRequestParticipants(params); case 'get_pull_request_reviewers': return this.getPullRequestReviewers(params); case 'get_pull_request_status': return this.getPullRequestStatus(params); case 'get_pull_request_tasks': return this.getPullRequestTasks(params); case 'get_pull_request_task_count': return this.getPullRequestTaskCount(params); case 'get_full_pull_request': return this.getFullPullRequest(params); case 'list_workspaces': return this.listWorkspaces(params); case 'list_repositories': return this.listRepositories(params); case 'get_repository': return this.getRepository(params); case 'list_branches': return this.listBranches(params); case 'create_pull_request': return this.createPullRequest(params); case 'update_pull_request': return this.updatePullRequest(params); case 'merge_pull_request': return this.mergePullRequest(params); case 'decline_pull_request': return this.declinePullRequest(params); case 'approve_pull_request': return this.approvePullRequest(params); case 'unapprove_pull_request': return this.unapprovePullRequest(params); case 'request_changes_pull_request': return this.requestChangesPullRequest(params); case 'remove_request_changes_pull_request': return this.removeRequestChangesPullRequest(params); case 'add_pull_request_comment': return this.addPullRequestComment(params); case 'update_pull_request_comment': return this.updatePullRequestComment(params); case 'delete_pull_request_comment': return this.deletePullRequestComment(params); case 'create_pull_request_task': return this.createPullRequestTask(params); case 'update_pull_request_task': return this.updatePullRequestTask(params); case 'delete_pull_request_task': return this.deletePullRequestTask(params); default: return { success: false, error: `Unknown tool: ${toolName}` }; } } /** * List pull requests in a repository. */ private async listPullRequests( params: ToolCallParams ): Promise { try { const { workspace, repoSlug } = this.getDefaultParams(params); if (!workspace || !repoSlug) { return { success: false, error: 'Missing required parameters: workspace and repository' }; } // Default to open PRs if state not specified const options = params.state ? { state: params.state } : undefined; const prs = await this.client.listPullRequests(workspace, repoSlug, options); return { success: true, data: { pull_requests: prs, count: prs.length } }; } catch (error) { return { success: false, error: `List pull requests failed: ${typeof error === 'string' ? error : String(error)}` }; } } /** * Get a specific pull request. */ private async getPullRequest( params: ToolCallParams ): Promise { try { const { workspace, repoSlug } = this.getDefaultParams(params); const prId = parseInt(params.pullRequestId || params.pr_id, 10); if (!workspace || !repoSlug || isNaN(prId)) { return { success: false, error: 'Missing required parameters' }; } const pr = await this.client.getPullRequest(workspace, repoSlug, prId); return { success: true, data: pr }; } catch (error) { return { success: false, error: `Get pull request failed: ${typeof error === 'string' ? error : String(error)}` }; } } /** * Get pull request activities (events/actions). */ private async getPullRequestActivities( params: ToolCallParams ): Promise { try { const { workspace, repoSlug } = this.getDefaultParams(params); const prId = parseInt(params.pullRequestId || params.pr_id, 10); if (!workspace || !repoSlug || isNaN(prId)) { return { success: false, error: 'Missing required parameters' }; } const result = await this.client.getPullRequestActivities(workspace, repoSlug, prId, { limit: params.limit, start: params.start }); return { success: true, data: result }; } catch (error) { return { success: false, error: `Get activities failed: ${String(error)}` }; } } /** * Get pull request changes (files modified). */ private async getPullRequestChanges( params: ToolCallParams ): Promise { try { const { workspace, repoSlug } = this.getDefaultParams(params); const prId = parseInt(params.pullRequestId || params.pr_id, 10); if (!workspace || !repoSlug || isNaN(prId)) { return { success: false, error: 'Missing required parameters' }; } const result = await this.client.getPullRequestChanges(workspace, repoSlug, prId, { limit: params.limit, start: params.start }); return { success: true, data: result }; } catch (error) { return { success: false, error: `Get changes failed: ${String(error)}` }; } } /** * Get pull request comments. */ private async getPullRequestComments( params: ToolCallParams ): Promise { try { const { workspace, repoSlug } = this.getDefaultParams(params); const prId = parseInt(params.pullRequestId || params.pr_id, 10); if (!workspace || !repoSlug || isNaN(prId)) { return { success: false, error: 'Missing required parameters' }; } const result = await this.client.getPullRequestComments(workspace, repoSlug, prId, { limit: params.limit, start: params.start }); return { success: true, data: result }; } catch (error) { return { success: false, error: `Get comments failed: ${String(error)}` }; } } /** * Get a specific pull request comment. */ private async getPullRequestComment( params: ToolCallParams ): Promise { try { const { workspace, repoSlug } = this.getDefaultParams(params); const prId = parseInt(params.pullRequestId || params.pr_id, 10); const commentId = parseInt(params.commentId || params.comment_id, 10); if (!workspace || !repoSlug || isNaN(prId) || isNaN(commentId)) { return { success: false, error: 'Missing required parameters' }; } const result = await this.client.getPullRequestComment(workspace, repoSlug, prId, commentId); return { success: true, data: result }; } catch (error) { return { success: false, error: `Get comment failed: ${String(error)}` }; } } /** * Get commits in a pull request. */ private async getPullRequestCommits( params: ToolCallParams ): Promise { try { const { workspace, repoSlug } = this.getDefaultParams(params); const prId = parseInt(params.pullRequestId || params.pr_id, 10); if (!workspace || !repoSlug || isNaN(prId)) { return { success: false, error: 'Missing required parameters' }; } const result = await this.client.getPullRequestCommits(workspace, repoSlug, prId, { limit: params.limit, start: params.start }); return { success: true, data: result }; } catch (error) { return { success: false, error: `Get commits failed: ${String(error)}` }; } } /** * Get pull request diff. */ private async getPullRequestDiff( params: ToolCallParams ): Promise { try { const { workspace, repoSlug } = this.getDefaultParams(params); const prId = parseInt(params.pullRequestId || params.pr_id, 10); if (!workspace || !repoSlug || isNaN(prId)) { return { success: false, error: 'Missing required parameters' }; } const result = await this.client.getPullRequestDiff(workspace, repoSlug, prId, { context: params.context, path: params.path, whitespace: params.whitespace }); return { success: true, data: result }; } catch (error) { return { success: false, error: `Get diff failed: ${String(error)}` }; } } /** * Get pull request patch. */ private async getPullRequestPatch( params: ToolCallParams ): Promise { try { const { workspace, repoSlug } = this.getDefaultParams(params); const prId = parseInt(params.pullRequestId || params.pr_id, 10); if (!workspace || !repoSlug || isNaN(prId)) { return { success: false, error: 'Missing required parameters' }; } const result = await this.client.getPullRequestPatch(workspace, repoSlug, prId); return { success: true, data: result }; } catch (error) { return { success: false, error: `Get patch failed: ${String(error)}` }; } } /** * Get pull request participants. */ private async getPullRequestParticipants( params: ToolCallParams ): Promise { try { const { workspace, repoSlug } = this.getDefaultParams(params); const prId = parseInt(params.pullRequestId || params.pr_id, 10); if (!workspace || !repoSlug || isNaN(prId)) { return { success: false, error: 'Missing required parameters' }; } const result = await this.client.getPullRequestParticipants(workspace, repoSlug, prId); return { success: true, data: result }; } catch (error) { return { success: false, error: `Get participants failed: ${String(error)}` }; } } /** * Get pull request reviewers. */ private async getPullRequestReviewers( params: ToolCallParams ): Promise { try { const { workspace, repoSlug } = this.getDefaultParams(params); const prId = parseInt(params.pullRequestId || params.pr_id, 10); if (!workspace || !repoSlug || isNaN(prId)) { return { success: false, error: 'Missing required parameters' }; } const result = await this.client.getPullRequestReviewers(workspace, repoSlug, prId); return { success: true, data: result }; } catch (error) { return { success: false, error: `Get reviewers failed: ${String(error)}` }; } } /** * Get pull request status. */ private async getPullRequestStatus( params: ToolCallParams ): Promise { try { const { workspace, repoSlug } = this.getDefaultParams(params); const prId = parseInt(params.pullRequestId || params.pr_id, 10); if (!workspace || !repoSlug || isNaN(prId)) { return { success: false, error: 'Missing required parameters' }; } const result = await this.client.getPullRequestStatus(workspace, repoSlug, prId); return { success: true, data: result }; } catch (error) { return { success: false, error: `Get status failed: ${String(error)}` }; } } /** * Get pull request tasks. */ private async getPullRequestTasks( params: ToolCallParams ): Promise { try { const { workspace, repoSlug } = this.getDefaultParams(params); const prId = parseInt(params.pullRequestId || params.pr_id, 10); if (!workspace || !repoSlug || isNaN(prId)) { return { success: false, error: 'Missing required parameters' }; } const result = await this.client.getPullRequestTasks(workspace, repoSlug, prId); return { success: true, data: result }; } catch (error) { return { success: false, error: `Get tasks failed: ${String(error)}` }; } } /** * Get pull request task count. */ private async getPullRequestTaskCount( params: ToolCallParams ): Promise { try { const { workspace, repoSlug } = this.getDefaultParams(params); const prId = parseInt(params.pullRequestId || params.pr_id, 10); if (!workspace || !repoSlug || isNaN(prId)) { return { success: false, error: 'Missing required parameters' }; } const result = await this.client.getPullRequestTaskCount(workspace, repoSlug, prId); return { success: true, data: result }; } catch (error) { return { success: false, error: `Get task count failed: ${String(error)}` }; } } /** * Get full pull request details. */ private async getFullPullRequest( params: ToolCallParams ): Promise { try { const { workspace, repoSlug } = this.getDefaultParams(params); const prId = parseInt(params.pullRequestId || params.pr_id, 10); if (!workspace || !repoSlug || isNaN(prId)) { return { success: false, error: 'Missing required parameters' }; } const result = await this.client.getFullPullRequest(workspace, repoSlug, prId); return { success: true, data: result }; } catch (error) { return { success: false, error: `Get full PR failed: ${String(error)}` }; } } private async listWorkspaces(params: ToolCallParams): Promise { try { const result = await this.client.listWorkspaces({ page: params.page, pagelen: params.pagelen, }); return { success: true, data: result }; } catch (error) { return { success: false, error: `List workspaces failed: ${String(error)}` }; } } private async listRepositories(params: ToolCallParams): Promise { try { const { workspace } = this.getDefaultParams(params); if (!workspace) { return { success: false, error: 'Missing required parameters: workspace' }; } const result = await this.client.listRepositories(workspace, { role: params.role, page: params.page, pagelen: params.pagelen, }); return { success: true, data: result }; } catch (error) { return { success: false, error: `List repositories failed: ${String(error)}` }; } } private async getRepository(params: ToolCallParams): Promise { try { const { workspace, repoSlug } = this.getDefaultParams(params); if (!workspace || !repoSlug) { return { success: false, error: 'Missing required parameters: workspace and repository' }; } const result = await this.client.getRepository(workspace, repoSlug); return { success: true, data: result }; } catch (error) { return { success: false, error: `Get repository failed: ${String(error)}` }; } } private async listBranches(params: ToolCallParams): Promise { try { const { workspace, repoSlug } = this.getDefaultParams(params); if (!workspace || !repoSlug) { return { success: false, error: 'Missing required parameters: workspace and repository' }; } const result = await this.client.listBranches(workspace, repoSlug, { filter_by_name: params.filter_by_name, page: params.page, pagelen: params.pagelen, }); return { success: true, data: result }; } catch (error) { return { success: false, error: `List branches failed: ${String(error)}` }; } } private async createPullRequest(params: ToolCallParams): Promise { try { const { workspace, repoSlug } = this.getDefaultParams(params); if (!workspace || !repoSlug || !params.title || !params.source_branch || !params.destination_branch) { return { success: false, error: 'Missing required parameters: workspace, repository, title, source_branch, destination_branch' }; } const result = await this.client.createPullRequest(workspace, repoSlug, { title: params.title, source_branch: params.source_branch, destination_branch: params.destination_branch, description: params.description, reviewers: params.reviewers, close_source_branch: params.close_source_branch, }); return { success: true, data: result }; } catch (error) { return { success: false, error: `Create pull request failed: ${String(error)}` }; } } private async updatePullRequest(params: ToolCallParams): Promise { try { const { workspace, repoSlug } = this.getDefaultParams(params); const prId = parseInt(params.pullRequestId || params.pr_id, 10); if (!workspace || !repoSlug || isNaN(prId)) { return { success: false, error: 'Missing required parameters' }; } const result = await this.client.updatePullRequest(workspace, repoSlug, prId, { title: params.title, description: params.description, reviewers: params.reviewers, destination_branch: params.destination_branch, }); return { success: true, data: result }; } catch (error) { return { success: false, error: `Update pull request failed: ${String(error)}` }; } } private async mergePullRequest(params: ToolCallParams): Promise { try { const { workspace, repoSlug } = this.getDefaultParams(params); const prId = parseInt(params.pullRequestId || params.pr_id, 10); if (!workspace || !repoSlug || isNaN(prId)) { return { success: false, error: 'Missing required parameters' }; } const result = await this.client.mergePullRequest(workspace, repoSlug, prId, { merge_strategy: params.merge_strategy, commit_message: params.commit_message, close_source_branch: params.close_source_branch, }); return { success: true, data: result }; } catch (error) { return { success: false, error: `Merge pull request failed: ${String(error)}` }; } } private async declinePullRequest(params: ToolCallParams): Promise { try { const { workspace, repoSlug } = this.getDefaultParams(params); const prId = parseInt(params.pullRequestId || params.pr_id, 10); if (!workspace || !repoSlug || isNaN(prId)) { return { success: false, error: 'Missing required parameters' }; } const result = await this.client.declinePullRequest(workspace, repoSlug, prId); return { success: true, data: result }; } catch (error) { return { success: false, error: `Decline pull request failed: ${String(error)}` }; } } private async approvePullRequest(params: ToolCallParams): Promise { try { const { workspace, repoSlug } = this.getDefaultParams(params); const prId = parseInt(params.pullRequestId || params.pr_id, 10); if (!workspace || !repoSlug || isNaN(prId)) { return { success: false, error: 'Missing required parameters' }; } const result = await this.client.approvePullRequest(workspace, repoSlug, prId); return { success: true, data: result }; } catch (error) { return { success: false, error: `Approve pull request failed: ${String(error)}` }; } } private async unapprovePullRequest(params: ToolCallParams): Promise { try { const { workspace, repoSlug } = this.getDefaultParams(params); const prId = parseInt(params.pullRequestId || params.pr_id, 10); if (!workspace || !repoSlug || isNaN(prId)) { return { success: false, error: 'Missing required parameters' }; } const result = await this.client.unapprovePullRequest(workspace, repoSlug, prId); return { success: true, data: result }; } catch (error) { return { success: false, error: `Unapprove pull request failed: ${String(error)}` }; } } private async requestChangesPullRequest(params: ToolCallParams): Promise { try { const { workspace, repoSlug } = this.getDefaultParams(params); const prId = parseInt(params.pullRequestId || params.pr_id, 10); if (!workspace || !repoSlug || isNaN(prId)) { return { success: false, error: 'Missing required parameters' }; } const result = await this.client.requestChangesPullRequest(workspace, repoSlug, prId); return { success: true, data: result }; } catch (error) { return { success: false, error: `Request changes failed: ${String(error)}` }; } } private async removeRequestChangesPullRequest(params: ToolCallParams): Promise { try { const { workspace, repoSlug } = this.getDefaultParams(params); const prId = parseInt(params.pullRequestId || params.pr_id, 10); if (!workspace || !repoSlug || isNaN(prId)) { return { success: false, error: 'Missing required parameters' }; } const result = await this.client.removeRequestChangesPullRequest(workspace, repoSlug, prId); return { success: true, data: result }; } catch (error) { return { success: false, error: `Remove request-changes failed: ${String(error)}` }; } } private async addPullRequestComment(params: ToolCallParams): Promise { try { const { workspace, repoSlug } = this.getDefaultParams(params); const prId = parseInt(params.pullRequestId || params.pr_id, 10); if (!workspace || !repoSlug || isNaN(prId) || !params.content) { return { success: false, error: 'Missing required parameters: workspace, repository, pullRequestId, content' }; } const opts: any = { content: params.content }; if (params.inline_path && params.inline_line) { opts.inline = { path: params.inline_path, to: parseInt(params.inline_line, 10) }; } if (params.parent_comment_id) { opts.parent_id = parseInt(params.parent_comment_id, 10); } const result = await this.client.addPullRequestComment(workspace, repoSlug, prId, opts); return { success: true, data: result }; } catch (error) { return { success: false, error: `Add comment failed: ${String(error)}` }; } } private async updatePullRequestComment(params: ToolCallParams): Promise { try { const { workspace, repoSlug } = this.getDefaultParams(params); const prId = parseInt(params.pullRequestId || params.pr_id, 10); const commentId = parseInt(params.commentId || params.comment_id, 10); if (!workspace || !repoSlug || isNaN(prId) || isNaN(commentId) || !params.content) { return { success: false, error: 'Missing required parameters: workspace, repository, pullRequestId, commentId, content' }; } const result = await this.client.updatePullRequestComment(workspace, repoSlug, prId, commentId, { content: params.content }); return { success: true, data: result }; } catch (error) { return { success: false, error: `Update comment failed: ${String(error)}` }; } } private async deletePullRequestComment(params: ToolCallParams): Promise { try { const { workspace, repoSlug } = this.getDefaultParams(params); const prId = parseInt(params.pullRequestId || params.pr_id, 10); const commentId = parseInt(params.commentId || params.comment_id, 10); if (!workspace || !repoSlug || isNaN(prId) || isNaN(commentId)) { return { success: false, error: 'Missing required parameters' }; } const result = await this.client.deletePullRequestComment(workspace, repoSlug, prId, commentId); return { success: true, data: result }; } catch (error) { return { success: false, error: `Delete comment failed: ${String(error)}` }; } } private async createPullRequestTask(params: ToolCallParams): Promise { try { const { workspace, repoSlug } = this.getDefaultParams(params); const prId = parseInt(params.pullRequestId || params.pr_id, 10); if (!workspace || !repoSlug || isNaN(prId) || !params.content) { return { success: false, error: 'Missing required parameters: workspace, repository, pullRequestId, content' }; } const opts: any = { content: params.content }; if (params.comment_id) opts.comment_id = parseInt(params.comment_id, 10); const result = await this.client.createPullRequestTask(workspace, repoSlug, prId, opts); return { success: true, data: result }; } catch (error) { return { success: false, error: `Create task failed: ${String(error)}` }; } } private async updatePullRequestTask(params: ToolCallParams): Promise { try { const { workspace, repoSlug } = this.getDefaultParams(params); const prId = parseInt(params.pullRequestId || params.pr_id, 10); const taskId = parseInt(params.taskId || params.task_id, 10); if (!workspace || !repoSlug || isNaN(prId) || isNaN(taskId)) { return { success: false, error: 'Missing required parameters' }; } const result = await this.client.updatePullRequestTask(workspace, repoSlug, prId, taskId, { content: params.content, state: params.state, }); return { success: true, data: result }; } catch (error) { return { success: false, error: `Update task failed: ${String(error)}` }; } } private async deletePullRequestTask(params: ToolCallParams): Promise { try { const { workspace, repoSlug } = this.getDefaultParams(params); const prId = parseInt(params.pullRequestId || params.pr_id, 10); const taskId = parseInt(params.taskId || params.task_id, 10); if (!workspace || !repoSlug || isNaN(prId) || isNaN(taskId)) { return { success: false, error: 'Missing required parameters' }; } const result = await this.client.deletePullRequestTask(workspace, repoSlug, prId, taskId); return { success: true, data: result }; } catch (error) { return { success: false, error: `Delete task failed: ${String(error)}` }; } } } export default BitbucketRouter;