@atlassian-dc-mcp/bitbucket 0.6.0 → 0.7.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 (37) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/build/__tests__/bitbucket-service.test.d.ts +2 -0
  3. package/build/__tests__/bitbucket-service.test.d.ts.map +1 -0
  4. package/build/__tests__/bitbucket-service.test.js +269 -0
  5. package/build/__tests__/bitbucket-service.test.js.map +1 -0
  6. package/build/__tests__/pr-changes-mapper.test.d.ts +2 -0
  7. package/build/__tests__/pr-changes-mapper.test.d.ts.map +1 -0
  8. package/build/__tests__/pr-changes-mapper.test.js +396 -0
  9. package/build/__tests__/pr-changes-mapper.test.js.map +1 -0
  10. package/build/__tests__/pr-comment-mapper.test.d.ts +2 -0
  11. package/build/__tests__/pr-comment-mapper.test.d.ts.map +1 -0
  12. package/build/__tests__/pr-comment-mapper.test.js +210 -0
  13. package/build/__tests__/pr-comment-mapper.test.js.map +1 -0
  14. package/build/bitbucket-service.d.ts +84 -1
  15. package/build/bitbucket-service.d.ts.map +1 -1
  16. package/build/bitbucket-service.js +148 -1
  17. package/build/bitbucket-service.js.map +1 -1
  18. package/build/index.js +13 -1
  19. package/build/index.js.map +1 -1
  20. package/build/pr-changes-mapper.d.ts +32 -0
  21. package/build/pr-changes-mapper.d.ts.map +1 -0
  22. package/build/pr-changes-mapper.js +111 -0
  23. package/build/pr-changes-mapper.js.map +1 -0
  24. package/build/pr-comment-mapper.d.ts +48 -0
  25. package/build/pr-comment-mapper.d.ts.map +1 -0
  26. package/build/pr-comment-mapper.js +128 -0
  27. package/build/pr-comment-mapper.js.map +1 -0
  28. package/jest.config.js +23 -0
  29. package/package.json +5 -3
  30. package/src/__tests__/bitbucket-service.test.ts +419 -0
  31. package/src/__tests__/pr-changes-mapper.test.ts +420 -0
  32. package/src/__tests__/pr-comment-mapper.test.ts +230 -0
  33. package/src/bitbucket-service.ts +215 -2
  34. package/src/index.ts +33 -2
  35. package/src/pr-changes-mapper.ts +216 -0
  36. package/src/pr-comment-mapper.ts +318 -0
  37. package/tsconfig.tsbuildinfo +1 -1
@@ -1,6 +1,9 @@
1
1
  import { z } from 'zod';
2
2
  import { OpenAPI, ProjectService, PullRequestsService, RepositoryService } from './bitbucket-client/index.js';
3
+ import { request as __request } from './bitbucket-client/core/request.js';
3
4
  import { handleApiOperation } from '@atlassian-dc-mcp/common';
5
+ import { simplifyBitbucketPRComments } from './pr-comment-mapper.js';
6
+ import { simplifyBitbucketPRChanges } from './pr-changes-mapper.js';
4
7
 
