@atercates/bitbucket-mcp 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +347 -0
  3. package/dist/client.d.ts +16 -0
  4. package/dist/client.js +35 -0
  5. package/dist/client.js.map +1 -0
  6. package/dist/config.d.ts +11 -0
  7. package/dist/config.js +54 -0
  8. package/dist/config.js.map +1 -0
  9. package/dist/handlers/branching-model.d.ts +2 -0
  10. package/dist/handlers/branching-model.js +324 -0
  11. package/dist/handlers/branching-model.js.map +1 -0
  12. package/dist/handlers/commits.d.ts +2 -0
  13. package/dist/handlers/commits.js +78 -0
  14. package/dist/handlers/commits.js.map +1 -0
  15. package/dist/handlers/index.d.ts +17 -0
  16. package/dist/handlers/index.js +29 -0
  17. package/dist/handlers/index.js.map +1 -0
  18. package/dist/handlers/pipelines.d.ts +2 -0
  19. package/dist/handlers/pipelines.js +538 -0
  20. package/dist/handlers/pipelines.js.map +1 -0
  21. package/dist/handlers/pr-comments.d.ts +2 -0
  22. package/dist/handlers/pr-comments.js +509 -0
  23. package/dist/handlers/pr-comments.js.map +1 -0
  24. package/dist/handlers/pr-content.d.ts +2 -0
  25. package/dist/handlers/pr-content.js +332 -0
  26. package/dist/handlers/pr-content.js.map +1 -0
  27. package/dist/handlers/pr-tasks.d.ts +2 -0
  28. package/dist/handlers/pr-tasks.js +275 -0
  29. package/dist/handlers/pr-tasks.js.map +1 -0
  30. package/dist/handlers/pull-requests.d.ts +2 -0
  31. package/dist/handlers/pull-requests.js +902 -0
  32. package/dist/handlers/pull-requests.js.map +1 -0
  33. package/dist/handlers/refs.d.ts +2 -0
  34. package/dist/handlers/refs.js +225 -0
  35. package/dist/handlers/refs.js.map +1 -0
  36. package/dist/handlers/repositories.d.ts +2 -0
  37. package/dist/handlers/repositories.js +131 -0
  38. package/dist/handlers/repositories.js.map +1 -0
  39. package/dist/handlers/source.d.ts +2 -0
  40. package/dist/handlers/source.js +35 -0
  41. package/dist/handlers/source.js.map +1 -0
  42. package/dist/handlers/types.d.ts +42 -0
  43. package/dist/handlers/types.js +2 -0
  44. package/dist/handlers/types.js.map +1 -0
  45. package/dist/handlers/users.d.ts +2 -0
  46. package/dist/handlers/users.js +38 -0
  47. package/dist/handlers/users.js.map +1 -0
  48. package/dist/index.d.ts +2 -0
  49. package/dist/index.js +4 -0
  50. package/dist/index.js.map +1 -0
  51. package/dist/logger.d.ts +3 -0
  52. package/dist/logger.js +60 -0
  53. package/dist/logger.js.map +1 -0
  54. package/dist/pagination.d.ts +32 -0
  55. package/dist/pagination.js +116 -0
  56. package/dist/pagination.js.map +1 -0
  57. package/dist/schemas.d.ts +21 -0
  58. package/dist/schemas.js +23 -0
  59. package/dist/schemas.js.map +1 -0
  60. package/dist/server.d.ts +33 -0
  61. package/dist/server.js +124 -0
  62. package/dist/server.js.map +1 -0
  63. package/dist/types.d.ts +296 -0
  64. package/dist/types.js +3 -0
  65. package/dist/types.js.map +1 -0
  66. package/dist/utils.d.ts +18 -0
  67. package/dist/utils.js +17 -0
  68. package/dist/utils.js.map +1 -0
  69. package/docs/README.md +216 -0
  70. package/docs/TOOLS.md +464 -0
  71. package/docs/architecture/ARCHITECTURE.md +302 -0
  72. package/docs/guides/ENVIRONMENT_VARIABLES.md +306 -0
  73. package/docs/guides/GETTING_STARTED.md +267 -0
  74. package/docs/guides/GITHUB_ACTIONS_SETUP.md +148 -0
  75. package/docs/guides/NPM_DEPLOYMENT.md +266 -0
  76. package/docs/guides/PROJECT_STRUCTURE.md +317 -0
  77. package/package.json +84 -0
