@stubbedev/atlassian-mcp 0.2.7 → 0.2.8

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 (2) hide show
  1. package/dist/bitbucket.js +15 -63
  2. package/package.json +1 -1
package/dist/bitbucket.js CHANGED
@@ -183,7 +183,7 @@ function validateCommentText(textValue) {
183
183
  export class BitbucketClient {
184
184
  baseUrl;
185
185
  headers;
186
- currentUserCache;
186
+ currentUsernameCache;
187
187
  constructor(baseUrl, token) {
188
188
  this.baseUrl = baseUrl.replace(/\/$/, '');
189
189
  this.headers = {
@@ -192,6 +192,18 @@ export class BitbucketClient {
192
192
  Accept: 'application/json',
193
193
  };
194
194
  }
195
+ /** Returns the slug/username of the authenticated user via the X-AUSERNAME response header. */
196
+ async getCurrentUsername() {
197
+ if (this.currentUsernameCache)
198
+ return this.currentUsernameCache;
199
+ const url = `${this.baseUrl}/rest/api/1.0/application-properties`;
200
+ const res = await fetch(url, { method: 'GET', headers: this.headers });
201
+ const username = res.headers.get('X-AUSERNAME');
202
+ if (!username)
203
+ throw new Error('Could not determine current Bitbucket user. Check token permissions.');
204
+ this.currentUsernameCache = username;
205
+ return username;
206
+ }
195
207
  /** Returns a URL-safe `/projects/.../repos/...` prefix for REST paths. */
196
208
  rp(projectKey, repoSlug) {
197
209
  return `/projects/${encodeURIComponent(projectKey)}/repos/${encodeURIComponent(repoSlug)}`;
@@ -278,38 +290,6 @@ export class BitbucketClient {
278
290
  }
279
291
  return res.status === 204 ? null : res.json();
280
292
  }
281
- normalizeIdentity(value) {
282
- return (value ?? '').trim().toLowerCase();
283
- }
284
- async getCurrentUser() {
285
- if (this.currentUserCache)
286
- return this.currentUserCache;
287
- const me = await this.request('GET', '/users/~self');
288
- if (!me) {
289
- throw new Error('Could not determine current Bitbucket user identity.');
290
- }
291
- this.currentUserCache = me;
292
- return me;
293
- }
294
- async assertOwnComment(comment) {
295
- const me = await this.getCurrentUser();
296
- const commentAuthorName = this.normalizeIdentity(comment.author?.name);
297
- const commentAuthorDisplayName = this.normalizeIdentity(comment.author?.displayName);
298
- const meName = this.normalizeIdentity(me.name);
299
- const meSlug = this.normalizeIdentity(me.slug);
300
- const meDisplayName = this.normalizeIdentity(me.displayName);
301
- const hasStrongCommentIdentity = commentAuthorName.length > 0;
302
- const hasStrongUserIdentity = meName.length > 0 || meSlug.length > 0;
303
- const matchesByName = commentAuthorName.length > 0 && (commentAuthorName === meName || commentAuthorName === meSlug);
304
- const matchesByDisplayNameFallback = !hasStrongCommentIdentity
305
- && !hasStrongUserIdentity
306
- && commentAuthorDisplayName.length > 0
307
- && commentAuthorDisplayName === meDisplayName;
308
- if (matchesByName || matchesByDisplayNameFallback) {
309
- return;
310
- }
311
- throw new Error(`You can only edit your own Bitbucket comments. Comment #${comment.id} is authored by ${comment.author?.displayName ?? comment.author?.name ?? 'another user'}.`);
312
- }
313
293
  /** Returns true if the given remote URL belongs to this Bitbucket instance. */
314
294
  isRemoteForThisInstance(remoteUrl) {
315
295
  return this.remoteMatchesInstance(remoteUrl);
@@ -367,10 +347,7 @@ export class BitbucketClient {
367
347
  }
368
348
  async myPrs(args) {
369
349
  const { limit = 25, start = 0, role } = args;
370
- const me = await this.getCurrentUser();
371
- const userSlug = me.slug ?? me.name;
372
- if (!userSlug)
373
- throw new Error('Could not determine your Bitbucket user slug. Check token permissions.');
350
+ const userSlug = await this.getCurrentUsername();
374
351
  const qs = new URLSearchParams({ limit: String(limit), start: String(start), state: 'OPEN' });
375
352
  if (role)
376
353
  qs.set('role', role.toUpperCase());
@@ -424,28 +401,12 @@ export class BitbucketClient {
424
401
  throw new Error(`No open PR found for branch "${branchDisplayId(branch)}".`);
425
402
  prId = found.id;
426
403
  }
427
- const [pr, me] = await Promise.all([
428
- this.request('GET', `${this.rp(projectKey, repoSlug)}/pull-requests/${prId}`),
429
- this.getCurrentUser().catch(() => null),
430
- ]);
404
+ const pr = await this.request('GET', `${this.rp(projectKey, repoSlug)}/pull-requests/${prId}`);
431
405
  if (!pr)
432
406
  return text('Pull request not found.');
433
407
  const sections = [];
434
408
  const reviewers = pr.reviewers.map((r) => `${r.user.displayName}${r.approved ? ' ✓' : ''}`).join(', ');
435
409
  const url = pr.links?.self?.[0]?.href;
436
- // Prefer slug (canonical unique identifier), fall back to username (name field).
437
- // Display name is deliberately excluded — it is not unique.
438
- const meSlug = this.normalizeIdentity(me?.slug);
439
- const meName = this.normalizeIdentity(me?.name);
440
- const authorSlug = this.normalizeIdentity(pr.author.user.slug);
441
- const authorName = this.normalizeIdentity(pr.author.user.name);
442
- const isAuthor = me
443
- ? (meSlug && authorSlug ? meSlug === authorSlug : false) ||
444
- (meName && authorName ? meName === authorName : false)
445
- : false;
446
- const viewingAs = me
447
- ? `Viewing as: ${me.displayName} (${isAuthor ? 'you are the author' : 'you are a reviewer'})`
448
- : '';
449
410
  const header = [
450
411
  `PR #${pr.id}: ${pr.title}`,
451
412
  `State: ${pr.state}`,
@@ -453,7 +414,6 @@ export class BitbucketClient {
453
414
  `Branch: ${pr.fromRef.displayId} → ${pr.toRef.displayId}`,
454
415
  `Reviewers: ${reviewers || 'None'}`,
455
416
  url ? `URL: ${url}` : '',
456
- viewingAs,
457
417
  '',
458
418
  'Description:',
459
419
  pr.description ?? '(no description)',
@@ -918,13 +878,6 @@ export class BitbucketClient {
918
878
  if (!current)
919
879
  throw new Error(`Comment #${args.commentId} not found.`);
920
880
  const currentSeverity = current.severity ?? 'NORMAL';
921
- const severityIsChanging = args.severity !== undefined && args.severity !== currentSeverity;
922
- const isResolutionOnlyUpdate = (args.state !== undefined || args.threadResolved !== undefined)
923
- && args.text === undefined
924
- && !severityIsChanging;
925
- if (!isResolutionOnlyUpdate) {
926
- await this.assertOwnComment(current);
927
- }
928
881
  const targetSeverity = args.severity ?? currentSeverity;
929
882
  if (args.state && targetSeverity !== 'BLOCKER') {
930
883
  throw new Error('state is only supported for BLOCKER comments (tasks). Use threadResolved for normal comment threads.');
@@ -974,7 +927,6 @@ export class BitbucketClient {
974
927
  const current = await this.request('GET', `${this.rp(projectKey, repoSlug)}/pull-requests/${args.prId}/comments/${args.commentId}`);
975
928
  if (!current)
976
929
  throw new Error(`Comment #${args.commentId} not found.`);
977
- await this.assertOwnComment(current);
978
930
  const commentPath = current.severity === 'BLOCKER'
979
931
  ? `${this.rp(projectKey, repoSlug)}/pull-requests/${args.prId}/blocker-comments/${args.commentId}`
980
932
  : `${this.rp(projectKey, repoSlug)}/pull-requests/${args.prId}/comments/${args.commentId}`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stubbedev/atlassian-mcp",
3
- "version": "0.2.7",
3
+ "version": "0.2.8",
4
4
  "description": "MCP server for self-hosted Jira and Bitbucket",
5
5
  "license": "MIT",
6
6
  "type": "module",