5
8
  export class BitbucketService {
6
9
  constructor(host: string, token: string, fullBaseUrl?: string) {
@@ -99,7 +102,7 @@ export class BitbucketService {
99
102
  async getPullRequestCommentsAndActions(projectKey: string, repositorySlug: string, pullRequestId: string, start?: number,
100
103
  limit: number = 25
101
104
  ) {
102
- return handleApiOperation(
105
+ const result = await handleApiOperation(
103
106
  () => PullRequestsService.getActivities(
104
107
  projectKey,
105
108
  pullRequestId,
@@ -110,7 +113,184 @@ export class BitbucketService {
110
113
  limit
111
114
  ),
112
115
  'Error fetching pull request comments'
113
- )
116
+ );
117
+
118
+ // Apply simplification if the API call was successful
119
+ if (result.success && result.data) {
120
+ const simplifiedData = simplifyBitbucketPRComments(result.data);
121
+ return {
122
+ success: true,
123
+ data: simplifiedData
124
+ };
125
+ }
126
+
127
+ return result;
128
+ }
129
+
130
+ /**
131
+ * Get pull request changes
132
+ * @param projectKey The project key
133
+ * @param repositorySlug The repository slug
134
+ * @param pullRequestId The pull request ID
135
+ * @param sinceId Optional since commit hash to stream changes for a RANGE arbitrary change scope
136
+ * @param changeScope Optional scope: 'UNREVIEWED' for unreviewed changes, 'RANGE' for changes between commits, 'ALL' for all changes (default)
137
+ * @param untilId Optional until commit hash to stream changes for a RANGE arbitrary change scope
138
+ * @param withComments Optional flag to include comment counts (default: true)
139
+ * @param start Optional pagination start
140
+ * @param limit Optional pagination limit (default: 25)
141
+ * @returns Promise with PR changes data
142
+ */
143
+ async getPullRequestChanges(
144
+ projectKey: string,
145
+ repositorySlug: string,
146
+ pullRequestId: string,
147
+ sinceId?: string,
148
+ changeScope?: string,
149
+ untilId?: string,
150
+ withComments?: string,
151
+ start?: number,
152
+ limit: number = 25
153
+ ) {
154
+ const result = await handleApiOperation(
155
+ () => PullRequestsService.streamChanges1(
156
+ projectKey,
157
+ pullRequestId,
158
+ repositorySlug,
159
+ sinceId,
160
+ changeScope,
161
+ untilId,
162
+ withComments,
163
+ start,
164
+ limit
165
+ ),
166
+ 'Error fetching pull request changes'
167
+ );
168
+
169
+ // Apply simplification if the API call was successful
170
+ if (result.success && result.data) {
171
+ const simplifiedData = simplifyBitbucketPRChanges(result.data);
172
+ return {
173
+ ...result,
174
+ data: simplifiedData
175
+ };
176
+ }
177
+
178
+ return result;
179
+ }
180
+
181
+ /**
182
+ * Post a comment to a pull request
183
+ * @param projectKey The project key
184
+ * @param repositorySlug The repository slug
185
+ * @param pullRequestId The pull request ID
186
+ * @param text The comment text
187
+ * @param parentId Optional parent comment ID for replies
188
+ * @param filePath Optional file path for file-specific comments
189
+ * @param line Optional line number for line-specific comments
190
+ * @param lineType Optional line type ('ADDED', 'REMOVED', 'CONTEXT') for line comments
191
+ * @returns Promise with created comment data
192
+ */
193
+ async postPullRequestComment(
194
+ projectKey: string,
195
+ repositorySlug: string,
196
+ pullRequestId: string,
197
+ text: string,
198
+ parentId?: number,
199
+ filePath?: string,
200
+ line?: number,
201
+ lineType?: 'ADDED' | 'REMOVED' | 'CONTEXT'
202
+ ) {
203
+ const comment: any = {
204
+ text
205
+ };
206
+
207
+ // Add parent reference for replies
208
+ if (parentId) {
209
+ comment.parent = { id: parentId };
210
+ }
211
+
212
+ // Add anchor for file/line comments
213
+ if (filePath) {
214
+ comment.anchor = {
215
+ path: filePath,
216
+ diffType: 'EFFECTIVE'
217
+ };
218
+
219
+ // Add line-specific anchor properties
220
+ if (line !== undefined && lineType) {
221
+ comment.anchor.line = line;
222
+ comment.anchor.lineType = lineType;
223
+ comment.anchor.fileType = 'TO'; // Default to destination file
224
+ }
225
+ }
226
+
227
+ return handleApiOperation(
228
+ () => PullRequestsService.createComment2(
229
+ projectKey,
230
+ pullRequestId,
231
+ repositorySlug,
232
+ comment
233
+ ),
234
+ 'Error posting pull request comment'
235
+ );
236
+ }
237
+
238
+
239
+ /**
240
+ * Get text diff for a specific file in a pull request
241
+ * @param projectKey The project key
242
+ * @param repositorySlug The repository slug
243
+ * @param pullRequestId The pull request ID
244
+ * @param path The path to the file which should be diffed
245
+ * @param contextLines Optional number of context lines to include around added/removed lines
246
+ * @param sinceId Optional since commit hash to stream a diff between two arbitrary hashes
247
+ * @param srcPath Optional previous path to the file, if the file has been copied, moved or renamed
248
+ * @param diffType Optional type of diff being requested
249
+ * @param untilId Optional until commit hash to stream a diff between two arbitrary hashes
250
+ * @param whitespace Optional whitespace flag which can be set to 'ignore-all'
251
+ * @returns Promise with text diff data
252
+ */
253
+ async getPullRequestDiff(
254
+ projectKey: string,
255
+ repositorySlug: string,
256
+ pullRequestId: string,
257
+ path: string,
258
+ contextLines?: string,
259
+ sinceId?: string,
260
+ srcPath?: string,
261
+ diffType?: string,
262
+ untilId?: string,
263
+ whitespace?: string
264
+ ) {
265
+ return handleApiOperation(
266
+ () => __request(OpenAPI, {
267
+ method: 'GET',
268
+ url: '/api/latest/projects/{projectKey}/repos/{repositorySlug}/pull-requests/{pullRequestId}/diff/{path}',
269
+ path: {
270
+ 'path': path,
271
+ 'projectKey': projectKey,
272
+ 'pullRequestId': pullRequestId,
273
+ 'repositorySlug': repositorySlug,
274
+ },
275
+ query: {
276
+ 'contextLines': contextLines,
277
+ 'sinceId': sinceId,
278
+ 'srcPath': srcPath,
279
+ 'diffType': diffType,
280
+ 'untilId': untilId,
281
+ 'whitespace': whitespace,
282
+ },
283
+ headers: {
284
+ 'Accept': 'text/plain'
285
+ },
286
+ errors: {
287
+ 400: `If the request was malformed.`,
288
+ 401: `The currently authenticated user has insufficient permissions to view the repository or pull request.`,
289
+ 404: `The repository or pull request does not exist.`,
290
+ },
291
+ }),
292
+ 'Error fetching pull request diff'
293
+ );
114
294
  }
115
295
 
116
296
  static validateConfig(): string[] {
@@ -160,5 +340,38 @@ export const bitbucketToolSchemas = {
160
340
  pullRequestId: z.string().describe("The pull request ID"),
161
341
  start: z.number().optional().describe("Start number for pagination"),
162
342
  limit: z.number().optional().default(25).describe("Number of items to return")
343
+ },
344
+ getPullRequestChanges: {
345
+ projectKey: z.string().describe("The project key"),
346
+ repositorySlug: z.string().describe("The repository slug"),
347
+ pullRequestId: z.string().describe("The pull request ID"),
348
+ sinceId: z.string().optional().describe("The since commit hash to stream changes for a RANGE arbitrary change scope"),
349
+ changeScope: z.string().optional().describe("UNREVIEWED for unreviewed changes, RANGE for changes between commits, ALL for all changes (default)"),
350
+ untilId: z.string().optional().describe("The until commit hash to stream changes for a RANGE arbitrary change scope"),
351
+ withComments: z.string().optional().describe("true to apply comment counts in the changes (default), false to stream changes without comment counts"),
352
+ start: z.number().optional().describe("Start number for pagination"),
353
+ limit: z.number().optional().default(25).describe("Number of items to return")
354
+ },
355
+ postPullRequestComment: {
356
+ projectKey: z.string().describe("The project key"),
357
+ repositorySlug: z.string().describe("The repository slug"),
358
+ pullRequestId: z.string().describe("The pull request ID"),
359
+ text: z.string().describe("The comment text"),
360
+ parentId: z.number().optional().describe("Parent comment ID for replies"),
361
+ filePath: z.string().optional().describe("File path for file-specific comments"),
362
+ line: z.number().optional().describe("Line number for line-specific comments"),
363
+ lineType: z.enum(['ADDED', 'REMOVED', 'CONTEXT']).optional().describe("Line type for line comments")
364
+ },
365
+ getPullRequestDiff: {
366
+ projectKey: z.string().describe("The project key"),
367
+ repositorySlug: z.string().describe("The repository slug"),
368
+ pullRequestId: z.string().describe("The pull request ID"),
369
+ path: z.string().describe("The path to the file which should be diffed. Note: Before getting diff, use getPullRequestChanges to understand what files were changed in the PR"),
370
+ contextLines: z.string().optional().describe("Number of context lines to include around added/removed lines in the diff"),
371
+ sinceId: z.string().optional().describe("The since commit hash to stream a diff between two arbitrary hashes"),
372
+ srcPath: z.string().optional().describe("The previous path to the file, if the file has been copied, moved or renamed"),
373
+ diffType: z.string().optional().describe("The type of diff being requested"),
374
+ untilId: z.string().optional().describe("The until commit hash to stream a diff between two arbitrary hashes"),
375
+ whitespace: z.string().optional().describe("Optional whitespace flag which can be set to 'ignore-all'")
163
376
  }
164
377
  };
package/src/index.ts CHANGED
@@ -73,13 +73,44 @@ server.tool(
73
73
  );
74
74
 
75
75
  server.tool(
76
- "bitbucket_getPullRequestCommentsAndAction",
76
+ "bitbucket_getPR_CommentsAndAction",
77
77
  "Get comments for a Bitbucket pull request and other actions, like approvals",
78
78
  bitbucketToolSchemas.getPullRequestComments,
79
79
  async ({ projectKey, repositorySlug, pullRequestId, start, limit }) => {
80
80
  const result = await bitbucketService.getPullRequestCommentsAndActions(projectKey, repositorySlug, pullRequestId, start, limit);
81
81
  return formatToolResponse(result);
82
82
  }
83
- )
83
+ );
84
+
85
+ server.tool(
86
+ "bitbucket_getPullRequestChanges",
87
+ "Get the changes for a Bitbucket pull request",
88
+ bitbucketToolSchemas.getPullRequestChanges,
89
+ async ({ projectKey, repositorySlug, pullRequestId, sinceId, changeScope, untilId, withComments, start, limit }) => {
90
+ const result = await bitbucketService.getPullRequestChanges(projectKey, repositorySlug, pullRequestId, sinceId, changeScope, untilId, withComments, start, limit);
91
+ return formatToolResponse(result);
92
+ }
93
+ );
94
+
95
+ server.tool(
96
+ "bitbucket_postPullRequestComment",
97
+ "Post a comment to a Bitbucket pull request",
98
+ bitbucketToolSchemas.postPullRequestComment,
99
+ async ({ projectKey, repositorySlug, pullRequestId, text, parentId, filePath, line, lineType }) => {
100
+ const result = await bitbucketService.postPullRequestComment(projectKey, repositorySlug, pullRequestId, text, parentId, filePath, line, lineType);
101
+ return formatToolResponse(result);
102
+ }
103
+ );
104
+
105
+
106
+ server.tool(
107
+ "bitbucket_getPullRequestDiff",
108
+ "Get text diff for a specific file in a Bitbucket pull request. Returns plain text diff format. Note: Before getting diff, use getPullRequestChanges to understand what files were changed in the PR",
109
+ bitbucketToolSchemas.getPullRequestDiff,
110
+ async ({ projectKey, repositorySlug, pullRequestId, path, contextLines, sinceId, srcPath, diffType, untilId, whitespace }) => {
111
+ const result = await bitbucketService.getPullRequestDiff(projectKey, repositorySlug, pullRequestId, path, contextLines, sinceId, srcPath, diffType, untilId, whitespace);
112
+ return formatToolResponse(result);
113
+ }
114
+ );
84
115
 
85
116
  await connectServer(server);
@@ -0,0 +1,216 @@
1
+ // Original API response interfaces
2
+ interface PathInfo {
3
+ components: string[];
4
+ parent: string;
5
+ name: string;
6
+ extension: string;
7
+ toString: string;
8
+ }
9
+
10
+ interface ChangeProperties {
11
+ gitChangeType: string;
12
+ orphanedComments?: number;
13
+ activeComments?: number;
14
+ changeScope?: string;
15
+ }
16
+
17
+ interface ChangeLinks {
18
+ self: (null | string)[];
19
+ }
20
+
21
+ interface PRChange {
22
+ contentId: string;
23
+ fromContentId: string;
24
+ path: PathInfo;
25
+ srcPath?: PathInfo;
26
+ percentUnchanged?: number;
27
+ type: string;
28
+ nodeType?: string;
29
+ executable?: boolean;
30
+ srcExecutable?: boolean;
31
+ links?: ChangeLinks;
32
+ properties: ChangeProperties;
33
+ }
34
+
35
+ interface PRChangesResponse {
36
+ fromHash: string;
37
+ toHash: string;
38
+ properties: {
39
+ changeScope: string;
40
+ };
41
+ values: PRChange[];
42
+ size: number;
43
+ isLastPage: boolean;
44
+ start: number;
45
+ limit: number;
46
+ nextPageStart: number | null;
47
+ }
48
+
49
+ // Simplified interfaces
50
+ interface SimplifiedPath {
51
+ name: string;
52
+ path: string;
53
+ extension?: string;
54
+ }
55
+
56
+ interface SimplifiedChange {
57
+ contentId: string;
58
+ path: SimplifiedPath;
59
+ srcPath?: SimplifiedPath;
60
+ type: string;
61
+ gitChangeType: string;
62
+ comments?: number;
63
+ }
64
+
65
+ interface SimplifiedPRChangesResponse {
66
+ fromHash: string;
67
+ toHash: string;
68
+ changeScope: string;
69
+ changes: SimplifiedChange[];
70
+ summary: {
71
+ totalChanges: number;
72
+ additions: number;
73
+ deletions: number;
74
+ modifications: number;
75
+ moves: number;
76
+ filesWithComments: number;
77
+ };
78
+ isLastPage: boolean;
79
+ }
80
+
81
+ // Type guards
82
+ function isPathInfo(obj: unknown): obj is PathInfo {
83
+ return (
84
+ typeof obj === 'object' &&
85
+ obj !== null &&
86
+ Array.isArray((obj as any).components) &&
87
+ typeof (obj as any).name === 'string' &&
88
+ typeof (obj as any).toString === 'string'
89
+ );
90
+ }
91
+
92
+ function isPRChange(obj: unknown): obj is PRChange {
93
+ return (
94
+ typeof obj === 'object' &&
95
+ obj !== null &&
96
+ typeof (obj as any).contentId === 'string' &&
97
+ typeof (obj as any).type === 'string' &&
98
+ isPathInfo((obj as any).path) &&
99
+ typeof (obj as any).properties === 'object' &&
100
+ typeof (obj as any).properties.gitChangeType === 'string'
101
+ );
102
+ }
103
+
104
+ function isPRChangesResponse(obj: unknown): obj is PRChangesResponse {
105
+ return (
106
+ typeof obj === 'object' &&
107
+ obj !== null &&
108
+ typeof (obj as any).fromHash === 'string' &&
109
+ typeof (obj as any).toHash === 'string' &&
110
+ Array.isArray((obj as any).values) &&
111
+ typeof (obj as any).isLastPage === 'boolean'
112
+ );
113
+ }
114
+
115
+ // Transformation functions
116
+ function simplifyPath(path: PathInfo): SimplifiedPath {
117
+ return {
118
+ name: path.name,
119
+ path: path.toString,
120
+ ...(path.extension && { extension: path.extension })
121
+ };
122
+ }
123
+
124
+ function simplifyChange(change: PRChange): SimplifiedChange {
125
+ const result: SimplifiedChange = {
126
+ contentId: change.contentId,
127
+ path: simplifyPath(change.path),
128
+ type: change.type,
129
+ gitChangeType: change.properties.gitChangeType
130
+ };
131
+
132
+ // Add source path for moves/renames
133
+ if (change.srcPath) {
134
+ result.srcPath = simplifyPath(change.srcPath);
135
+ }
136
+
137
+ // Add comment count if present
138
+ const commentCount = (change.properties.orphanedComments || 0) + (change.properties.activeComments || 0);
139
+ if (commentCount > 0) {
140
+ result.comments = commentCount;
141
+ }
142
+
143
+ return result;
144
+ }
145
+
146
+ export function simplifyBitbucketPRChanges(response: any): SimplifiedPRChangesResponse | any {
147
+ // Validate response structure
148
+ if (!isPRChangesResponse(response)) {
149
+ return response;
150
+ }
151
+
152
+ const changes: SimplifiedChange[] = [];
153
+
154
+ // Process each change with type guard validation
155
+ for (const change of response.values || []) {
156
+ if (isPRChange(change)) {
157
+ changes.push(simplifyChange(change));
158
+ }
159
+ }
160
+
161
+ // If no valid changes were found, return the original response
162
+ if (changes.length === 0 && (response.values || []).length > 0) {
163
+ return response;
164
+ }
165
+
166
+ // Calculate summary statistics
167
+ const additions = changes.filter(c => c.type === 'ADD').length;
168
+ const deletions = changes.filter(c => c.type === 'DELETE').length;
169
+ const modifications = changes.filter(c => c.type === 'MODIFY').length;
170
+ const moves = changes.filter(c => c.type === 'MOVE').length;
171
+ const filesWithComments = changes.filter(c => c.comments && c.comments > 0).length;
172
+
173
+ return {
174
+ fromHash: response.fromHash,
175
+ toHash: response.toHash,
176
+ changeScope: response.properties.changeScope,
177
+ changes,
178
+ summary: {
179
+ totalChanges: changes.length,
180
+ additions,
181
+ deletions,
182
+ modifications,
183
+ moves,
184
+ filesWithComments
185
+ },
186
+ isLastPage: response.isLastPage
187
+ };
188
+ }
189
+
190
+ export function getChangesSummary(response: any): string[] {
191
+ const changeSummaries: string[] = [];
192
+
193
+ for (const change of response.values || []) {
194
+ if (isPRChange(change)) {
195
+ const action = change.type === 'ADD' ? 'Added' :
196
+ change.type === 'DELETE' ? 'Deleted' :
197
+ change.type === 'MODIFY' ? 'Modified' :
198
+ change.type === 'MOVE' ? 'Moved' : change.type;
199
+
200
+ let summary = `${action}: ${change.path.toString}`;
201
+
202
+ if (change.srcPath && change.type === 'MOVE') {
203
+ summary += ` (from ${change.srcPath.toString})`;
204
+ }
205
+
206
+ const commentCount = (change.properties.orphanedComments || 0) + (change.properties.activeComments || 0);
207
+ if (commentCount > 0) {
208
+ summary += ` [${commentCount} comment${commentCount > 1 ? 's' : ''}]`;
209
+ }
210
+
211
+ changeSummaries.push(summary);
212
+ }
213
+ }
214
+
215
+ return changeSummaries;
216
+ }