@atlassian-dc-mcp/bitbucket 0.12.2 → 0.14.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 (44) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/README.md +45 -2
  3. package/build/__tests__/bitbucket-service.test.js +106 -17
  4. package/build/__tests__/bitbucket-service.test.js.map +1 -1
  5. package/build/__tests__/bitbucket-token-optimization.test.d.ts +2 -0
  6. package/build/__tests__/bitbucket-token-optimization.test.d.ts.map +1 -0
  7. package/build/__tests__/bitbucket-token-optimization.test.js +375 -0
  8. package/build/__tests__/bitbucket-token-optimization.test.js.map +1 -0
  9. package/build/__tests__/config.test.d.ts +2 -0
  10. package/build/__tests__/config.test.d.ts.map +1 -0
  11. package/build/__tests__/config.test.js +49 -0
  12. package/build/__tests__/config.test.js.map +1 -0
  13. package/build/__tests__/pr-comment-mapper.test.js +152 -1
  14. package/build/__tests__/pr-comment-mapper.test.js.map +1 -1
  15. package/build/bitbucket-response-mapper.d.ts +8 -0
  16. package/build/bitbucket-response-mapper.d.ts.map +1 -0
  17. package/build/bitbucket-response-mapper.js +96 -0
  18. package/build/bitbucket-response-mapper.js.map +1 -0
  19. package/build/bitbucket-service.d.ts +25 -22
  20. package/build/bitbucket-service.d.ts.map +1 -1
  21. package/build/bitbucket-service.js +89 -54
  22. package/build/bitbucket-service.js.map +1 -1
  23. package/build/config.d.ts +4 -0
  24. package/build/config.d.ts.map +1 -0
  25. package/build/config.js +11 -0
  26. package/build/config.js.map +1 -0
  27. package/build/index.js +15 -15
  28. package/build/index.js.map +1 -1
  29. package/build/pr-comment-mapper.d.ts +6 -2
  30. package/build/pr-comment-mapper.d.ts.map +1 -1
  31. package/build/pr-comment-mapper.js +47 -7
  32. package/build/pr-comment-mapper.js.map +1 -1
  33. package/package.json +3 -3
  34. package/server.json +11 -4
  35. package/src/__tests__/bitbucket-service.test.ts +109 -17
  36. package/src/__tests__/bitbucket-token-optimization.test.ts +412 -0
  37. package/src/__tests__/config.test.ts +64 -0
  38. package/src/__tests__/pr-comment-mapper.test.ts +160 -0
  39. package/src/bitbucket-response-mapper.ts +121 -0
  40. package/src/bitbucket-service.ts +119 -57
  41. package/src/config.ts +13 -0
  42. package/src/index.ts +26 -17
  43. package/src/pr-comment-mapper.ts +66 -7
  44. package/tsconfig.tsbuildinfo +1 -1
