@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.
- package/CHANGELOG.md +23 -0
- package/README.md +45 -2
- package/build/__tests__/bitbucket-service.test.js +106 -17
- package/build/__tests__/bitbucket-service.test.js.map +1 -1
- package/build/__tests__/bitbucket-token-optimization.test.d.ts +2 -0
- package/build/__tests__/bitbucket-token-optimization.test.d.ts.map +1 -0
- package/build/__tests__/bitbucket-token-optimization.test.js +375 -0
- package/build/__tests__/bitbucket-token-optimization.test.js.map +1 -0
- package/build/__tests__/config.test.d.ts +2 -0
- package/build/__tests__/config.test.d.ts.map +1 -0
- package/build/__tests__/config.test.js +49 -0
- package/build/__tests__/config.test.js.map +1 -0
- package/build/__tests__/pr-comment-mapper.test.js +152 -1
- package/build/__tests__/pr-comment-mapper.test.js.map +1 -1
- package/build/bitbucket-response-mapper.d.ts +8 -0
- package/build/bitbucket-response-mapper.d.ts.map +1 -0
- package/build/bitbucket-response-mapper.js +96 -0
- package/build/bitbucket-response-mapper.js.map +1 -0
- package/build/bitbucket-service.d.ts +25 -22
- package/build/bitbucket-service.d.ts.map +1 -1
- package/build/bitbucket-service.js +89 -54
- package/build/bitbucket-service.js.map +1 -1
- package/build/config.d.ts +4 -0
- package/build/config.d.ts.map +1 -0
- package/build/config.js +11 -0
- package/build/config.js.map +1 -0
- package/build/index.js +15 -15
- package/build/index.js.map +1 -1
- package/build/pr-comment-mapper.d.ts +6 -2
- package/build/pr-comment-mapper.d.ts.map +1 -1
- package/build/pr-comment-mapper.js +47 -7
- package/build/pr-comment-mapper.js.map +1 -1
- package/package.json +3 -3
- package/server.json +11 -4
- package/src/__tests__/bitbucket-service.test.ts +109 -17
- package/src/__tests__/bitbucket-token-optimization.test.ts +412 -0
- package/src/__tests__/config.test.ts +64 -0
- package/src/__tests__/pr-comment-mapper.test.ts +160 -0
- package/src/bitbucket-response-mapper.ts +121 -0
- package/src/bitbucket-service.ts +119 -57
- package/src/config.ts +13 -0
- package/src/index.ts +26 -17
- package/src/pr-comment-mapper.ts +66 -7
- 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
|
+
}
|
package/src/bitbucket-service.ts
CHANGED
|
@@ -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
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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(
|
|
171
|
-
|
|
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:
|
|
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
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
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
|
|
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 (
|
|
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
|
|
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
|
-
|
|
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().
|
|
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().
|
|
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().
|
|
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().
|
|
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().
|
|
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().
|
|
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().
|
|
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().
|
|
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
|
-
|
|
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
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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(
|
|
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
|
);
|