@stubbedev/atlassian-mcp 0.0.4 → 0.0.5

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/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # atlassian-mcp
2
2
 
3
- A [Model Context Protocol](https://modelcontextprotocol.io) (MCP) server for **self-hosted Jira** (Server / Data Center) and **self-hosted Bitbucket** (Server / Data Center). Exposes 34 tools to Claude for reading and managing issues, pull requests, comments, and git context.
3
+ A [Model Context Protocol](https://modelcontextprotocol.io) (MCP) server for **self-hosted Jira** (Server / Data Center) and **self-hosted Bitbucket** (Server / Data Center). Exposes 37 tools to Claude for reading and managing issues, pull requests, comments, and git context.
4
4
 
5
5
  > **Note:** This server only supports self-hosted instances. Jira Cloud and Bitbucket Cloud use different APIs and are not supported.
6
6
 
@@ -46,7 +46,9 @@ A [Model Context Protocol](https://modelcontextprotocol.io) (MCP) server for **s
46
46
  | `bitbucket_unapprove_pr` | Remove your approval from a pull request |
47
47
  | `bitbucket_merge_pr` | Merge a pull request |
48
48
  | `bitbucket_decline_pr` | Decline a pull request |
49
- | `bitbucket_get_pr_comments` | Get comment threads with IDs and states for a pull request |
49
+ | `bitbucket_get_pr_comments` | Get PR comment threads with IDs/states (optional `path` filter) |
50
+ | `bitbucket_get_pr_tasks` | List PR tasks (blocker comments), with `OPEN`/`RESOLVED` filter |
51
+ | `bitbucket_get_pr_task_count` | Get total OPEN and RESOLVED PR task counts |
50
52
  | `bitbucket_add_pr_comment` | Add a top-level PR comment or reply to an existing comment |
51
53
  | `bitbucket_update_pr_comment` | Update comment text, state, or severity (`NORMAL` / `BLOCKER`) |
52
54
  | `bitbucket_delete_pr_comment` | Delete a PR comment by comment ID |
package/dist/bitbucket.js CHANGED
@@ -49,6 +49,21 @@ function formatCommentThread(comment, indent = '') {
49
49
  }
50
50
  return lines;
51
51
  }
52
+ function commentMatchesState(comment, state) {
53
+ const currentState = comment.state ?? 'OPEN';
54
+ if (currentState === state)
55
+ return true;
56
+ return (comment.comments ?? []).some((child) => commentMatchesState(child, state));
57
+ }
58
+ function uniqueCommentsFromActivities(activities) {
59
+ const byId = new Map();
60
+ for (const activity of activities) {
61
+ if (activity.action === 'COMMENTED' && activity.comment && !byId.has(activity.comment.id)) {
62
+ byId.set(activity.comment.id, activity.comment);
63
+ }
64
+ }
65
+ return Array.from(byId.values()).sort((a, b) => (a.createdDate ?? 0) - (b.createdDate ?? 0));
66
+ }
52
67
  function pageHint(data) {
53
68
  return data.isLastPage ? '' : ` (use start=${data.nextPageStart} for next page)`;
54
69
  }
@@ -153,8 +168,11 @@ export class BitbucketClient {
153
168
  return text(`${data.values.length} PR(s) (${state})${pageHint(data)}:\n${lines.join('\n')}`);
154
169
  }
155
170
  async myPrs(args) {
156
- const { limit = 25, start = 0 } = args;
157
- const data = await this.request('GET', `/inbox/pull-requests?limit=${limit}&start=${start}`);
171
+ const { limit = 25, start = 0, role } = args;
172
+ const qs = new URLSearchParams({ limit: String(limit), start: String(start) });
173
+ if (role)
174
+ qs.set('role', role);
175
+ const data = await this.request('GET', `/inbox/pull-requests?${qs}`);
158
176
  if (!data || data.values.length === 0)
159
177
  return text('No pull requests in your inbox.');
160
178
  const lines = data.values.map((pr) => {
@@ -193,7 +211,8 @@ export class BitbucketClient {
193
211
  async getPrCommits(args) {
194
212
  const { projectKey, repoSlug } = this.resolveProjectAndRepo(args.projectKey, args.repoSlug);
195
213
  const limit = args.limit ?? 25;
196
- const data = await this.request('GET', `/projects/${projectKey}/repos/${repoSlug}/pull-requests/${args.prId}/commits?limit=${limit}`);
214
+ const start = args.start ?? 0;
215
+ const data = await this.request('GET', `/projects/${projectKey}/repos/${repoSlug}/pull-requests/${args.prId}/commits?limit=${limit}&start=${start}`);
197
216
  if (!data || data.values.length === 0)
198
217
  return text('No commits found.');
199
218
  const lines = data.values.map((c) => `${c.displayId} ${formatDate(c.authorTimestamp)} ${c.author.name}: ${c.message.split('\n')[0]}`);
@@ -280,14 +299,56 @@ export class BitbucketClient {
280
299
  async getPrComments(args) {
281
300
  const { projectKey, repoSlug } = this.resolveProjectAndRepo(args.projectKey, args.repoSlug);
282
301
  const { limit = 50, start = 0, state = 'OPEN' } = args;
283
- const qs = new URLSearchParams({ limit: String(limit), start: String(start) });
284
- qs.set('state', state);
285
- const data = await this.request('GET', `/projects/${projectKey}/repos/${repoSlug}/pull-requests/${args.prId}/comments?${qs}`);
286
- if (!data || data.values.length === 0) {
302
+ if (args.path) {
303
+ const qs = new URLSearchParams({
304
+ limit: String(limit),
305
+ start: String(start),
306
+ state,
307
+ path: args.path,
308
+ });
309
+ const data = await this.request('GET', `/projects/${projectKey}/repos/${repoSlug}/pull-requests/${args.prId}/comments?${qs}`);
310
+ const filtered = (data?.values ?? []).filter((comment) => commentMatchesState(comment, state));
311
+ if (filtered.length === 0) {
312
+ return text(`No ${state} comments on PR #${args.prId} for path ${args.path}.`);
313
+ }
314
+ const blocks = filtered.flatMap((comment) => formatCommentThread(comment));
315
+ const paging = data ? pageHint(data) : '';
316
+ return text(`${filtered.length} comment thread(s) on PR #${args.prId} for ${args.path}${paging}:\n\n${blocks.join('\n\n')}`);
317
+ }
318
+ const activityData = await this.request('GET', `/projects/${projectKey}/repos/${repoSlug}/pull-requests/${args.prId}/activities?limit=${limit}&start=${start}`);
319
+ const comments = uniqueCommentsFromActivities(activityData?.values ?? []).filter((comment) => commentMatchesState(comment, state));
320
+ if (comments.length === 0) {
287
321
  return text(`No ${state} comments on PR #${args.prId}.`);
288
322
  }
323
+ const blocks = comments.flatMap((comment) => formatCommentThread(comment));
324
+ const paging = activityData ? pageHint(activityData) : '';
325
+ return text(`${comments.length} comment thread(s) on PR #${args.prId}${paging}:\n\n${blocks.join('\n\n')}`);
326
+ }
327
+ async getPrTasks(args) {
328
+ const { projectKey, repoSlug } = this.resolveProjectAndRepo(args.projectKey, args.repoSlug);
329
+ const { limit = 50, start = 0, state = 'OPEN' } = args;
330
+ const qs = new URLSearchParams({ limit: String(limit), start: String(start), state });
331
+ const data = await this.request('GET', `/projects/${projectKey}/repos/${repoSlug}/pull-requests/${args.prId}/blocker-comments?${qs}`);
332
+ if (!data || data.values.length === 0) {
333
+ return text(`No ${state} tasks on PR #${args.prId}.`);
334
+ }
289
335
  const blocks = data.values.flatMap((comment) => formatCommentThread(comment));
290
- return text(`${data.values.length} comment thread(s) on PR #${args.prId}${pageHint(data)}:\n\n${blocks.join('\n\n')}`);
336
+ return text(`${data.values.length} ${state} task(s) on PR #${args.prId}${pageHint(data)}:\n\n${blocks.join('\n\n')}`);
337
+ }
338
+ async getPrTaskCount(args) {
339
+ const { projectKey, repoSlug } = this.resolveProjectAndRepo(args.projectKey, args.repoSlug);
340
+ const data = await this.request('GET', `/projects/${projectKey}/repos/${repoSlug}/pull-requests/${args.prId}/blocker-comments?count=true`);
341
+ let open = data?.open ?? 0;
342
+ let resolved = data?.resolved ?? 0;
343
+ if ((open === 0 && resolved === 0) && data?.values && data.values.length > 0) {
344
+ for (const v of data.values) {
345
+ if ((v.state ?? '').toUpperCase() === 'OPEN')
346
+ open = v.count ?? open;
347
+ if ((v.state ?? '').toUpperCase() === 'RESOLVED')
348
+ resolved = v.count ?? resolved;
349
+ }
350
+ }
351
+ return text(`PR #${args.prId} tasks: OPEN=${open}, RESOLVED=${resolved}`);
291
352
  }
292
353
  async addPrComment(args) {
293
354
  const { projectKey, repoSlug } = this.resolveProjectAndRepo(args.projectKey, args.repoSlug);
package/dist/index.js CHANGED
@@ -228,6 +228,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
228
228
  properties: {
229
229
  limit: { type: 'number', description: 'Max PRs per page (default 25)', default: 25 },
230
230
  start: { type: 'number', description: 'Offset for pagination (default 0)', default: 0 },
231
+ role: { type: 'string', enum: ['author', 'reviewer', 'participant'], description: 'Inbox role filter (default server behavior)' },
231
232
  },
232
233
  },
233
234
  },
@@ -267,6 +268,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
267
268
  repoSlug: { type: 'string', description: 'Repository slug (auto-detected from git remote if omitted)' },
268
269
  prId: { type: 'number', description: 'Pull request ID' },
269
270
  limit: { type: 'number', description: 'Max commits (default 25)', default: 25 },
271
+ start: { type: 'number', description: 'Offset for pagination (default 0)', default: 0 },
270
272
  },
271
273
  required: ['prId'],
272
274
  },
@@ -345,13 +347,14 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
345
347
  },
346
348
  {
347
349
  name: 'bitbucket_get_pr_comments',
348
- description: 'Get pull request comment threads with comment IDs and states (needed for replies and resolving)',
350
+ description: 'Get pull request comment threads with comment IDs and states (uses activities feed unless a path filter is provided)',
349
351
  inputSchema: {
350
352
  type: 'object',
351
353
  properties: {
352
354
  projectKey: { type: 'string', description: 'Bitbucket project key (auto-detected from git remote if omitted)' },
353
355
  repoSlug: { type: 'string', description: 'Repository slug (auto-detected from git remote if omitted)' },
354
356
  prId: { type: 'number', description: 'Pull request ID' },
357
+ path: { type: 'string', description: 'Optional file path filter, e.g. "src/index.ts"' },
355
358
  state: { type: 'string', enum: ['OPEN', 'RESOLVED', 'PENDING'], description: 'Filter comment state (default OPEN)', default: 'OPEN' },
356
359
  limit: { type: 'number', description: 'Max items per page (default 50)', default: 50 },
357
360
  start: { type: 'number', description: 'Offset for pagination (default 0)', default: 0 },
@@ -359,6 +362,35 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
359
362
  required: ['prId'],
360
363
  },
361
364
  },
365
+ {
366
+ name: 'bitbucket_get_pr_tasks',
367
+ description: 'List blocker comments (tasks) on a pull request',
368
+ inputSchema: {
369
+ type: 'object',
370
+ properties: {
371
+ projectKey: { type: 'string', description: 'Bitbucket project key (auto-detected from git remote if omitted)' },
372
+ repoSlug: { type: 'string', description: 'Repository slug (auto-detected from git remote if omitted)' },
373
+ prId: { type: 'number', description: 'Pull request ID' },
374
+ state: { type: 'string', enum: ['OPEN', 'RESOLVED'], description: 'Task state filter (default OPEN)', default: 'OPEN' },
375
+ limit: { type: 'number', description: 'Max tasks per page (default 50)', default: 50 },
376
+ start: { type: 'number', description: 'Offset for pagination (default 0)', default: 0 },
377
+ },
378
+ required: ['prId'],
379
+ },
380
+ },
381
+ {
382
+ name: 'bitbucket_get_pr_task_count',
383
+ description: 'Get total OPEN and RESOLVED task counts for a pull request',
384
+ inputSchema: {
385
+ type: 'object',
386
+ properties: {
387
+ projectKey: { type: 'string', description: 'Bitbucket project key (auto-detected from git remote if omitted)' },
388
+ repoSlug: { type: 'string', description: 'Repository slug (auto-detected from git remote if omitted)' },
389
+ prId: { type: 'number', description: 'Pull request ID' },
390
+ },
391
+ required: ['prId'],
392
+ },
393
+ },
362
394
  {
363
395
  name: 'bitbucket_add_pr_comment',
364
396
  description: 'Add a top-level comment or reply to an existing comment in a pull request',
@@ -530,6 +562,10 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
530
562
  return await bitbucket.mergePr(args);
531
563
  case 'bitbucket_get_pr_comments':
532
564
  return await bitbucket.getPrComments(args);
565
+ case 'bitbucket_get_pr_tasks':
566
+ return await bitbucket.getPrTasks(args);
567
+ case 'bitbucket_get_pr_task_count':
568
+ return await bitbucket.getPrTaskCount(args);
533
569
  case 'bitbucket_add_pr_comment':
534
570
  return await bitbucket.addPrComment(args);
535
571
  case 'bitbucket_update_pr_comment':
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stubbedev/atlassian-mcp",
3
- "version": "0.0.4",
3
+ "version": "0.0.5",
4
4
  "description": "MCP server for self-hosted Jira and Bitbucket",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",