cli / mcp for bitbucket
1import {
2 addComment,
3 createPullRequest,
4 declinePullRequest,
5 getPullRequest,
6 getPullRequestComments,
7 getPullRequestDiff,
8 listPullRequests,
9 updatePullRequest,
10} from '@bitbucket-tool/core';
11import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
12import {
13 addPullRequestCommentSchema,
14 createPullRequestSchema,
15 declinePullRequestSchema,
16 getPullRequestCommentsSchema,
17 getPullRequestDiffSchema,
18 getPullRequestSchema,
19 listPullRequestsSchema,
20 updatePullRequestSchema,
21} from '../schemas/pr.schemas';
22import { resolveWorkspace, resultToResponse } from './helpers';
23
24export const registerPrTools = (server: McpServer): void => {
25 // @ts-expect-error TS2589: MCP SDK overload resolution + transitive generated types exceed TypeScript recursion limit
26 server.tool(
27 'list_pull_requests',
28 'List pull requests for a repository. Use state to filter by OPEN, MERGED, DECLINED, or SUPERSEDED. Returns PR IDs needed by other PR tools.',
29 listPullRequestsSchema,
30 { readOnlyHint: true },
31 async ({ workspace, repo_slug, state, page, pagelen }) => {
32 const w = resolveWorkspace(workspace);
33 return resultToResponse(
34 await listPullRequests({ workspace: w, repoSlug: repo_slug, state, page, pagelen })
35 );
36 }
37 );
38
39 // @ts-expect-error TS2589: MCP SDK overload resolution + transitive generated types exceed TypeScript recursion limit
40 server.tool(
41 'get_pull_request',
42 'Get details of a specific pull request including title, description, source/destination branches, author, and status. Use list_pull_requests to find PR IDs.',
43 getPullRequestSchema,
44 { readOnlyHint: true },
45 async ({ workspace, repo_slug, pull_request_id }) => {
46 const w = resolveWorkspace(workspace);
47 return resultToResponse(
48 await getPullRequest({ workspace: w, repoSlug: repo_slug, prId: pull_request_id })
49 );
50 }
51 );
52
53 server.tool(
54 'create_pull_request',
55 'Create a new pull request. Requires source branch and title. Destination defaults to main.',
56 createPullRequestSchema,
57 { readOnlyHint: false },
58 async ({ workspace, repo_slug, source_branch, destination_branch, title, description }) => {
59 const w = resolveWorkspace(workspace);
60 return resultToResponse(
61 await createPullRequest({
62 workspace: w,
63 repoSlug: repo_slug,
64 sourceBranch: source_branch,
65 destinationBranch: destination_branch ?? 'main',
66 title,
67 description,
68 })
69 );
70 }
71 );
72
73 server.tool(
74 'update_pull_request',
75 'Update an existing pull request. Can change title, description, or destination branch. Use get_pull_request to see current values first.',
76 updatePullRequestSchema,
77 { readOnlyHint: false, idempotentHint: true },
78 async ({ workspace, repo_slug, pull_request_id, title, description, destination_branch }) => {
79 const w = resolveWorkspace(workspace);
80 const updates = {
81 ...(title !== undefined && { title }),
82 ...(description !== undefined && { description }),
83 ...(destination_branch !== undefined && {
84 destination: { branch: { name: destination_branch } },
85 }),
86 };
87
88 return resultToResponse(
89 await updatePullRequest({
90 workspace: w,
91 repoSlug: repo_slug,
92 prId: pull_request_id,
93 updates,
94 })
95 );
96 }
97 );
98
99 server.tool(
100 'decline_pull_request',
101 'Decline (close) a pull request. This is irreversible.',
102 declinePullRequestSchema,
103 { readOnlyHint: false, destructiveHint: true },
104 async ({ workspace, repo_slug, pull_request_id }) => {
105 const w = resolveWorkspace(workspace);
106 return resultToResponse(
107 await declinePullRequest({ workspace: w, repoSlug: repo_slug, prId: pull_request_id }),
108 () => 'Pull request declined.'
109 );
110 }
111 );
112
113 server.tool(
114 'get_pull_request_comments',
115 'Get all comments on a pull request, including inline code review comments with file/line information.',
116 getPullRequestCommentsSchema,
117 { readOnlyHint: true },
118 async ({ workspace, repo_slug, pull_request_id, page, pagelen }) => {
119 const w = resolveWorkspace(workspace);
120 return resultToResponse(
121 await getPullRequestComments({
122 workspace: w,
123 repoSlug: repo_slug,
124 prId: pull_request_id,
125 page,
126 pagelen,
127 })
128 );
129 }
130 );
131
132 server.tool(
133 'add_pull_request_comment',
134 'Add a comment to a pull request. Supports Markdown formatting.',
135 addPullRequestCommentSchema,
136 { readOnlyHint: false },
137 async ({ workspace, repo_slug, pull_request_id, content }) => {
138 const w = resolveWorkspace(workspace);
139 return resultToResponse(
140 await addComment({
141 workspace: w,
142 repoSlug: repo_slug,
143 prId: pull_request_id,
144 content,
145 }),
146 () => 'Comment added.'
147 );
148 }
149 );
150
151 server.tool(
152 'get_pull_request_diff',
153 'Get the diff for a pull request. Returns raw unified diff text. Use get_pull_request for metadata.',
154 getPullRequestDiffSchema,
155 { readOnlyHint: true },
156 async ({ workspace, repo_slug, pull_request_id }) => {
157 const w = resolveWorkspace(workspace);
158 const result = await getPullRequestDiff({
159 workspace: w,
160 repoSlug: repo_slug,
161 prId: pull_request_id,
162 });
163
164 return resultToResponse(result, (diff) => diff);
165 }
166 );
167};