@stubbedev/atlassian-mcp 0.0.2 → 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 +4 -2
- package/dist/bitbucket.js +69 -8
- package/dist/index.js +37 -1
- package/package.json +1 -1
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
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}
|
|
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 (
|
|
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':
|