@atlassian-dc-mcp/bitbucket 0.14.2 → 0.15.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlassian-dc-mcp/bitbucket",
3
- "version": "0.14.2",
3
+ "version": "0.15.0",
4
4
  "main": "build/index.js",
5
5
  "type": "module",
6
6
  "bin": "./bin/run.js",
@@ -14,7 +14,7 @@
14
14
  "test": "jest"
15
15
  },
16
16
  "dependencies": {
17
- "@atlassian-dc-mcp/common": "^0.14.2",
17
+ "@atlassian-dc-mcp/common": "^0.15.0",
18
18
  "@modelcontextprotocol/sdk": "^1.27.1",
19
19
  "dotenv": "^16.4.7",
20
20
  "zod": "^3.24.2"
@@ -31,5 +31,5 @@
31
31
  "publishConfig": {
32
32
  "access": "public"
33
33
  },
34
- "gitHead": "f98cb1d1e3294bb13bf50ec202b6458203e62aaa"
34
+ "gitHead": "2ef6abdbc1eea0aefb128dedeb9238eb6ad5b638"
35
35
  }
@@ -2086,4 +2086,78 @@ describe('BitbucketService', () => {
2086
2086
  expect(result.error).toBe('Not Found');
2087
2087
  });
2088
2088
  });
2089
+
2090
+ describe('case-insensitive project keys and repository slugs', () => {
2091
+ it('should uppercase projectKey and lowercase repositorySlug for getPullRequests', async () => {
2092
+ const mockData = { values: [], size: 0, isLastPage: true };
2093
+ (PullRequestsService.getPage as jest.Mock).mockResolvedValue(mockData);
2094
+
2095
+ await bitbucketService.getPullRequests('test', 'Test-Repo');
2096
+
2097
+ expect(PullRequestsService.getPage).toHaveBeenCalledWith(
2098
+ 'TEST',
2099
+ 'test-repo',
2100
+ undefined, undefined, undefined, undefined, undefined,
2101
+ undefined, undefined, undefined, undefined, 25
2102
+ );
2103
+ });
2104
+
2105
+ it('should uppercase projectKey and lowercase repositorySlug for getPullRequest', async () => {
2106
+ const mockData = { id: 1, title: 'Test PR' };
2107
+ (PullRequestsService.get3 as jest.Mock).mockResolvedValue(mockData);
2108
+
2109
+ await bitbucketService.getPullRequest('test', 'Test-Repo', '1');
2110
+
2111
+ expect(PullRequestsService.get3).toHaveBeenCalledWith('TEST', '1', 'test-repo');
2112
+ });
2113
+
2114
+ it('should uppercase projectKey and lowercase repositorySlug for getPullRequestChanges', async () => {
2115
+ const mockData = { values: [], size: 0, isLastPage: true };
2116
+ (PullRequestsService.streamChanges1 as jest.Mock).mockResolvedValue(mockData);
2117
+
2118
+ await bitbucketService.getPullRequestChanges('test', 'Test-Repo', '1');
2119
+
2120
+ expect(PullRequestsService.streamChanges1).toHaveBeenCalledWith(
2121
+ 'TEST', '1', 'test-repo',
2122
+ undefined, undefined, undefined, undefined, undefined, 25
2123
+ );
2124
+ });
2125
+
2126
+ it('should uppercase projectKey and lowercase repositorySlug for createPullRequest', async () => {
2127
+ const mockData = { id: 1, title: 'New PR' };
2128
+ (PullRequestsService.create as jest.Mock).mockResolvedValue(mockData);
2129
+
2130
+ await bitbucketService.createPullRequest(
2131
+ 'test', 'Test-Repo', 'title', 'desc',
2132
+ 'refs/heads/feature', 'refs/heads/main'
2133
+ );
2134
+
2135
+ expect(PullRequestsService.create).toHaveBeenCalledWith(
2136
+ 'TEST', 'test-repo',
2137
+ expect.objectContaining({
2138
+ title: 'title',
2139
+ fromRef: expect.objectContaining({
2140
+ repository: expect.objectContaining({
2141
+ slug: 'test-repo',
2142
+ project: { key: 'TEST' }
2143
+ })
2144
+ })
2145
+ })
2146
+ );
2147
+ });
2148
+
2149
+ it('should uppercase projectKey and lowercase repositorySlug for getRequiredReviewers', async () => {
2150
+ const mockData: never[] = [];
2151
+ (PullRequestsService.getReviewers as jest.Mock).mockResolvedValue(mockData);
2152
+
2153
+ await bitbucketService.getRequiredReviewers(
2154
+ 'test', 'Test-Repo', 'refs/heads/feature', 'refs/heads/main'
2155
+ );
2156
+
2157
+ expect(PullRequestsService.getReviewers).toHaveBeenCalledWith(
2158
+ 'TEST', 'test-repo', undefined, undefined,
2159
+ 'refs/heads/feature', 'refs/heads/main'
2160
+ );
2161
+ });
2162
+ });
2089
2163
  });