@@ -0,0 +1,121 @@
1
+ import {
2
+ BitbucketPRApiResponse,
3
+ filterPullRequestComments,
4
+ getCommentSummary,
5
+ type PullRequestCommentOptions,
6
+ simplifyBitbucketPRComments
7
+ } from './pr-comment-mapper.js';
8
+ import { getChangesSummary, simplifyBitbucketPRChanges } from './pr-changes-mapper.js';
9
+
10
+ export type BitbucketOutputMode = 'summary' | 'compact' | 'full';
11
+ export type BitbucketMutationOutputMode = 'ack' | 'full';
12
+
13
+ function getLink(links: any): string | undefined {
14
+ const selfLinks = links?.self;
15
+ if (Array.isArray(selfLinks)) {
16
+ const link = selfLinks.find(item => typeof item?.href === 'string');
17
+ return link?.href;
18
+ }
19
+
20
+ if (typeof selfLinks === 'string') {
21
+ return selfLinks;
22
+ }
23
+
24
+ return undefined;
25
+ }
26
+
27
+ function hasSummary(value: any): value is { summary: unknown; isLastPage?: boolean } {
28
+ return Boolean(value) && typeof value === 'object' && 'summary' in value;
29
+ }
30
+
31
+ export function shapePullRequestCommentsResponse(
32
+ response: BitbucketPRApiResponse,
33
+ output: BitbucketOutputMode = 'compact',
34
+ options: PullRequestCommentOptions = {}
35
+ ): Record<string, any> {
36
+ const filteredResponse = filterPullRequestComments(response, options);
37
+
38
+ if (output === 'full') {
39
+ return filteredResponse;
40
+ }
41
+
42
+ const compact = simplifyBitbucketPRComments(filteredResponse, options);
43
+ if (output === 'compact') {
44
+ return compact;
45
+ }
46
+
47
+ return {
48
+ isLastPage: hasSummary(compact) ? compact.isLastPage ?? true : filteredResponse.isLastPage ?? true,
49
+ summary: hasSummary(compact)
50
+ ? compact.summary
51
+ : {
52
+ totalActivities: Array.isArray(filteredResponse.values) ? filteredResponse.values.length : 0,
53
+ commentCount: getCommentSummary(filteredResponse, options).length,
54
+ unresolvedCount: 0,
55
+ },
56
+ items: getCommentSummary(filteredResponse, options),
57
+ };
58
+ }
59
+
60
+ export function shapePullRequestChangesResponse(response: any, output: BitbucketOutputMode = 'compact'): Record<string, any> {
61
+ if (output === 'full') {
62
+ return response;
63
+ }
64
+
65
+ const compact = simplifyBitbucketPRChanges(response);
66
+ if (output === 'compact') {
67
+ return compact;
68
+ }
69
+
70
+ return {
71
+ ...(typeof response?.fromHash === 'string' ? { fromHash: response.fromHash } : {}),
72
+ ...(typeof response?.toHash === 'string' ? { toHash: response.toHash } : {}),
73
+ ...(typeof response?.properties?.changeScope === 'string' ? { changeScope: response.properties.changeScope } : {}),
74
+ isLastPage: hasSummary(compact) ? compact.isLastPage ?? true : response?.isLastPage ?? true,
75
+ summary: hasSummary(compact)
76
+ ? compact.summary
77
+ : {
78
+ totalChanges: Array.isArray(response?.values) ? response.values.length : 0,
79
+ additions: 0,
80
+ deletions: 0,
81
+ modifications: 0,
82
+ moves: 0,
83
+ filesWithComments: 0,
84
+ },
85
+ items: getChangesSummary(response),
86
+ };
87
+ }
88
+
89
+ export function shapePullRequestAck(pullRequest: any): Record<string, any> {
90
+ const link = getLink(pullRequest?.links);
91
+ return {
92
+ ...(pullRequest?.id !== undefined ? { id: pullRequest.id } : {}),
93
+ ...(pullRequest?.version !== undefined ? { version: pullRequest.version } : {}),
94
+ ...(typeof pullRequest?.title === 'string' ? { title: pullRequest.title } : {}),
95
+ ...(typeof pullRequest?.state === 'string' ? { state: pullRequest.state } : {}),
96
+ ...(typeof pullRequest?.fromRef?.id === 'string' ? { fromRefId: pullRequest.fromRef.id } : {}),
97
+ ...(typeof pullRequest?.toRef?.id === 'string' ? { toRefId: pullRequest.toRef.id } : {}),
98
+ reviewerCount: Array.isArray(pullRequest?.reviewers) ? pullRequest.reviewers.length : 0,
99
+ ...(link ? { link } : {}),
100
+ };
101
+ }
102
+
103
+ export function shapePullRequestCommentAck(comment: any): Record<string, any> {
104
+ const link = getLink(comment?.links);
105
+ return {
106
+ ...(comment?.id !== undefined ? { id: comment.id } : {}),
107
+ ...(comment?.parent?.id !== undefined ? { parentId: comment.parent.id } : {}),
108
+ ...(typeof comment?.state === 'string' ? { state: comment.state } : {}),
109
+ pending: comment?.state === 'PENDING',
110
+ ...(typeof comment?.anchor?.path === 'string'
111
+ ? {
112
+ anchor: {
113
+ path: comment.anchor.path,
114
+ ...(comment.anchor.line !== undefined ? { line: comment.anchor.line } : {}),
115
+ ...(typeof comment.anchor.lineType === 'string' ? { lineType: comment.anchor.lineType } : {}),
116
+ },
117
+ }
118
+ : {}),
119
+ ...(link ? { link } : {}),
120
+ };
121
+ }
@@ -2,15 +2,47 @@ import { z } from 'zod';
2
2
  import { OpenAPI, ProjectService, PullRequestsService, RepositoryService } from './bitbucket-client/index.js';
3
3
  import { request as __request } from './bitbucket-client/core/request.js';
4
4
  import { handleApiOperation } from '@atlassian-dc-mcp/common';
5
- import { simplifyBitbucketPRComments } from './pr-comment-mapper.js';
6
- import { simplifyBitbucketPRChanges } from './pr-changes-mapper.js';
7
5
  import { simplifyInboxPullRequests } from './inbox-pr-mapper.js';
