@stubbedev/atlassian-mcp 0.1.6 → 0.1.7
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 +3 -1
- package/dist/bitbucket.js +72 -15
- package/dist/index.js +4 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -52,7 +52,7 @@ A [Model Context Protocol](https://modelcontextprotocol.io) (MCP) server for **s
|
|
|
52
52
|
| `bitbucket_decline_pr` | Decline a pull request |
|
|
53
53
|
| `bitbucket_get_pr_comments` | Get PR comment threads in bulk, including task-style BLOCKER comments and blocker counts |
|
|
54
54
|
| `bitbucket_add_pr_comment` | Add a top-level PR comment or reply to an existing comment |
|
|
55
|
-
| `bitbucket_update_pr_comment` | Update comment text,
|
|
55
|
+
| `bitbucket_update_pr_comment` | Update comment text/severity, resolve or reopen normal threads via `threadResolved`, and resolve/reopen BLOCKER tasks via `state` (strictly enforced) |
|
|
56
56
|
| `bitbucket_delete_pr_comment` | Delete a PR comment by comment ID |
|
|
57
57
|
| `bitbucket_get_pr_commits` | List commits included in a pull request |
|
|
58
58
|
| `bitbucket_get_branches` | List branches in a repository |
|
|
@@ -76,6 +76,8 @@ All list tools support `limit` and `start`/`startAt` for pagination.
|
|
|
76
76
|
- "show review comments on PR 42" → `bitbucket_get_pr_comments`
|
|
77
77
|
- "give me one full overview of PR 42" → `bitbucket_get_pr_overview`
|
|
78
78
|
- "how many open blockers are on PR 42" → `bitbucket_get_pr_comments` with `severity=BLOCKER` and `countOnly=true`
|
|
79
|
+
- "resolve this review thread on PR 42" → `bitbucket_update_pr_comment` with `threadResolved=true`
|
|
80
|
+
- "resolve this blocker task on PR 42" → `bitbucket_update_pr_comment` with `severity=BLOCKER` and `state=RESOLVED`
|
|
79
81
|
- "move FOO-123 to In Progress" → `jira_transition_issue` with `transitionName="In Progress"`
|
|
80
82
|
- "find bugs assigned to me in PAY project" → `jira_search_issues`
|
|
81
83
|
- "give me my coding context for this branch" → `get_dev_context`
|
package/dist/bitbucket.js
CHANGED
|
@@ -39,8 +39,11 @@ function formatCommentThread(comment, indent = '') {
|
|
|
39
39
|
const date = comment.createdDate ? ` (${formatDate(comment.createdDate)})` : '';
|
|
40
40
|
const state = comment.state ?? 'OPEN';
|
|
41
41
|
const severity = comment.severity ?? 'NORMAL';
|
|
42
|
+
const threadStatus = comment.threadResolved !== undefined
|
|
43
|
+
? ` thread=${comment.threadResolved ? 'RESOLVED' : 'OPEN'}`
|
|
44
|
+
: '';
|
|
42
45
|
const lines = [
|
|
43
|
-
`${indent}#${comment.id} [${state}/${severity}] ${author}${date} (v${comment.version})`,
|
|
46
|
+
`${indent}#${comment.id} [${state}/${severity}${threadStatus}] ${author}${date} (v${comment.version})`,
|
|
44
47
|
`${indent}${comment.text}`,
|
|
45
48
|
];
|
|
46
49
|
if (comment.comments && comment.comments.length > 0) {
|
|
@@ -51,6 +54,11 @@ function formatCommentThread(comment, indent = '') {
|
|
|
51
54
|
return lines;
|
|
52
55
|
}
|
|
53
56
|
function commentMatchesState(comment, state) {
|
|
57
|
+
if (state !== 'PENDING' && (comment.severity ?? 'NORMAL') !== 'BLOCKER' && comment.threadResolved !== undefined) {
|
|
58
|
+
const threadState = comment.threadResolved ? 'RESOLVED' : 'OPEN';
|
|
59
|
+
if (threadState === state)
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
54
62
|
const currentState = comment.state ?? 'OPEN';
|
|
55
63
|
if (currentState === state)
|
|
56
64
|
return true;
|
|
@@ -67,11 +75,31 @@ function commentMatchesSeverity(comment, severity) {
|
|
|
67
75
|
function uniqueCommentsFromActivities(activities) {
|
|
68
76
|
const byId = new Map();
|
|
69
77
|
for (const activity of activities) {
|
|
70
|
-
|
|
71
|
-
|
|
78
|
+
const comment = activity.comment;
|
|
79
|
+
if (!comment)
|
|
80
|
+
continue;
|
|
81
|
+
const existing = byId.get(comment.id);
|
|
82
|
+
if (!existing) {
|
|
83
|
+
byId.set(comment.id, comment);
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
const commentVersion = comment.version ?? -1;
|
|
87
|
+
const existingVersion = existing.version ?? -1;
|
|
88
|
+
if (commentVersion > existingVersion) {
|
|
89
|
+
byId.set(comment.id, comment);
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
if (commentVersion === existingVersion) {
|
|
93
|
+
const commentUpdated = comment.updatedDate ?? comment.createdDate ?? 0;
|
|
94
|
+
const existingUpdated = existing.updatedDate ?? existing.createdDate ?? 0;
|
|
95
|
+
if (commentUpdated > existingUpdated) {
|
|
96
|
+
byId.set(comment.id, comment);
|
|
97
|
+
}
|
|
72
98
|
}
|
|
73
99
|
}
|
|
74
|
-
return Array.from(byId.values())
|
|
100
|
+
return Array.from(byId.values())
|
|
101
|
+
.filter((comment) => !comment.deleted)
|
|
102
|
+
.sort((a, b) => (a.createdDate ?? 0) - (b.createdDate ?? 0));
|
|
75
103
|
}
|
|
76
104
|
function pageHint(data) {
|
|
77
105
|
return data.isLastPage ? '' : ` (use start=${data.nextPageStart} for next page)`;
|
|
@@ -542,26 +570,55 @@ export class BitbucketClient {
|
|
|
542
570
|
}
|
|
543
571
|
async updatePrComment(args) {
|
|
544
572
|
const { projectKey, repoSlug } = this.resolveProjectAndRepo(args.projectKey, args.repoSlug);
|
|
545
|
-
if (!args.text && !args.state && !args.severity) {
|
|
546
|
-
throw new Error('At least one field is required: text, state, or
|
|
573
|
+
if (!args.text && !args.state && !args.severity && args.threadResolved === undefined) {
|
|
574
|
+
throw new Error('At least one field is required: text, state, severity, or threadResolved');
|
|
547
575
|
}
|
|
548
576
|
const current = await this.request('GET', `/projects/${projectKey}/repos/${repoSlug}/pull-requests/${args.prId}/comments/${args.commentId}`);
|
|
549
577
|
if (!current)
|
|
550
578
|
throw new Error(`Comment #${args.commentId} not found.`);
|
|
551
|
-
const
|
|
552
|
-
|
|
553
|
-
|
|
579
|
+
const targetSeverity = args.severity ?? current.severity ?? 'NORMAL';
|
|
580
|
+
if (args.state && targetSeverity !== 'BLOCKER') {
|
|
581
|
+
throw new Error('state is only supported for BLOCKER comments (tasks). Use threadResolved for normal comment threads.');
|
|
582
|
+
}
|
|
583
|
+
if (args.threadResolved !== undefined && targetSeverity === 'BLOCKER') {
|
|
584
|
+
throw new Error('threadResolved is only supported for normal comments. Use state for BLOCKER comment tasks.');
|
|
585
|
+
}
|
|
586
|
+
const commentPath = (targetSeverity === 'BLOCKER' || current.severity === 'BLOCKER')
|
|
587
|
+
? `/projects/${projectKey}/repos/${repoSlug}/pull-requests/${args.prId}/blocker-comments/${args.commentId}`
|
|
588
|
+
: `/projects/${projectKey}/repos/${repoSlug}/pull-requests/${args.prId}/comments/${args.commentId}`;
|
|
589
|
+
const buildBody = (version) => {
|
|
590
|
+
const body = { version };
|
|
591
|
+
if (args.text !== undefined)
|
|
592
|
+
body.text = validateCommentText(args.text);
|
|
593
|
+
if (args.state && targetSeverity === 'BLOCKER')
|
|
594
|
+
body.state = args.state;
|
|
595
|
+
if (args.severity)
|
|
596
|
+
body.severity = args.severity;
|
|
597
|
+
if (args.threadResolved !== undefined) {
|
|
598
|
+
body.threadResolved = args.threadResolved;
|
|
599
|
+
}
|
|
600
|
+
return body;
|
|
554
601
|
};
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
602
|
+
let updated;
|
|
603
|
+
try {
|
|
604
|
+
updated = await this.request('PUT', commentPath, buildBody(current.version));
|
|
605
|
+
}
|
|
606
|
+
catch (error) {
|
|
607
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
608
|
+
if (!message.includes('Bitbucket 409'))
|
|
609
|
+
throw error;
|
|
610
|
+
const latest = await this.request('GET', commentPath);
|
|
611
|
+
if (!latest)
|
|
612
|
+
throw error;
|
|
613
|
+
updated = await this.request('PUT', commentPath, buildBody(latest.version));
|
|
614
|
+
}
|
|
560
615
|
if (!updated)
|
|
561
616
|
return text(`Comment #${args.commentId} updated.`);
|
|
562
617
|
const state = updated.state ?? current.state ?? 'OPEN';
|
|
563
618
|
const severity = updated.severity ?? current.severity ?? 'NORMAL';
|
|
564
|
-
|
|
619
|
+
const threadResolved = updated.threadResolved ?? current.threadResolved;
|
|
620
|
+
const threadStatus = threadResolved === undefined ? '' : `, thread=${threadResolved ? 'RESOLVED' : 'OPEN'}`;
|
|
621
|
+
return text(`Comment #${updated.id} updated (${state}/${severity}${threadStatus}).`);
|
|
565
622
|
}
|
|
566
623
|
async deletePrComment(args) {
|
|
567
624
|
const { projectKey, repoSlug } = this.resolveProjectAndRepo(args.projectKey, args.repoSlug);
|
package/dist/index.js
CHANGED
|
@@ -505,7 +505,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
505
505
|
repo: { type: 'string', description: 'Alias for repoSlug' },
|
|
506
506
|
prId: { type: 'number', description: 'Pull request number (PR ID)' },
|
|
507
507
|
path: { type: 'string', description: 'Optional file path filter, e.g. "src/index.ts"' },
|
|
508
|
-
state: { type: 'string', enum: ['OPEN', 'RESOLVED', 'PENDING'], description: '
|
|
508
|
+
state: { type: 'string', enum: ['OPEN', 'RESOLVED', 'PENDING'], description: 'State filter. For normal comments this maps to threadResolved (OPEN/RESOLVED). For BLOCKER tasks this uses task state (OPEN/RESOLVED; PENDING allowed only for non-BLOCKER).', default: 'OPEN' },
|
|
509
509
|
severity: { type: 'string', enum: ['ALL', 'NORMAL', 'BLOCKER'], description: 'Comment severity filter. BLOCKER means task/checklist-style review comments.', default: 'ALL' },
|
|
510
510
|
countOnly: { type: 'boolean', description: 'When true with severity=BLOCKER, returns counts instead of comment bodies', default: false },
|
|
511
511
|
limit: { type: 'number', description: 'Max items per page (default 50)', default: 50 },
|
|
@@ -533,7 +533,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
533
533
|
},
|
|
534
534
|
{
|
|
535
535
|
name: 'bitbucket_update_pr_comment',
|
|
536
|
-
description: 'Use when you want to edit PR comments, resolve/reopen
|
|
536
|
+
description: 'Use when you want to edit PR comments, resolve/reopen normal discussion threads, or manage task-style BLOCKER comments. Hint: for normal comments, resolve/reopen means threadResolved; for BLOCKER tasks, resolve/reopen uses state. Keep comments concise, plain text, and free of filler. Never include emojis. You can pass projectKey/repoSlug or project/repo.',
|
|
537
537
|
inputSchema: {
|
|
538
538
|
type: 'object',
|
|
539
539
|
properties: {
|
|
@@ -544,7 +544,8 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
544
544
|
prId: { type: 'number', description: 'Pull request number (PR ID)' },
|
|
545
545
|
commentId: { type: 'number', description: 'Comment ID to update' },
|
|
546
546
|
text: { type: 'string', description: 'New concise comment text only. No filler. Do not include emojis. (optional)' },
|
|
547
|
-
state: { type: 'string', enum: ['OPEN', 'RESOLVED'], description: '
|
|
547
|
+
state: { type: 'string', enum: ['OPEN', 'RESOLVED'], description: 'Task state for BLOCKER comments only (optional). Rejected for normal comments; use threadResolved instead.' },
|
|
548
|
+
threadResolved: { type: 'boolean', description: 'Resolve/reopen normal comment threads in Bitbucket UI only (optional). Rejected for BLOCKER comments; use state instead.' },
|
|
548
549
|
severity: { type: 'string', enum: ['NORMAL', 'BLOCKER'], description: 'Comment severity (optional). BLOCKER marks it as a task/checklist item.' },
|
|
549
550
|
},
|
|
550
551
|
required: ['prId', 'commentId'],
|