@@ -58,6 +58,8 @@ export class BitbucketService {
58
58
  async getCommits(projectKey: string, repositorySlug: string, path?: string, since?: string, until?: string,
59
59
  limit?: number
60
60
  ) {
61
+ projectKey = projectKey.toUpperCase();
62
+ repositorySlug = repositorySlug.toLowerCase();
61
63
  return handleApiOperation(
62
64
  () => RepositoryService.getCommits(
63
65
  projectKey,
@@ -99,6 +101,7 @@ export class BitbucketService {
99
101
  * @returns Promise with project data
100
102
  */
101
103
  async getProject(projectKey: string) {
104
+ projectKey = projectKey.toUpperCase();
102
105
  return handleApiOperation(
103
106
  () => ProjectService.getProject(projectKey),
104
107
  'Error fetching project'
@@ -113,6 +116,7 @@ export class BitbucketService {
113
116
  * @returns Promise with repositories data
114
117
  */
115
118
  async getRepositories(projectKey: string, start?: number, limit?: number) {
119
+ projectKey = projectKey.toUpperCase();
116
120
  return handleApiOperation(
117
121
  () => ProjectService.getRepositories(projectKey, start, limit ?? this.getPageSize()),
118
122
  'Error fetching repositories'
@@ -126,6 +130,8 @@ export class BitbucketService {
126
130
  * @returns Promise with repository data
127
131
  */
128
132
  async getRepository(projectKey: string, repositorySlug: string) {
133
+ projectKey = projectKey.toUpperCase();
134
+ repositorySlug = repositorySlug.toLowerCase();
129
135
  return handleApiOperation(
130
136
  () => ProjectService.getRepository(projectKey, repositorySlug),
131
137
  'Error fetching repository'
@@ -162,6 +168,8 @@ export class BitbucketService {
162
168
  start?: number,
163
169
  limit?: number
164
170
  ) {
171
+ projectKey = projectKey.toUpperCase();
172
+ repositorySlug = repositorySlug.toLowerCase();
165
173
  return handleApiOperation(
166
174
  () => PullRequestsService.getPage(
167
175
  projectKey,
@@ -193,6 +201,8 @@ export class BitbucketService {
193
201
  repositorySlug: string,
194
202
  pullRequestId: string
195
203
  ) {
204
+ projectKey = projectKey.toUpperCase();
205
+ repositorySlug = repositorySlug.toLowerCase();
196
206
  return handleApiOperation(
197
207
  () => PullRequestsService.get3(projectKey, pullRequestId, repositorySlug),
198
208
  'Error fetching pull request'
@@ -208,6 +218,8 @@ export class BitbucketService {
208
218
  output: BitbucketOutputMode = 'compact',
209
219
  includeResolved = false
210
220
  ) {
221
+ projectKey = projectKey.toUpperCase();
222
+ repositorySlug = repositorySlug.toLowerCase();
211
223
  const result = await handleApiOperation(
212
224
  () => PullRequestsService.getActivities(
213
225
  projectKey,
@@ -256,6 +268,8 @@ export class BitbucketService {
256
268
  limit?: number,
257
269
  output: BitbucketOutputMode = 'compact'
258
270
  ) {
271
+ projectKey = projectKey.toUpperCase();
272
+ repositorySlug = repositorySlug.toLowerCase();
259
273
  const result = await handleApiOperation(
260
274
  () => PullRequestsService.streamChanges1(
261
275
  projectKey,
@@ -308,6 +322,8 @@ export class BitbucketService {
308
322
  pending?: boolean,
309
323
  output: BitbucketMutationOutputMode = 'ack'
310
324
  ) {
325
+ projectKey = projectKey.toUpperCase();
326
+ repositorySlug = repositorySlug.toLowerCase();
311
327
  const comment: any = {
312
328
  text
313
329
  };
@@ -404,6 +420,8 @@ export class BitbucketService {
404
420
  status: 'APPROVED' | 'NEEDS_WORK' | 'UNAPPROVED',
405
421
  lastReviewedCommit?: string
406
422
  ) {
423
+ projectKey = projectKey.toUpperCase();
424
+ repositorySlug = repositorySlug.toLowerCase();
407
425
  const requestBody: any = {
408
426
  status,
409
427
  ...(lastReviewedCommit ? { lastReviewedCommit } : {})
@@ -448,6 +466,8 @@ export class BitbucketService {
448
466
  untilId?: string,
449
467
  whitespace?: string
450
468
  ) {
469
+ projectKey = projectKey.toUpperCase();
470
+ repositorySlug = repositorySlug.toLowerCase();
451
471
  return handleApiOperation(
452
472
  () => __request(OpenAPI, {
453
473
  method: 'GET',
@@ -500,6 +520,8 @@ export class BitbucketService {
500
520
  reviewers?: string[],
501
521
  output: BitbucketMutationOutputMode = 'ack'
502
522
  ) {
523
+ projectKey = projectKey.toUpperCase();
524
+ repositorySlug = repositorySlug.toLowerCase();
503
525
  const pullRequestData: any = {
504
526
  title,
505
527
  description,
@@ -567,6 +589,8 @@ export class BitbucketService {
567
589
  reviewers?: string[],
568
590
  output: BitbucketMutationOutputMode = 'ack'
569
591
  ) {
592
+ projectKey = projectKey.toUpperCase();
593
+ repositorySlug = repositorySlug.toLowerCase();
570
594
  const pullRequestData: any = {
571
595
  version
572
596
  };
@@ -622,6 +646,8 @@ export class BitbucketService {
622
646
  sourceRepoId?: string,
623
647
  targetRepoId?: string
624
648
  ) {
649
+ projectKey = projectKey.toUpperCase();
650
+ repositorySlug = repositorySlug.toLowerCase();
625
651
  return handleApiOperation(
626
652
  () => PullRequestsService.getReviewers(
627
653
  projectKey,
@@ -827,7 +853,7 @@ export const bitbucketToolSchemas = {
827
853
  projectKey: z.string().describe("The project key"),
828
854
  repositorySlug: z.string().describe("The repository slug"),
829
855
  pullRequestId: z.string().describe("The pull request ID"),
830
- version: z.number().describe("The current version of the pull request (required for optimistic locking)"),
856
+ version: z.number().describe("The current version of the pull request (required for optimistic locking). Obtain this by calling bitbucket_getPullRequest first."),
831
857
  title: z.string().optional().describe("The new title for the pull request"),
832
858
  description: z.string().optional().describe("The new description for the pull request"),
833
859
  reviewers: z.array(z.string()).optional().describe("Optional array of reviewer usernames to set"),
package/src/index.ts CHANGED
@@ -89,7 +89,7 @@ server.tool(
89
89
 
90
90
  server.tool(
91
91
  "bitbucket_getPullRequest",
92
- "Get a specific pull request by ID. Returns full details including title, description, reviewers, participants, author, source/target branches, and current state.",
92
+ "Get a specific pull request by ID. Returns full details including title, description, reviewers, participants, author, source/target branches, current state, and version (needed for bitbucket_updatePullRequest).",
93
93
  bitbucketToolSchemas.getPullRequest,
94
94
  async ({ projectKey, repositorySlug, pullRequestId }) => {
95
95
  const result = await bitbucketService.getPullRequest(projectKey, repositorySlug, pullRequestId);
@@ -170,7 +170,7 @@ server.tool(
170
170
 
171
171
  server.tool(
172
172
  "bitbucket_updatePullRequest",
173
- "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.",
173
+ "Update the title, description, reviewers, destination branch or draft status of an existing pull request. IMPORTANT: You MUST first call bitbucket_getPullRequest to get the current 'version' number — this is required for optimistic locking and the call will fail without it. The reviewers parameter replaces ALL existing reviewers. If you want to preserve existing reviewers, include those from the current PR details along with any new ones you want to add.",
174
174
  bitbucketToolSchemas.updatePullRequest,
175
175
  async ({ projectKey, repositorySlug, pullRequestId, version, title, description, reviewers, output }) => {
176
176
  const result = await bitbucketService.updatePullRequest(projectKey, repositorySlug, pullRequestId, version, title, description, reviewers, output);