6
+ import { getDefaultPageSize, getMissingConfig } from './config.js';
7
+ import {
8
+ BitbucketMutationOutputMode,
9
+ BitbucketOutputMode,
10
+ shapePullRequestAck,
11
+ shapePullRequestChangesResponse,
12
+ shapePullRequestCommentAck,
13
+ shapePullRequestCommentsResponse,
14
+ } from './bitbucket-response-mapper.js';
15
+
16
+ function resolveToken(token: string | (() => string | undefined), missingTokenMessage: string) {
17
+ return async () => {
18
+ const resolvedToken = typeof token === 'function' ? token() : token;
19
+ if (!resolvedToken) {
20
+ throw new Error(missingTokenMessage);
21
+ }
22
+ return resolvedToken;
23
+ };
24
+ }
8
25
 
9
26
  export class BitbucketService {
10
- constructor(host: string, token: string, fullBaseUrl?: string) {
11
- OpenAPI.BASE = fullBaseUrl ?? `https://${host}/rest`;
12
- OpenAPI.TOKEN = token;
27
+ private readonly getPageSize: () => number;
28
+
29
+ constructor(
30
+ host: string | undefined,
31
+ token: string | (() => string | undefined),
32
+ fullBaseUrl?: string,
33
+ getPageSize: () => number = getDefaultPageSize,
34
+ ) {
35
+ if (fullBaseUrl) {
36
+ OpenAPI.BASE = fullBaseUrl;
37
+ } else if (host) {
38
+ OpenAPI.BASE = `https://${host}/rest`;
39
+ } else {
40
+ throw new Error('Either host or fullBaseUrl must be provided');
41
+ }
42
+
43
+ OpenAPI.TOKEN = resolveToken(token, 'Missing required environment variable: BITBUCKET_API_TOKEN');
13
44
  OpenAPI.VERSION = '1.0';
45
+ this.getPageSize = getPageSize;
14
46
  }
15
47
 
16
48
  /**
@@ -24,7 +56,7 @@ export class BitbucketService {
24
56
  * @returns Promise with commits data
25
57
  */
26
58
  async getCommits(projectKey: string, repositorySlug: string, path?: string, since?: string, until?: string,
27
- limit: number = 25
59
+ limit?: number
28
60
  ) {
29
61
  return handleApiOperation(
30
62
  () => RepositoryService.getCommits(
@@ -40,7 +72,7 @@ export class BitbucketService {
40
72
  undefined, // merges
41
73
  undefined, // ignoreMissing
42
74
  0, // start
43
- limit
75
+ limit ?? this.getPageSize()
44
76
  ),
45
77
  'Error fetching commits'
46
78
  );
@@ -54,9 +86,9 @@ export class BitbucketService {
54
86
  * @param limit Optional pagination limit (default: 25)
55
87
  * @returns Promise with projects data
56
88
  */
57
- async getProjects(name?: string, permission?: string, start?: number, limit: number = 25) {
89
+ async getProjects(name?: string, permission?: string, start?: number, limit?: number) {
58
90
  return handleApiOperation(
59
- () => ProjectService.getProjects(name, permission, start, limit),
91
+ () => ProjectService.getProjects(name, permission, start, limit ?? this.getPageSize()),
60
92
  'Error fetching projects'
61
93
  );
62
94
  }
@@ -80,9 +112,9 @@ export class BitbucketService {
80
112
  * @param limit Optional pagination limit (default: 25)
81
113
  * @returns Promise with repositories data
82
114
  */
83
- async getRepositories(projectKey: string, start?: number, limit: number = 25) {
115
+ async getRepositories(projectKey: string, start?: number, limit?: number) {
84
116
  return handleApiOperation(
85
- () => ProjectService.getRepositories(projectKey, start, limit),
117
+ () => ProjectService.getRepositories(projectKey, start, limit ?? this.getPageSize()),
86
118
  'Error fetching repositories'
87
119
  );
88
120
  }
@@ -128,7 +160,7 @@ export class BitbucketService {
128
160
  order?: string,
129
161
  direction?: string,
130
162
  start?: number,
131
- limit: number = 25
163
+ limit?: number
132
164
  ) {
133
165
  return handleApiOperation(
134
166
  () => PullRequestsService.getPage(
@@ -143,7 +175,7 @@ export class BitbucketService {
143
175
  order,
144
176
  direction,
145
177
  start,
146
- limit
178
+ limit ?? this.getPageSize()
147
179
  ),
148
180
  'Error fetching pull requests'
149
181
  );
@@ -167,8 +199,14 @@ export class BitbucketService {
167
199
  );
168
200
  }
169
201
 
170
- async getPullRequestCommentsAndActions(projectKey: string, repositorySlug: string, pullRequestId: string, start?: number,
171
- limit: number = 25
202
+ async getPullRequestCommentsAndActions(
203
+ projectKey: string,
204
+ repositorySlug: string,
205
+ pullRequestId: string,
206
+ start?: number,
207
+ limit?: number,
208
+ output: BitbucketOutputMode = 'compact',
209
+ includeResolved = false
172
210
  ) {
173
211
  const result = await handleApiOperation(
174
212
  () => PullRequestsService.getActivities(
@@ -178,17 +216,15 @@ export class BitbucketService {
178
216
  undefined,
179
217
  undefined,
180
218
  start,
181
- limit
219
+ limit ?? this.getPageSize()
182
220
  ),
183
221
  'Error fetching pull request comments'
184
222
  );
185
223
 
186
- // Apply simplification if the API call was successful
187
224
  if (result.success && result.data) {
188
- const simplifiedData = simplifyBitbucketPRComments(result.data);
189
225
  return {
190
226
  success: true,
191
- data: simplifiedData
227
+ data: shapePullRequestCommentsResponse(result.data, output, { includeResolved })
192
228
  };
193
229
  }
194
230
 
@@ -217,7 +253,8 @@ export class BitbucketService {
217
253
  untilId?: string,
218
254
  withComments?: string,
219
255
  start?: number,
220
- limit: number = 25
256
+ limit?: number,
257
+ output: BitbucketOutputMode = 'compact'
221
258
  ) {
222
259
  const result = await handleApiOperation(
223
260
  () => PullRequestsService.streamChanges1(
@@ -229,17 +266,15 @@ export class BitbucketService {
229
266
  untilId,
230
267
  withComments,
231
268
  start,
232
- limit
269
+ limit ?? this.getPageSize()
233
270
  ),
234
271
  'Error fetching pull request changes'
235
272
  );
236
273
 
237
- // Apply simplification if the API call was successful
238
274
  if (result.success && result.data) {
239
- const simplifiedData = simplifyBitbucketPRChanges(result.data);
240
275
  return {
241
276
  ...result,
242
- data: simplifiedData
277
+ data: shapePullRequestChangesResponse(result.data, output)
243
278
  };
244
279
  }
245
280
 
@@ -270,7 +305,8 @@ export class BitbucketService {
270
305
  filePath?: string,
271
306
  line?: number,
272
307
  lineType?: 'ADDED' | 'REMOVED' | 'CONTEXT',
273
- pending?: boolean
308
+ pending?: boolean,
309
+ output: BitbucketMutationOutputMode = 'ack'
274
310
  ) {
275
311
  const comment: any = {
276
312
  text
@@ -302,7 +338,7 @@ export class BitbucketService {
302
338
  }
303
339
  }
304
340
 
305
- return handleApiOperation(
341
+ const result = await handleApiOperation(
306
342
  () => PullRequestsService.createComment2(
307
343
  projectKey,
308
344
  pullRequestId,
@@ -311,6 +347,15 @@ export class BitbucketService {
311
347
  ),
312
348
  'Error posting pull request comment'
313
349
  );
350
+
351
+ if (result.success && result.data && output !== 'full') {
352
+ return {
353
+ ...result,
354
+ data: shapePullRequestCommentAck(result.data),
355
+ };
356
+ }
357
+
358
+ return result;
314
359
  }
315
360
 
316
361
  /**
@@ -452,7 +497,8 @@ export class BitbucketService {
452
497
  description: string | undefined,
453
498
  fromRefId: string,
454
499
  toRefId: string,
455
- reviewers?: string[]
500
+ reviewers?: string[],
501
+ output: BitbucketMutationOutputMode = 'ack'
456
502
  ) {
457
503
  const pullRequestData: any = {
458
504
  title,
@@ -485,10 +531,19 @@ export class BitbucketService {
485
531
  }));
486
532
  }
487
533
 
488
- return handleApiOperation(
534
+ const result = await handleApiOperation(
489
535
  () => PullRequestsService.create(projectKey, repositorySlug, pullRequestData),
490
536
  'Error creating pull request'
491
537
  );
538
+
539
+ if (result.success && result.data && output !== 'full') {
540
+ return {
541
+ ...result,
542
+ data: shapePullRequestAck(result.data),
543
+ };
544
+ }
545
+
546
+ return result;
492
547
  }
493
548
 
494
549
  /**
@@ -509,7 +564,8 @@ export class BitbucketService {
509
564
  version: number,
510
565
  title?: string,
511
566
  description?: string,
512
- reviewers?: string[]
567
+ reviewers?: string[],
568
+ output: BitbucketMutationOutputMode = 'ack'
513
569
  ) {
514
570
  const pullRequestData: any = {
515
571
  version
@@ -531,10 +587,19 @@ export class BitbucketService {
531
587
  }));
532
588
  }
533
589
 
534
- return handleApiOperation(
590
+ const result = await handleApiOperation(
535
591
  () => PullRequestsService.update(projectKey, pullRequestId, repositorySlug, pullRequestData),
536
592
  'Error updating pull request'
537
593
  );
594
+
595
+ if (result.success && result.data && output !== 'full') {
596
+ return {
597
+ ...result,
598
+ data: shapePullRequestAck(result.data),
599
+ };
600
+ }
601
+
602
+ return result;
538
603
  }
539
604
 
540
605
  /**
@@ -577,7 +642,7 @@ export class BitbucketService {
577
642
  * @param closedSince Optional timestamp (in milliseconds) to filter PRs closed after this date
578
643
  * @param order Order: NEWEST (default), OLDEST, or PARTICIPANT
579
644
  * @param start Optional pagination start
580
- * @param limit Pagination limit (default: 10)
645
+ * @param limit Pagination limit (defaults to the package page size)
581
646
  * @returns Promise with dashboard pull requests data
582
647
  */
583
648
  async getDashboardPullRequests(
@@ -586,7 +651,7 @@ export class BitbucketService {
586
651
  closedSince?: number,
587
652
  order: string = 'NEWEST',
588
653
  start?: number,
589
- limit: number = 10
654
+ limit?: number
590
655
  ) {
591
656
  return handleApiOperation(
592
657
  () => __request(OpenAPI, {
@@ -598,7 +663,7 @@ export class BitbucketService {
598
663
  'closedSince': closedSince,
599
664
  'order': order,
600
665
  'start': start,
601
- 'limit': limit,
666
+ 'limit': limit ?? this.getPageSize(),
602
667
  },
603
668
  errors: {
604
669
  401: 'The currently authenticated user is not permitted to access the dashboard.',
@@ -611,17 +676,17 @@ export class BitbucketService {
611
676
  /**
612
677
  * Get pull requests from the authenticated user's inbox (PRs awaiting review)
613
678
  * @param start Optional pagination start
614
- * @param limit Optional pagination limit (default: 25)
679
+ * @param limit Optional pagination limit (defaults to the package page size)
615
680
  * @returns Promise with inbox pull requests data
616
681
  */
617
- async getInboxPullRequests(start?: number, limit: number = 25) {
682
+ async getInboxPullRequests(start?: number, limit?: number) {
618
683
  const result = await handleApiOperation(
619
684
  () => __request(OpenAPI, {
620
685
  method: 'GET',
621
686
  url: '/api/latest/inbox/pull-requests',
622
687
  query: {
623
688
  'start': start,
624
- 'limit': limit,
689
+ 'limit': limit ?? this.getPageSize(),
625
690
  },
626
691
  errors: {
627
692
  401: 'The currently authenticated user is not permitted to access the inbox.',
@@ -641,16 +706,7 @@ export class BitbucketService {
641
706
  }
642
707
 
643
708
  static validateConfig(): string[] {
644
- // Check for BITBUCKET_HOST or its alternative BITBUCKET_API_BASE_PATH
645
- const requiredEnvVars = ['BITBUCKET_API_TOKEN'] as const;
646
- const missingVars: string[] = requiredEnvVars.filter(varName => !process.env[varName]);
647
-
648
- // Special handling for BITBUCKET_HOST with BITBUCKET_API_BASE_PATH as an alternative
649
- if (!process.env.BITBUCKET_HOST && !process.env.BITBUCKET_API_BASE_PATH) {
650
- missingVars.push('BITBUCKET_HOST or BITBUCKET_API_BASE_PATH');
651
- }
652
-
653
- return missingVars;
709
+ return getMissingConfig();
654
710
  }
655
711
  }
656
712
 
@@ -659,7 +715,7 @@ export const bitbucketToolSchemas = {
659
715
  name: z.string().optional().describe("Filter projects by name"),
660
716
  permission: z.string().optional().describe("Filter projects by permission"),
661
717
  start: z.number().optional().describe("Start number for pagination"),
662
- limit: z.number().optional().default(25).describe("Number of items to return")
718
+ limit: z.number().optional().describe("Number of items to return. If not passed, the package default page size is used.")
663
719
  },
664
720
  getPullRequests: {
665
721
  projectKey: z.string().describe("The project key"),
@@ -673,7 +729,7 @@ export const bitbucketToolSchemas = {
673
729
  order: z.string().optional().describe("(optional, defaults to NEWEST) the order to return pull requests in, either OLDEST (as in: \"oldest first\") or NEWEST"),
674
730
  direction: z.string().optional().describe("(optional, defaults to INCOMING) the direction relative to the specified repository. Either INCOMING or OUTGOING"),
675
731
  start: z.number().optional().describe("Start number for the page (inclusive). If not passed, first page is assumed"),
676
- limit: z.number().optional().default(25).describe("Number of items to return. If not passed, a page size of 25 is used")
732
+ limit: z.number().optional().describe("Number of items to return. If not passed, the package default page size is used.")
677
733
  },
678
734
  getPullRequest: {
679
735
  projectKey: z.string().describe("The project key"),
@@ -686,7 +742,7 @@ export const bitbucketToolSchemas = {
686
742
  getRepositories: {
687
743
  projectKey: z.string().describe("The project key"),
688
744
  start: z.number().optional().describe("Start number for pagination"),
689
- limit: z.number().optional().default(25).describe("Number of items to return")
745
+ limit: z.number().optional().describe("Number of items to return. If not passed, the package default page size is used.")
690
746
  },
691
747
  getRepository: {
692
748
  projectKey: z.string().describe("The project key"),
@@ -698,14 +754,16 @@ export const bitbucketToolSchemas = {
698
754
  path: z.string().optional().describe("Optional path to filter commits by"),
699
755
  since: z.string().optional().describe("The commit ID (exclusively) to retrieve commits after"),
700
756
  until: z.string().optional().describe("The commit ID (inclusively) to retrieve commits before"),
701
- limit: z.number().optional().default(25).describe("Number of items to return")
757
+ limit: z.number().optional().describe("Number of items to return. If not passed, the package default page size is used.")
702
758
  },
703
759
  getPullRequestComments: {
704
760
  projectKey: z.string().describe("The project key"),
705
761
  repositorySlug: z.string().describe("The repository slug"),
706
762
  pullRequestId: z.string().describe("The pull request ID"),
707
763
  start: z.number().optional().describe("Start number for pagination"),
708
- limit: z.number().optional().default(25).describe("Number of items to return")
764
+ limit: z.number().optional().describe("Number of items to return. If not passed, the package default page size is used."),
765
+ output: z.enum(['summary', 'compact', 'full']).optional().describe("Choose between summary lines, compact structured output, or the full API payload. Defaults to compact."),
766
+ includeResolved: z.boolean().optional().describe("Include resolved comment threads and their replies. Defaults to false, so resolved threads are omitted.")
709
767
  },
710
768
  getPullRequestChanges: {
711
769
  projectKey: z.string().describe("The project key"),
@@ -716,7 +774,8 @@ export const bitbucketToolSchemas = {
716
774
  untilId: z.string().optional().describe("The until commit hash to stream changes for a RANGE arbitrary change scope"),
717
775
  withComments: z.string().optional().describe("true to apply comment counts in the changes (default), false to stream changes without comment counts"),
718
776
  start: z.number().optional().describe("Start number for pagination"),
719
- limit: z.number().optional().default(25).describe("Number of items to return")
777
+ limit: z.number().optional().describe("Number of items to return. If not passed, the package default page size is used."),
778
+ output: z.enum(['summary', 'compact', 'full']).optional().describe("Choose between summary lines, compact structured output, or the full API payload. Defaults to compact.")
720
779
  },
721
780
  postPullRequestComment: {
722
781
  projectKey: z.string().describe("The project key"),
@@ -727,7 +786,8 @@ export const bitbucketToolSchemas = {
727
786
  filePath: z.string().optional().describe("File path for file-specific comments"),
728
787
  line: z.number().optional().describe("Line number for line-specific comments"),
729
788
  lineType: z.enum(['ADDED', 'REMOVED', 'CONTEXT']).optional().describe("Line type for line comments"),
730
- pending: z.boolean().optional().describe("If true, creates a pending (draft) comment not visible to others until the review is submitted via bitbucket_submitPullRequestReview. Only works when filePath is provided — top-level PR comments (no filePath) are always posted live.")
789
+ pending: z.boolean().optional().describe("If true, creates a pending (draft) comment not visible to others until the review is submitted via bitbucket_submitPullRequestReview. Only works when filePath is provided — top-level PR comments (no filePath) are always posted live."),
790
+ output: z.enum(['ack', 'full']).optional().describe("Return a compact acknowledgement or the full API response. Defaults to ack.")
731
791
  },
732
792
  getUser: {
733
793
  userSlug: z.string().optional().describe("Exact slug of the user to look up (e.g. 'tdepole'). Use this to confirm a known slug or fetch a user's details."),
@@ -760,7 +820,8 @@ export const bitbucketToolSchemas = {
760
820
  description: z.string().optional().describe("The pull request description"),
761
821
  fromRefId: z.string().describe("The source branch reference ID (e.g., 'refs/heads/feature-branch')"),
762
822
  toRefId: z.string().describe("The destination branch reference ID (e.g., 'refs/heads/main')"),
763
- reviewers: z.array(z.string()).optional().describe("Optional array of reviewer usernames")
823
+ reviewers: z.array(z.string()).optional().describe("Optional array of reviewer usernames"),
824
+ output: z.enum(['ack', 'full']).optional().describe("Return a compact acknowledgement or the full API response. Defaults to ack.")
764
825
  },
765
826
  updatePullRequest: {
766
827
  projectKey: z.string().describe("The project key"),
@@ -769,7 +830,8 @@ export const bitbucketToolSchemas = {
769
830
  version: z.number().describe("The current version of the pull request (required for optimistic locking)"),
770
831
  title: z.string().optional().describe("The new title for the pull request"),
771
832
  description: z.string().optional().describe("The new description for the pull request"),
772
- reviewers: z.array(z.string()).optional().describe("Optional array of reviewer usernames to set")
833
+ reviewers: z.array(z.string()).optional().describe("Optional array of reviewer usernames to set"),
834
+ output: z.enum(['ack', 'full']).optional().describe("Return a compact acknowledgement or the full API response. Defaults to ack.")
773
835
  },
774
836
  getRequiredReviewers: {
775
837
  projectKey: z.string().describe("The project key"),
@@ -785,10 +847,10 @@ export const bitbucketToolSchemas = {
785
847
  closedSince: z.number().optional().describe("Timestamp in milliseconds. If state is not OPEN, return only PRs closed after this date"),
786
848
  order: z.enum(['NEWEST', 'OLDEST', 'PARTICIPANT']).optional().default('NEWEST').describe("Order of results: NEWEST (default), OLDEST, or PARTICIPANT"),
787
849
  start: z.number().optional().describe("Start number for pagination"),
788
- limit: z.number().optional().default(10).describe("Number of items to return (default: 10)")
850
+ limit: z.number().optional().describe("Number of items to return. If not passed, the package default page size is used.")
789
851
  },
790
852
  getInboxPullRequests: {
791
853
  start: z.number().optional().describe("Start number for the page (inclusive). If not passed, first page is assumed"),
792
- limit: z.number().optional().default(25).describe("Number of items to return. If not passed, a page size of 25 is used")
854
+ limit: z.number().optional().describe("Number of items to return. If not passed, the package default page size is used.")
793
855
  }
794
856
  };
package/src/config.ts ADDED
@@ -0,0 +1,13 @@
1
+ import { getProductRuntimeConfig, validateProductRuntimeConfig } from '@atlassian-dc-mcp/common';
2
+
3
+ export function getBitbucketRuntimeConfig() {
4
+ return getProductRuntimeConfig('bitbucket');
5
+ }
6
+
7
+ export function getDefaultPageSize() {
8
+ return getBitbucketRuntimeConfig().defaultPageSize;
9
+ }
10
+
11
+ export function getMissingConfig() {
12
+ return validateProductRuntimeConfig('bitbucket');
13
+ }
package/src/index.ts CHANGED
@@ -1,9 +1,8 @@
1
- import { connectServer, createMcpServer, formatToolResponse } from '@atlassian-dc-mcp/common';
2
- import dotenv from 'dotenv';
1
+ import { connectServer, createMcpServer, formatToolResponse, initializeRuntimeConfig } from '@atlassian-dc-mcp/common';
3
2
  import { BitbucketService, bitbucketToolSchemas } from './bitbucket-service.js';
3
+ import { getBitbucketRuntimeConfig, getDefaultPageSize } from './config.js';
4
4
 
5
- // Load environment variables
6
- dotenv.config();
5
+ initializeRuntimeConfig();
7
6
 
8
7
  const missingVars = BitbucketService.validateConfig();
9
8
  if (missingVars.length > 0) {
@@ -11,10 +10,12 @@ if (missingVars.length > 0) {
11
10
  process.exit(1);
12
11
  }
13
12
 
13
+ const bitbucketConfig = getBitbucketRuntimeConfig();
14
14
  const bitbucketService = new BitbucketService(
15
- process.env.BITBUCKET_HOST!,
16
- process.env.BITBUCKET_API_TOKEN!,
17
- process.env.BITBUCKET_API_BASE_PATH,
15
+ bitbucketConfig.host,
16
+ () => getBitbucketRuntimeConfig().token,
17
+ bitbucketConfig.apiBasePath,
18
+ getDefaultPageSize,
18
19
  );
19
20
 
20
21
  const server = createMcpServer({
@@ -96,8 +97,16 @@ server.tool(
96
97
  "bitbucket_getPR_CommentsAndAction",
97
98
  "Get comments for a Bitbucket pull request and other actions, like approvals",
98
99
  bitbucketToolSchemas.getPullRequestComments,
99
- async ({ projectKey, repositorySlug, pullRequestId, start, limit }) => {
100
- const result = await bitbucketService.getPullRequestCommentsAndActions(projectKey, repositorySlug, pullRequestId, start, limit);
100
+ async ({ projectKey, repositorySlug, pullRequestId, start, limit, output, includeResolved }) => {
101
+ const result = await bitbucketService.getPullRequestCommentsAndActions(
102
+ projectKey,
103
+ repositorySlug,
104
+ pullRequestId,
105
+ start,
106
+ limit,
107
+ output,
108
+ includeResolved
109
+ );
101
110
  return formatToolResponse(result);
102
111
  }
103
112
  );
@@ -106,8 +115,8 @@ server.tool(
106
115
  "bitbucket_getPullRequestChanges",
107
116
  "Get the changes for a Bitbucket pull request",
108
117
  bitbucketToolSchemas.getPullRequestChanges,
109
- async ({ projectKey, repositorySlug, pullRequestId, sinceId, changeScope, untilId, withComments, start, limit }) => {
110
- const result = await bitbucketService.getPullRequestChanges(projectKey, repositorySlug, pullRequestId, sinceId, changeScope, untilId, withComments, start, limit);
118
+ async ({ projectKey, repositorySlug, pullRequestId, sinceId, changeScope, untilId, withComments, start, limit, output }) => {
119
+ const result = await bitbucketService.getPullRequestChanges(projectKey, repositorySlug, pullRequestId, sinceId, changeScope, untilId, withComments, start, limit, output);
111
120
  return formatToolResponse(result);
112
121
  }
113
122
  );
@@ -126,8 +135,8 @@ server.tool(
126
135
  "bitbucket_postPullRequestComment",
127
136
  "Post a comment to a Bitbucket pull request. Use pending: true to create a draft comment that is only visible to you until you call bitbucket_submitPullRequestReview. NOTE: pending only works when filePath is provided (file-level or inline comments). True top-level PR comments (no filePath) are always posted live and cannot be drafted.",
128
137
  bitbucketToolSchemas.postPullRequestComment,
129
- async ({ projectKey, repositorySlug, pullRequestId, text, parentId, filePath, line, lineType, pending }) => {
130
- const result = await bitbucketService.postPullRequestComment(projectKey, repositorySlug, pullRequestId, text, parentId, filePath, line, lineType, pending);
138
+ async ({ projectKey, repositorySlug, pullRequestId, text, parentId, filePath, line, lineType, pending, output }) => {
139
+ const result = await bitbucketService.postPullRequestComment(projectKey, repositorySlug, pullRequestId, text, parentId, filePath, line, lineType, pending, output);
131
140
  return formatToolResponse(result);
132
141
  }
133
142
  );
@@ -157,8 +166,8 @@ server.tool(
157
166
  "bitbucket_createPullRequest",
158
167
  "Create a new pull request in a Bitbucket repository. IMPORTANT: Before creating a PR, use bitbucket_getRequiredReviewers to fetch required reviewers for the source and target branches to ensure the PR is not created without mandatory reviewers.",
159
168
  bitbucketToolSchemas.createPullRequest,
160
- async ({ projectKey, repositorySlug, title, description, fromRefId, toRefId, reviewers }) => {
161
- const result = await bitbucketService.createPullRequest(projectKey, repositorySlug, title, description, fromRefId, toRefId, reviewers);
169
+ async ({ projectKey, repositorySlug, title, description, fromRefId, toRefId, reviewers, output }) => {
170
+ const result = await bitbucketService.createPullRequest(projectKey, repositorySlug, title, description, fromRefId, toRefId, reviewers, output);
162
171
  return formatToolResponse(result);
163
172
  }
164
173
  );
@@ -167,8 +176,8 @@ server.tool(
167
176
  "bitbucket_updatePullRequest",
168
177
  "Update the title, description, reviewers, destination branch or draft status of an existing pull request. IMPORTANT: The reviewers parameter replaces ALL existing reviewers. If you want to preserve existing reviewers, first fetch the current PR details (using bitbucket_getPullRequests filtered by ID) and include those reviewers along with any new ones you want to add.",
169
178
  bitbucketToolSchemas.updatePullRequest,
170
- async ({ projectKey, repositorySlug, pullRequestId, version, title, description, reviewers }) => {
171
- const result = await bitbucketService.updatePullRequest(projectKey, repositorySlug, pullRequestId, version, title, description, reviewers);
179
+ async ({ projectKey, repositorySlug, pullRequestId, version, title, description, reviewers, output }) => {
180
+ const result = await bitbucketService.updatePullRequest(projectKey, repositorySlug, pullRequestId, version, title, description, reviewers, output);
172
181
  return formatToolResponse(result);
173
182
  }
174
183
  );