@@ -0,0 +1,902 @@
1
+ import { PAGINATION_BASE_SCHEMA, PAGINATION_ALL_SCHEMA, LEGACY_LIMIT_SCHEMA } from "../schemas.js";
2
+ import { jsonResponse, textResponse } from "../utils.js";
3
+ import { logger } from "../logger.js";
4
+ import { ErrorCode, McpError } from "@modelcontextprotocol/sdk/types.js";
5
+ import { BITBUCKET_MAX_PAGELEN } from "../pagination.js";
6
+ export const pullRequestsModule = {
7
+ tools: [
8
+ {
9
+ name: "getPullRequests",
10
+ description: "Get pull requests for a repository",
11
+ inputSchema: {
12
+ type: "object",
13
+ properties: {
14
+ workspace: {
15
+ type: "string",
16
+ description: "Bitbucket workspace name",
17
+ },
18
+ repo_slug: { type: "string", description: "Repository slug" },
19
+ state: {
20
+ type: "string",
21
+ enum: ["OPEN", "MERGED", "DECLINED", "SUPERSEDED"],
22
+ description: "Pull request state",
23
+ },
24
+ ...PAGINATION_BASE_SCHEMA,
25
+ all: PAGINATION_ALL_SCHEMA,
26
+ limit: LEGACY_LIMIT_SCHEMA,
27
+ },
28
+ required: ["workspace", "repo_slug"],
29
+ },
30
+ },
31
+ {
32
+ name: "createPullRequest",
33
+ description: "Create a new pull request",
34
+ inputSchema: {
35
+ type: "object",
36
+ properties: {
37
+ workspace: {
38
+ type: "string",
39
+ description: "Bitbucket workspace name",
40
+ },
41
+ repo_slug: { type: "string", description: "Repository slug" },
42
+ title: { type: "string", description: "Pull request title" },
43
+ description: {
44
+ type: "string",
45
+ description: "Pull request description",
46
+ },
47
+ sourceBranch: {
48
+ type: "string",
49
+ description: "Source branch name",
50
+ },
51
+ targetBranch: {
52
+ type: "string",
53
+ description: "Target branch name",
54
+ },
55
+ reviewers: {
56
+ type: "array",
57
+ items: { type: "string" },
58
+ description: "List of reviewer UUIDs (e.g., '{04776764-62c7-453b-b97e-302f60395ceb}')",
59
+ },
60
+ draft: {
61
+ type: "boolean",
62
+ description: "Whether to create the pull request as a draft",
63
+ },
64
+ close_source_branch: {
65
+ type: "boolean",
66
+ description: "Whether to close source branch after merge (default: true)",
67
+ },
68
+ },
69
+ required: [
70
+ "workspace",
71
+ "repo_slug",
72
+ "title",
73
+ "description",
74
+ "sourceBranch",
75
+ "targetBranch",
76
+ ],
77
+ },
78
+ },
79
+ {
80
+ name: "getPullRequest",
81
+ description: "Get details for a specific pull request",
82
+ inputSchema: {
83
+ type: "object",
84
+ properties: {
85
+ workspace: {
86
+ type: "string",
87
+ description: "Bitbucket workspace name",
88
+ },
89
+ repo_slug: { type: "string", description: "Repository slug" },
90
+ pull_request_id: {
91
+ type: "string",
92
+ description: "Pull request ID",
93
+ },
94
+ ...PAGINATION_BASE_SCHEMA,
95
+ all: PAGINATION_ALL_SCHEMA,
96
+ },
97
+ required: ["workspace", "repo_slug", "pull_request_id"],
98
+ },
99
+ },
100
+ {
101
+ name: "updatePullRequest",
102
+ description: "Update a pull request",
103
+ inputSchema: {
104
+ type: "object",
105
+ properties: {
106
+ workspace: {
107
+ type: "string",
108
+ description: "Bitbucket workspace name",
109
+ },
110
+ repo_slug: { type: "string", description: "Repository slug" },
111
+ pull_request_id: {
112
+ type: "string",
113
+ description: "Pull request ID",
114
+ },
115
+ title: { type: "string", description: "New pull request title" },
116
+ description: {
117
+ type: "string",
118
+ description: "New pull request description",
119
+ },
120
+ reviewers: {
121
+ type: "array",
122
+ items: { type: "string" },
123
+ description: "List of reviewer UUIDs (e.g., '{04776764-62c7-453b-b97e-302f60395ceb}')",
124
+ },
125
+ destination: {
126
+ type: "string",
127
+ description: "New destination branch name",
128
+ },
129
+ close_source_branch: {
130
+ type: "boolean",
131
+ description: "Whether to close source branch after merge",
132
+ },
133
+ draft: {
134
+ type: "boolean",
135
+ description: "Whether the pull request is a draft",
136
+ },
137
+ },
138
+ required: ["workspace", "repo_slug", "pull_request_id"],
139
+ },
140
+ },
141
+ {
142
+ name: "approvePullRequest",
143
+ description: "Approve a pull request",
144
+ inputSchema: {
145
+ type: "object",
146
+ properties: {
147
+ workspace: {
148
+ type: "string",
149
+ description: "Bitbucket workspace name",
150
+ },
151
+ repo_slug: { type: "string", description: "Repository slug" },
152
+ pull_request_id: {
153
+ type: "string",
154
+ description: "Pull request ID",
155
+ },
156
+ ...PAGINATION_BASE_SCHEMA,
157
+ all: PAGINATION_ALL_SCHEMA,
158
+ },
159
+ required: ["workspace", "repo_slug", "pull_request_id"],
160
+ },
161
+ },
162
+ {
163
+ name: "unapprovePullRequest",
164
+ description: "Remove approval from a pull request",
165
+ inputSchema: {
166
+ type: "object",
167
+ properties: {
168
+ workspace: {
169
+ type: "string",
170
+ description: "Bitbucket workspace name",
171
+ },
172
+ repo_slug: { type: "string", description: "Repository slug" },
173
+ pull_request_id: {
174
+ type: "string",
175
+ description: "Pull request ID",
176
+ },
177
+ ...PAGINATION_BASE_SCHEMA,
178
+ all: PAGINATION_ALL_SCHEMA,
179
+ },
180
+ required: ["workspace", "repo_slug", "pull_request_id"],
181
+ },
182
+ },
183
+ {
184
+ name: "declinePullRequest",
185
+ description: "Decline a pull request",
186
+ inputSchema: {
187
+ type: "object",
188
+ properties: {
189
+ workspace: {
190
+ type: "string",
191
+ description: "Bitbucket workspace name",
192
+ },
193
+ repo_slug: { type: "string", description: "Repository slug" },
194
+ pull_request_id: {
195
+ type: "string",
196
+ description: "Pull request ID",
197
+ },
198
+ },
199
+ required: ["workspace", "repo_slug", "pull_request_id"],
200
+ },
201
+ },
202
+ {
203
+ name: "mergePullRequest",
204
+ description: "Merge a pull request",
205
+ inputSchema: {
206
+ type: "object",
207
+ properties: {
208
+ workspace: {
209
+ type: "string",
210
+ description: "Bitbucket workspace name",
211
+ },
212
+ repo_slug: { type: "string", description: "Repository slug" },
213
+ pull_request_id: {
214
+ type: "string",
215
+ description: "Pull request ID",
216
+ },
217
+ message: { type: "string", description: "Merge commit message" },
218
+ merge_strategy: {
219
+ type: "string",
220
+ enum: [
221
+ "merge_commit",
222
+ "squash",
223
+ "fast_forward",
224
+ "squash_fast_forward",
225
+ "rebase_fast_forward",
226
+ "rebase_merge",
227
+ ],
228
+ description: "Merge strategy",
229
+ },
230
+ close_source_branch: {
231
+ type: "boolean",
232
+ description: "Whether to close the source branch after merge",
233
+ },
234
+ },
235
+ required: ["workspace", "repo_slug", "pull_request_id"],
236
+ },
237
+ },
238
+ {
239
+ name: "createDraftPullRequest",
240
+ description: "Create a new draft pull request",
241
+ inputSchema: {
242
+ type: "object",
243
+ properties: {
244
+ workspace: {
245
+ type: "string",
246
+ description: "Bitbucket workspace name",
247
+ },
248
+ repo_slug: { type: "string", description: "Repository slug" },
249
+ title: { type: "string", description: "Pull request title" },
250
+ description: {
251
+ type: "string",
252
+ description: "Pull request description",
253
+ },
254
+ sourceBranch: {
255
+ type: "string",
256
+ description: "Source branch name",
257
+ },
258
+ targetBranch: {
259
+ type: "string",
260
+ description: "Target branch name",
261
+ },
262
+ reviewers: {
263
+ type: "array",
264
+ items: { type: "string" },
265
+ description: "List of reviewer UUIDs (e.g., '{04776764-62c7-453b-b97e-302f60395ceb}')",
266
+ },
267
+ },
268
+ required: [
269
+ "workspace",
270
+ "repo_slug",
271
+ "title",
272
+ "description",
273
+ "sourceBranch",
274
+ "targetBranch",
275
+ ],
276
+ },
277
+ },
278
+ {
279
+ name: "publishDraftPullRequest",
280
+ description: "Publish a draft pull request to make it ready for review",
281
+ inputSchema: {
282
+ type: "object",
283
+ properties: {
284
+ workspace: {
285
+ type: "string",
286
+ description: "Bitbucket workspace name",
287
+ },
288
+ repo_slug: { type: "string", description: "Repository slug" },
289
+ pull_request_id: {
290
+ type: "string",
291
+ description: "Pull request ID",
292
+ },
293
+ },
294
+ required: ["workspace", "repo_slug", "pull_request_id"],
295
+ },
296
+ },
297
+ {
298
+ name: "convertTodraft",
299
+ description: "Convert a regular pull request to draft status",
300
+ inputSchema: {
301
+ type: "object",
302
+ properties: {
303
+ workspace: {
304
+ type: "string",
305
+ description: "Bitbucket workspace name",
306
+ },
307
+ repo_slug: { type: "string", description: "Repository slug" },
308
+ pull_request_id: {
309
+ type: "string",
310
+ description: "Pull request ID",
311
+ },
312
+ },
313
+ required: ["workspace", "repo_slug", "pull_request_id"],
314
+ },
315
+ },
316
+ {
317
+ name: "getPendingReviewPRs",
318
+ description: "List all open pull requests in the workspace where the authenticated user is a reviewer and has not yet approved.",
319
+ inputSchema: {
320
+ type: "object",
321
+ properties: {
322
+ workspace: {
323
+ type: "string",
324
+ description: "Bitbucket workspace name (optional, defaults to BITBUCKET_WORKSPACE)",
325
+ },
326
+ limit: {
327
+ type: "number",
328
+ description: "Maximum number of PRs to return (optional)",
329
+ },
330
+ repositoryList: {
331
+ type: "array",
332
+ items: { type: "string" },
333
+ description: "List of repository slugs to check (optional)",
334
+ },
335
+ },
336
+ },
337
+ },
338
+ {
339
+ name: "requestChanges",
340
+ description: "Request changes on a pull request",
341
+ inputSchema: {
342
+ type: "object",
343
+ properties: {
344
+ workspace: { type: "string", description: "Bitbucket workspace name" },
345
+ repo_slug: { type: "string", description: "Repository slug" },
346
+ pull_request_id: { type: "string", description: "Pull request ID" },
347
+ },
348
+ required: ["workspace", "repo_slug", "pull_request_id"],
349
+ },
350
+ },
351
+ {
352
+ name: "removeRequestChanges",
353
+ description: "Remove request changes from a pull request",
354
+ inputSchema: {
355
+ type: "object",
356
+ properties: {
357
+ workspace: { type: "string", description: "Bitbucket workspace name" },
358
+ repo_slug: { type: "string", description: "Repository slug" },
359
+ pull_request_id: { type: "string", description: "Pull request ID" },
360
+ },
361
+ required: ["workspace", "repo_slug", "pull_request_id"],
362
+ },
363
+ },
364
+ ],
365
+ createHandlers: (client) => ({
366
+ getPullRequests: async (args) => {
367
+ const workspace = args.workspace;
368
+ const repo_slug = args.repo_slug;
369
+ const state = args.state;
370
+ const pagelen = args.pagelen;
371
+ const page = args.page;
372
+ const all = args.all;
373
+ const legacyLimit = args.limit;
374
+ try {
375
+ logger.info("Getting Bitbucket pull requests", {
376
+ workspace,
377
+ repo_slug,
378
+ state,
379
+ pagelen: pagelen ?? legacyLimit,
380
+ page,
381
+ all,
382
+ });
383
+ const params = {};
384
+ if (state) {
385
+ params.state = state;
386
+ }
387
+ const result = await client.paginator.fetchValues(`/repositories/${workspace}/${repo_slug}/pullrequests`, {
388
+ pagelen: pagelen ?? legacyLimit,
389
+ page,
390
+ all,
391
+ params,
392
+ description: "getPullRequests",
393
+ });
394
+ return jsonResponse(result.values);
395
+ }
396
+ catch (error) {
397
+ logger.error("Error getting pull requests", {
398
+ error,
399
+ workspace,
400
+ repo_slug,
401
+ });
402
+ throw new McpError(ErrorCode.InternalError, `Failed to get pull requests: ${error instanceof Error ? error.message : String(error)}`);
403
+ }
404
+ },
405
+ createPullRequest: async (args) => {
406
+ const workspace = args.workspace;
407
+ const repo_slug = args.repo_slug;
408
+ const title = args.title;
409
+ const description = args.description;
410
+ const sourceBranch = args.sourceBranch;
411
+ const targetBranch = args.targetBranch;
412
+ const reviewers = args.reviewers;
413
+ const draft = args.draft;
414
+ const close_source_branch = args.close_source_branch;
415
+ try {
416
+ logger.info("Creating Bitbucket pull request", {
417
+ workspace,
418
+ repo_slug,
419
+ title,
420
+ sourceBranch,
421
+ targetBranch,
422
+ });
423
+ // Prepare reviewers format if provided
424
+ // Bitbucket API expects reviewers as array of objects: [{uuid: "{...}"}]
425
+ // Input is string array of UUIDs: ["{04776764-62c7-453b-b97e-302f60395ceb}", ...]
426
+ // Convert to API format: [{uuid: "{...}"}, ...]
427
+ let reviewersArray;
428
+ if (reviewers && reviewers.length > 0) {
429
+ reviewersArray = reviewers
430
+ .filter((uuid) => typeof uuid === "string" && uuid.trim().length > 0)
431
+ .map((uuid) => ({ uuid: uuid.trim() }));
432
+ if (reviewersArray.length === 0) {
433
+ reviewersArray = undefined;
434
+ }
435
+ }
436
+ // Build request payload - only include reviewers if provided
437
+ const requestPayload = {
438
+ title,
439
+ description,
440
+ source: {
441
+ branch: {
442
+ name: sourceBranch,
443
+ },
444
+ },
445
+ destination: {
446
+ branch: {
447
+ name: targetBranch,
448
+ },
449
+ },
450
+ close_source_branch: close_source_branch ?? true,
451
+ };
452
+ // Only include reviewers field if there are reviewers to add
453
+ if (reviewersArray && reviewersArray.length > 0) {
454
+ requestPayload.reviewers = reviewersArray;
455
+ }
456
+ // Only include draft field if explicitly set to true
457
+ if (draft === true) {
458
+ requestPayload.draft = true;
459
+ }
460
+ // Create the pull request
461
+ const response = await client.api.post(`/repositories/${workspace}/${repo_slug}/pullrequests`, requestPayload);
462
+ return jsonResponse(response.data);
463
+ }
464
+ catch (error) {
465
+ logger.error("Error creating pull request", {
466
+ error,
467
+ workspace,
468
+ repo_slug,
469
+ });
470
+ throw new McpError(ErrorCode.InternalError, `Failed to create pull request: ${error instanceof Error ? error.message : String(error)}`);
471
+ }
472
+ },
473
+ getPullRequest: async (args) => {
474
+ const workspace = args.workspace;
475
+ const repo_slug = args.repo_slug;
476
+ const pull_request_id = args.pull_request_id;
477
+ try {
478
+ logger.info("Getting Bitbucket pull request details", {
479
+ workspace,
480
+ repo_slug,
481
+ pull_request_id,
482
+ });
483
+ const response = await client.api.get(`/repositories/${workspace}/${repo_slug}/pullrequests/${pull_request_id}`);
484
+ return jsonResponse(response.data);
485
+ }
486
+ catch (error) {
487
+ logger.error("Error getting pull request details", {
488
+ error,
489
+ workspace,
490
+ repo_slug,
491
+ pull_request_id,
492
+ });
493
+ throw new McpError(ErrorCode.InternalError, `Failed to get pull request details: ${error instanceof Error ? error.message : String(error)}`);
494
+ }
495
+ },
496
+ updatePullRequest: async (args) => {
497
+ const workspace = args.workspace;
498
+ const repo_slug = args.repo_slug;
499
+ const pull_request_id = args.pull_request_id;
500
+ const title = args.title;
501
+ const description = args.description;
502
+ const reviewers = args.reviewers;
503
+ const destination = args.destination;
504
+ const close_source_branch = args.close_source_branch;
505
+ const draft = args.draft;
506
+ try {
507
+ logger.info("Updating Bitbucket pull request", {
508
+ workspace,
509
+ repo_slug,
510
+ pull_request_id,
511
+ });
512
+ // Only include fields that are provided
513
+ const updateData = {};
514
+ if (title !== undefined)
515
+ updateData.title = title;
516
+ if (description !== undefined)
517
+ updateData.description = description;
518
+ if (reviewers !== undefined) {
519
+ updateData.reviewers = reviewers
520
+ .filter((uuid) => typeof uuid === "string" && uuid.trim().length > 0)
521
+ .map((uuid) => ({ uuid: uuid.trim() }));
522
+ }
523
+ if (destination !== undefined) {
524
+ updateData.destination = { branch: { name: destination } };
525
+ }
526
+ if (close_source_branch !== undefined)
527
+ updateData.close_source_branch = close_source_branch;
528
+ if (draft !== undefined)
529
+ updateData.draft = draft;
530
+ const response = await client.api.put(`/repositories/${workspace}/${repo_slug}/pullrequests/${pull_request_id}`, updateData);
531
+ return jsonResponse(response.data);
532
+ }
533
+ catch (error) {
534
+ logger.error("Error updating pull request", {
535
+ error,
536
+ workspace,
537
+ repo_slug,
538
+ pull_request_id,
539
+ });
540
+ throw new McpError(ErrorCode.InternalError, `Failed to update pull request: ${error instanceof Error ? error.message : String(error)}`);
541
+ }
542
+ },
543
+ approvePullRequest: async (args) => {
544
+ const workspace = args.workspace;
545
+ const repo_slug = args.repo_slug;
546
+ const pull_request_id = args.pull_request_id;
547
+ try {
548
+ logger.info("Approving Bitbucket pull request", {
549
+ workspace,
550
+ repo_slug,
551
+ pull_request_id,
552
+ });
553
+ const response = await client.api.post(`/repositories/${workspace}/${repo_slug}/pullrequests/${pull_request_id}/approve`);
554
+ return jsonResponse(response.data);
555
+ }
556
+ catch (error) {
557
+ logger.error("Error approving pull request", {
558
+ error,
559
+ workspace,
560
+ repo_slug,
561
+ pull_request_id,
562
+ });
563
+ throw new McpError(ErrorCode.InternalError, `Failed to approve pull request: ${error instanceof Error ? error.message : String(error)}`);
564
+ }
565
+ },
566
+ unapprovePullRequest: async (args) => {
567
+ const workspace = args.workspace;
568
+ const repo_slug = args.repo_slug;
569
+ const pull_request_id = args.pull_request_id;
570
+ try {
571
+ logger.info("Unapproving Bitbucket pull request", {
572
+ workspace,
573
+ repo_slug,
574
+ pull_request_id,
575
+ });
576
+ await client.api.delete(`/repositories/${workspace}/${repo_slug}/pullrequests/${pull_request_id}/approve`);
577
+ return textResponse("Pull request approval removed successfully.");
578
+ }
579
+ catch (error) {
580
+ logger.error("Error unapproving pull request", {
581
+ error,
582
+ workspace,
583
+ repo_slug,
584
+ pull_request_id,
585
+ });
586
+ throw new McpError(ErrorCode.InternalError, `Failed to unapprove pull request: ${error instanceof Error ? error.message : String(error)}`);
587
+ }
588
+ },
589
+ declinePullRequest: async (args) => {
590
+ const workspace = args.workspace;
591
+ const repo_slug = args.repo_slug;
592
+ const pull_request_id = args.pull_request_id;
593
+ try {
594
+ logger.info("Declining Bitbucket pull request", {
595
+ workspace,
596
+ repo_slug,
597
+ pull_request_id,
598
+ });
599
+ const response = await client.api.post(`/repositories/${workspace}/${repo_slug}/pullrequests/${pull_request_id}/decline`);
600
+ return jsonResponse(response.data);
601
+ }
602
+ catch (error) {
603
+ logger.error("Error declining pull request", {
604
+ error,
605
+ workspace,
606
+ repo_slug,
607
+ pull_request_id,
608
+ });
609
+ throw new McpError(ErrorCode.InternalError, `Failed to decline pull request: ${error instanceof Error ? error.message : String(error)}`);
610
+ }
611
+ },
612
+ mergePullRequest: async (args) => {
613
+ const workspace = args.workspace;
614
+ const repo_slug = args.repo_slug;
615
+ const pull_request_id = args.pull_request_id;
616
+ const message = args.message;
617
+ const merge_strategy = args.merge_strategy;
618
+ const close_source_branch = args.close_source_branch;
619
+ try {
620
+ logger.info("Merging Bitbucket pull request", {
621
+ workspace,
622
+ repo_slug,
623
+ pull_request_id,
624
+ merge_strategy,
625
+ });
626
+ // Build request data
627
+ const data = {};
628
+ if (message)
629
+ data.message = message;
630
+ if (merge_strategy)
631
+ data.merge_strategy = merge_strategy;
632
+ if (close_source_branch !== undefined)
633
+ data.close_source_branch = close_source_branch;
634
+ const response = await client.api.post(`/repositories/${workspace}/${repo_slug}/pullrequests/${pull_request_id}/merge`, data);
635
+ return jsonResponse(response.data);
636
+ }
637
+ catch (error) {
638
+ logger.error("Error merging pull request", {
639
+ error,
640
+ workspace,
641
+ repo_slug,
642
+ pull_request_id,
643
+ });
644
+ throw new McpError(ErrorCode.InternalError, `Failed to merge pull request: ${error instanceof Error ? error.message : String(error)}`);
645
+ }
646
+ },
647
+ createDraftPullRequest: async (args) => {
648
+ const workspace = args.workspace;
649
+ const repo_slug = args.repo_slug;
650
+ const title = args.title;
651
+ const description = args.description;
652
+ const sourceBranch = args.sourceBranch;
653
+ const targetBranch = args.targetBranch;
654
+ const reviewers = args.reviewers;
655
+ try {
656
+ logger.info("Creating draft Bitbucket pull request", {
657
+ workspace,
658
+ repo_slug,
659
+ title,
660
+ sourceBranch,
661
+ targetBranch,
662
+ });
663
+ // Prepare reviewers format if provided
664
+ let reviewersArray;
665
+ if (reviewers && reviewers.length > 0) {
666
+ reviewersArray = reviewers
667
+ .filter((uuid) => typeof uuid === "string" && uuid.trim().length > 0)
668
+ .map((uuid) => ({ uuid: uuid.trim() }));
669
+ if (reviewersArray.length === 0) {
670
+ reviewersArray = undefined;
671
+ }
672
+ }
673
+ // Build request payload with draft=true
674
+ const requestPayload = {
675
+ title,
676
+ description,
677
+ source: {
678
+ branch: {
679
+ name: sourceBranch,
680
+ },
681
+ },
682
+ destination: {
683
+ branch: {
684
+ name: targetBranch,
685
+ },
686
+ },
687
+ close_source_branch: true,
688
+ draft: true,
689
+ };
690
+ // Only include reviewers field if there are reviewers to add
691
+ if (reviewersArray && reviewersArray.length > 0) {
692
+ requestPayload.reviewers = reviewersArray;
693
+ }
694
+ // Create the draft pull request
695
+ const response = await client.api.post(`/repositories/${workspace}/${repo_slug}/pullrequests`, requestPayload);
696
+ return jsonResponse(response.data);
697
+ }
698
+ catch (error) {
699
+ logger.error("Error creating draft pull request", {
700
+ error,
701
+ workspace,
702
+ repo_slug,
703
+ });
704
+ throw new McpError(ErrorCode.InternalError, `Failed to create draft pull request: ${error instanceof Error ? error.message : String(error)}`);
705
+ }
706
+ },
707
+ publishDraftPullRequest: async (args) => {
708
+ const workspace = args.workspace;
709
+ const repo_slug = args.repo_slug;
710
+ const pull_request_id = args.pull_request_id;
711
+ try {
712
+ logger.info("Publishing draft pull request", {
713
+ workspace,
714
+ repo_slug,
715
+ pull_request_id,
716
+ });
717
+ // Update the pull request to set draft=false
718
+ const response = await client.api.put(`/repositories/${workspace}/${repo_slug}/pullrequests/${pull_request_id}`, {
719
+ draft: false,
720
+ });
721
+ return jsonResponse(response.data);
722
+ }
723
+ catch (error) {
724
+ logger.error("Error publishing draft pull request", {
725
+ error,
726
+ workspace,
727
+ repo_slug,
728
+ pull_request_id,
729
+ });
730
+ throw new McpError(ErrorCode.InternalError, `Failed to publish draft pull request: ${error instanceof Error ? error.message : String(error)}`);
731
+ }
732
+ },
733
+ convertTodraft: async (args) => {
734
+ const workspace = args.workspace;
735
+ const repo_slug = args.repo_slug;
736
+ const pull_request_id = args.pull_request_id;
737
+ try {
738
+ logger.info("Converting pull request to draft", {
739
+ workspace,
740
+ repo_slug,
741
+ pull_request_id,
742
+ });
743
+ // Update the pull request to set draft=true
744
+ const response = await client.api.put(`/repositories/${workspace}/${repo_slug}/pullrequests/${pull_request_id}`, {
745
+ draft: true,
746
+ });
747
+ return jsonResponse(response.data);
748
+ }
749
+ catch (error) {
750
+ logger.error("Error converting pull request to draft", {
751
+ error,
752
+ workspace,
753
+ repo_slug,
754
+ pull_request_id,
755
+ });
756
+ throw new McpError(ErrorCode.InternalError, `Failed to convert pull request to draft: ${error instanceof Error ? error.message : String(error)}`);
757
+ }
758
+ },
759
+ getPendingReviewPRs: async (args) => {
760
+ const workspace = args.workspace;
761
+ const limit = args.limit || 50;
762
+ const repositoryList = args.repositoryList;
763
+ try {
764
+ const wsName = client.resolveWorkspace(workspace);
765
+ // Get current user's account_id from /user endpoint (works with both token and basic auth)
766
+ const currentUserResponse = await client.api.get("/user");
767
+ const currentUserAccountId = currentUserResponse.data.account_id;
768
+ if (!currentUserAccountId) {
769
+ throw new McpError(ErrorCode.InternalError, "Could not determine current user's account_id");
770
+ }
771
+ logger.info("Getting pending review PRs", {
772
+ workspace: wsName,
773
+ account_id: currentUserAccountId,
774
+ repositoryList: repositoryList?.length || "all repositories",
775
+ limit,
776
+ });
777
+ let repositoriesToCheck = [];
778
+ if (repositoryList && repositoryList.length > 0) {
779
+ // Use the provided repository list
780
+ repositoriesToCheck = repositoryList;
781
+ logger.info(`Checking specific repositories: ${repositoryList.join(", ")}`);
782
+ }
783
+ else {
784
+ // Get all repositories in the workspace (existing behavior)
785
+ logger.info("Getting all repositories in workspace...");
786
+ const reposResponse = await client.paginator.fetchValues(`/repositories/${wsName}`, {
787
+ pagelen: BITBUCKET_MAX_PAGELEN,
788
+ all: true,
789
+ description: "getPendingReviewPRs.repositories",
790
+ });
791
+ if (!reposResponse.values) {
792
+ throw new McpError(ErrorCode.InternalError, "Failed to fetch repositories");
793
+ }
794
+ repositoriesToCheck = reposResponse.values.map((repo) => repo.name);
795
+ logger.info(`Found ${repositoriesToCheck.length} repositories to check`);
796
+ }
797
+ const pendingPRs = [];
798
+ const batchSize = 5; // Process repositories in batches to avoid overwhelming the API
799
+ // Process repositories in batches
800
+ for (let i = 0; i < repositoriesToCheck.length; i += batchSize) {
801
+ const batch = repositoriesToCheck.slice(i, i + batchSize);
802
+ // Process batch in parallel
803
+ const batchPromises = batch.map(async (repoSlug) => {
804
+ try {
805
+ logger.info(`Checking repository: ${repoSlug}`);
806
+ // Get open PRs for this repository with participants expanded
807
+ const prsResponse = await client.api.get(`/repositories/${wsName}/${repoSlug}/pullrequests`, {
808
+ params: {
809
+ state: "OPEN",
810
+ pagelen: Math.min(limit, 50), // Limit per repo to avoid too much data
811
+ fields: "values.id,values.title,values.description,values.state,values.created_on,values.updated_on,values.author,values.source,values.destination,values.participants.user.account_id,values.participants.user.nickname,values.participants.role,values.participants.approved,values.links",
812
+ },
813
+ });
814
+ if (!prsResponse.data.values) {
815
+ return [];
816
+ }
817
+ // Filter PRs where current user is a reviewer and hasn't approved
818
+ const reposPendingPRs = prsResponse.data.values.filter((pr) => {
819
+ if (!pr.participants || !Array.isArray(pr.participants)) {
820
+ logger.debug(`PR ${pr.id} has no participants array`);
821
+ return false;
822
+ }
823
+ logger.debug(`PR ${pr.id} participants:`, pr.participants.map((p) => ({
824
+ account_id: p.user?.account_id,
825
+ nickname: p.user?.nickname,
826
+ role: p.role,
827
+ approved: p.approved,
828
+ })));
829
+ // Check if current user is a reviewer who hasn't approved (using account_id)
830
+ const userParticipant = pr.participants.find((participant) => participant.user?.account_id === currentUserAccountId &&
831
+ participant.role === "REVIEWER" &&
832
+ participant.approved === false);
833
+ logger.debug(`PR ${pr.id} - User ${currentUserAccountId} is pending reviewer:`, !!userParticipant);
834
+ return !!userParticipant;
835
+ });
836
+ // Add repository info to each PR
837
+ return reposPendingPRs.map((pr) => ({
838
+ ...pr,
839
+ repository: {
840
+ name: repoSlug,
841
+ full_name: `${wsName}/${repoSlug}`,
842
+ },
843
+ }));
844
+ }
845
+ catch (error) {
846
+ logger.error(`Error checking repository ${repoSlug}:`, error);
847
+ return [];
848
+ }
849
+ });
850
+ // Wait for batch to complete
851
+ const batchResults = await Promise.all(batchPromises);
852
+ // Flatten and add to results
853
+ for (const repoPRs of batchResults) {
854
+ pendingPRs.push(...repoPRs);
855
+ // Stop if we've reached the limit
856
+ if (pendingPRs.length >= limit) {
857
+ break;
858
+ }
859
+ }
860
+ // Stop processing if we've reached the limit
861
+ if (pendingPRs.length >= limit) {
862
+ break;
863
+ }
864
+ }
865
+ // Trim to exact limit and sort by updated date
866
+ const finalResults = pendingPRs
867
+ .slice(0, limit)
868
+ .sort((a, b) => new Date(b.updated_on).getTime() -
869
+ new Date(a.updated_on).getTime());
870
+ logger.info(`Found ${finalResults.length} pending review PRs`);
871
+ return jsonResponse({
872
+ pending_review_prs: finalResults,
873
+ total_found: finalResults.length,
874
+ searched_repositories: repositoriesToCheck.length,
875
+ user_account_id: currentUserAccountId,
876
+ workspace: wsName,
877
+ });
878
+ }
879
+ catch (error) {
880
+ logger.error("Error getting pending review PRs:", error);
881
+ throw new McpError(ErrorCode.InternalError, `Failed to get pending review PRs: ${error instanceof Error ? error.message : String(error)}`);
882
+ }
883
+ },
884
+ requestChanges: async (args) => {
885
+ const workspace = client.resolveWorkspace(args.workspace);
886
+ const repo_slug = args.repo_slug;
887
+ const pull_request_id = args.pull_request_id;
888
+ logger.info(`Requesting changes on PR #${pull_request_id} in ${workspace}/${repo_slug}`);
889
+ await client.api.post(`/repositories/${workspace}/${repo_slug}/pullrequests/${pull_request_id}/request-changes`);
890
+ return jsonResponse({ success: true });
891
+ },
892
+ removeRequestChanges: async (args) => {
893
+ const workspace = client.resolveWorkspace(args.workspace);
894
+ const repo_slug = args.repo_slug;
895
+ const pull_request_id = args.pull_request_id;
896
+ logger.info(`Removing request changes from PR #${pull_request_id} in ${workspace}/${repo_slug}`);
897
+ await client.api.delete(`/repositories/${workspace}/${repo_slug}/pullrequests/${pull_request_id}/request-changes`);
898
+ return jsonResponse({ success: true });
899
+ },
900
+ }),
901
+ };
902
+ //# sourceMappingURL=pull-requests.js.map