@stubbedev/atlassian-mcp 0.1.0 → 0.1.4
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 -3
- package/dist/bitbucket.js +13 -2
- package/dist/index.js +11 -11
- package/dist/jira.js +12 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -38,16 +38,16 @@ A [Model Context Protocol](https://modelcontextprotocol.io) (MCP) server for **s
|
|
|
38
38
|
| `bitbucket_list_pull_requests` | List repository pull requests (filter by state, source branch, or text) |
|
|
39
39
|
| `bitbucket_my_prs` | List PRs in your inbox (authored by you or awaiting review) |
|
|
40
40
|
| `bitbucket_get_pull_request` | Get pull request details |
|
|
41
|
-
| `bitbucket_get_pr_overview` | Get a one-call PR overview: metadata, commits, comments
|
|
41
|
+
| `bitbucket_get_pr_overview` | Get a one-call PR overview: metadata, commits, comments, task-style BLOCKER comments, and optional diff |
|
|
42
42
|
| `bitbucket_get_pr_diff` | Get the code diff for a pull request |
|
|
43
43
|
| `bitbucket_create_pull_request` | Create a new pull request |
|
|
44
44
|
| `bitbucket_approve_pr` | Approve a pull request |
|
|
45
45
|
| `bitbucket_unapprove_pr` | Remove your approval from a pull request |
|
|
46
46
|
| `bitbucket_merge_pr` | Merge a pull request |
|
|
47
47
|
| `bitbucket_decline_pr` | Decline a pull request |
|
|
48
|
-
| `bitbucket_get_pr_comments` | Get PR comment threads in bulk, including
|
|
48
|
+
| `bitbucket_get_pr_comments` | Get PR comment threads in bulk, including task-style BLOCKER comments and blocker counts |
|
|
49
49
|
| `bitbucket_add_pr_comment` | Add a top-level PR comment or reply to an existing comment |
|
|
50
|
-
| `bitbucket_update_pr_comment` | Update comment text, state, or severity (`
|
|
50
|
+
| `bitbucket_update_pr_comment` | Update comment text, state, or severity (`BLOCKER` = task/checklist item) |
|
|
51
51
|
| `bitbucket_delete_pr_comment` | Delete a PR comment by comment ID |
|
|
52
52
|
| `bitbucket_get_pr_commits` | List commits included in a pull request |
|
|
53
53
|
| `bitbucket_get_branches` | List branches in a repository |
|
package/dist/bitbucket.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { execSync } from 'child_process';
|
|
2
|
+
const EMOJI_RE = /\p{Extended_Pictographic}/u;
|
|
2
3
|
function safeExec(cmd) {
|
|
3
4
|
try {
|
|
4
5
|
return execSync(cmd, { encoding: 'utf-8' }).trim();
|
|
@@ -136,6 +137,16 @@ function formatBitbucketError(status, method, path, details) {
|
|
|
136
137
|
return `${prefix}. Conflict (often stale version/state). Refresh and retry. ${details}`.trim();
|
|
137
138
|
return details ? `${prefix}. ${details}` : prefix;
|
|
138
139
|
}
|
|
140
|
+
function validateCommentText(textValue) {
|
|
141
|
+
const trimmed = textValue.trim();
|
|
142
|
+
if (!trimmed) {
|
|
143
|
+
throw new Error('Bitbucket comment text must not be empty.');
|
|
144
|
+
}
|
|
145
|
+
if (EMOJI_RE.test(trimmed)) {
|
|
146
|
+
throw new Error('Bitbucket comments must not include emoji. Use concise plain text only.');
|
|
147
|
+
}
|
|
148
|
+
return trimmed;
|
|
149
|
+
}
|
|
139
150
|
export class BitbucketClient {
|
|
140
151
|
baseUrl;
|
|
141
152
|
headers;
|
|
@@ -510,7 +521,7 @@ export class BitbucketClient {
|
|
|
510
521
|
}
|
|
511
522
|
async addPrComment(args) {
|
|
512
523
|
const { projectKey, repoSlug } = this.resolveProjectAndRepo(args.projectKey, args.repoSlug);
|
|
513
|
-
const body = { text: args.text };
|
|
524
|
+
const body = { text: validateCommentText(args.text) };
|
|
514
525
|
if (args.parentCommentId)
|
|
515
526
|
body.parent = { id: args.parentCommentId };
|
|
516
527
|
const created = await this.request('POST', `/projects/${projectKey}/repos/${repoSlug}/pull-requests/${args.prId}/comments`, body);
|
|
@@ -531,7 +542,7 @@ export class BitbucketClient {
|
|
|
531
542
|
throw new Error(`Comment #${args.commentId} not found.`);
|
|
532
543
|
const body = {
|
|
533
544
|
version: current.version,
|
|
534
|
-
text: args.text
|
|
545
|
+
text: args.text !== undefined ? validateCommentText(args.text) : current.text,
|
|
535
546
|
};
|
|
536
547
|
if (args.state)
|
|
537
548
|
body.state = args.state;
|
package/dist/index.js
CHANGED
|
@@ -162,12 +162,12 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
162
162
|
},
|
|
163
163
|
{
|
|
164
164
|
name: 'jira_add_comment',
|
|
165
|
-
description: 'Use when you want to leave a comment on a Jira ticket.',
|
|
165
|
+
description: 'Use when you want to leave a comment on a Jira ticket. Keep comments concise, plain text, and free of filler. Never include emojis.',
|
|
166
166
|
inputSchema: {
|
|
167
167
|
type: 'object',
|
|
168
168
|
properties: {
|
|
169
169
|
issueKey: { type: 'string', description: 'Jira issue key' },
|
|
170
|
-
body: { type: 'string', description: '
|
|
170
|
+
body: { type: 'string', description: 'Concise comment text only. No filler. Do not include emojis.' },
|
|
171
171
|
},
|
|
172
172
|
required: ['issueKey', 'body'],
|
|
173
173
|
},
|
|
@@ -246,7 +246,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
246
246
|
},
|
|
247
247
|
{
|
|
248
248
|
name: 'bitbucket_get_pr_overview',
|
|
249
|
-
description: 'Use when you want one bulk PR snapshot in a single call: metadata, commits, comments
|
|
249
|
+
description: 'Use when you want one bulk PR snapshot in a single call: metadata, commits, comments, task-style BLOCKER comments, and optional diff.',
|
|
250
250
|
inputSchema: {
|
|
251
251
|
type: 'object',
|
|
252
252
|
properties: {
|
|
@@ -259,7 +259,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
259
259
|
includeComments: { type: 'boolean', description: 'Include review comments/blockers (default true)', default: true },
|
|
260
260
|
includeDiff: { type: 'boolean', description: 'Include diff text (default false)', default: false },
|
|
261
261
|
commentsState: { type: 'string', enum: ['OPEN', 'RESOLVED', 'PENDING'], description: 'Comment state filter (default OPEN)', default: 'OPEN' },
|
|
262
|
-
commentsSeverity: { type: 'string', enum: ['ALL', 'NORMAL', 'BLOCKER'], description: 'Comment severity filter (default ALL)', default: 'ALL' },
|
|
262
|
+
commentsSeverity: { type: 'string', enum: ['ALL', 'NORMAL', 'BLOCKER'], description: 'Comment severity filter (default ALL). BLOCKER means task/checklist-style review comments.', default: 'ALL' },
|
|
263
263
|
commentsLimit: { type: 'number', description: 'Max comments per page (default 50)', default: 50 },
|
|
264
264
|
commentsStart: { type: 'number', description: 'Comment pagination offset (default 0)', default: 0 },
|
|
265
265
|
commitsLimit: { type: 'number', description: 'Max commits per page (default 25)', default: 25 },
|
|
@@ -385,7 +385,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
385
385
|
},
|
|
386
386
|
{
|
|
387
387
|
name: 'bitbucket_get_pr_comments',
|
|
388
|
-
description: 'Use when you want PR review discussion in bulk: comment threads,
|
|
388
|
+
description: 'Use when you want PR review discussion in bulk: comment threads, task-style BLOCKER comments, and blocker counts with pagination. You can pass projectKey/repoSlug or project/repo.',
|
|
389
389
|
inputSchema: {
|
|
390
390
|
type: 'object',
|
|
391
391
|
properties: {
|
|
@@ -396,7 +396,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
396
396
|
prId: { type: 'number', description: 'Pull request number (PR ID)' },
|
|
397
397
|
path: { type: 'string', description: 'Optional file path filter, e.g. "src/index.ts"' },
|
|
398
398
|
state: { type: 'string', enum: ['OPEN', 'RESOLVED', 'PENDING'], description: 'Comment state filter (default OPEN; BLOCKER mode supports OPEN/RESOLVED)', default: 'OPEN' },
|
|
399
|
-
severity: { type: 'string', enum: ['ALL', 'NORMAL', 'BLOCKER'], description: 'Comment severity filter.
|
|
399
|
+
severity: { type: 'string', enum: ['ALL', 'NORMAL', 'BLOCKER'], description: 'Comment severity filter. BLOCKER means task/checklist-style review comments.', default: 'ALL' },
|
|
400
400
|
countOnly: { type: 'boolean', description: 'When true with severity=BLOCKER, returns counts instead of comment bodies', default: false },
|
|
401
401
|
limit: { type: 'number', description: 'Max items per page (default 50)', default: 50 },
|
|
402
402
|
start: { type: 'number', description: 'Offset for pagination (default 0)', default: 0 },
|
|
@@ -406,7 +406,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
406
406
|
},
|
|
407
407
|
{
|
|
408
408
|
name: 'bitbucket_add_pr_comment',
|
|
409
|
-
description: 'Use when you want to add a PR review comment or reply to an existing thread. You can pass projectKey/repoSlug or project/repo.',
|
|
409
|
+
description: 'Use when you want to add a PR review comment or reply to an existing thread. Keep comments concise, plain text, and free of filler. Never include emojis. You can pass projectKey/repoSlug or project/repo.',
|
|
410
410
|
inputSchema: {
|
|
411
411
|
type: 'object',
|
|
412
412
|
properties: {
|
|
@@ -416,14 +416,14 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
416
416
|
repo: { type: 'string', description: 'Alias for repoSlug' },
|
|
417
417
|
prId: { type: 'number', description: 'Pull request number (PR ID)' },
|
|
418
418
|
parentCommentId: { type: 'number', description: 'Parent comment ID for reply mode (optional)' },
|
|
419
|
-
text: { type: 'string', description: '
|
|
419
|
+
text: { type: 'string', description: 'Concise comment text only. No filler. Do not include emojis.' },
|
|
420
420
|
},
|
|
421
421
|
required: ['prId', 'text'],
|
|
422
422
|
},
|
|
423
423
|
},
|
|
424
424
|
{
|
|
425
425
|
name: 'bitbucket_update_pr_comment',
|
|
426
|
-
description: 'Use when you want to edit PR comments, resolve/reopen them, or
|
|
426
|
+
description: 'Use when you want to edit PR comments, resolve/reopen them, or mark comments as task-style BLOCKER items. Keep comments concise, plain text, and free of filler. Never include emojis. You can pass projectKey/repoSlug or project/repo.',
|
|
427
427
|
inputSchema: {
|
|
428
428
|
type: 'object',
|
|
429
429
|
properties: {
|
|
@@ -433,9 +433,9 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
433
433
|
repo: { type: 'string', description: 'Alias for repoSlug' },
|
|
434
434
|
prId: { type: 'number', description: 'Pull request number (PR ID)' },
|
|
435
435
|
commentId: { type: 'number', description: 'Comment ID to update' },
|
|
436
|
-
text: { type: 'string', description: 'New comment text (optional)' },
|
|
436
|
+
text: { type: 'string', description: 'New concise comment text only. No filler. Do not include emojis. (optional)' },
|
|
437
437
|
state: { type: 'string', enum: ['OPEN', 'RESOLVED'], description: 'Comment state (optional)' },
|
|
438
|
-
severity: { type: 'string', enum: ['NORMAL', 'BLOCKER'], description: 'Comment severity (optional)' },
|
|
438
|
+
severity: { type: 'string', enum: ['NORMAL', 'BLOCKER'], description: 'Comment severity (optional). BLOCKER marks it as a task/checklist item.' },
|
|
439
439
|
},
|
|
440
440
|
required: ['prId', 'commentId'],
|
|
441
441
|
},
|
package/dist/jira.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { execSync } from 'child_process';
|
|
2
2
|
const JIRA_KEY_IN_BRANCH_RE = /\b([A-Z][A-Z0-9]+)-\d+\b/;
|
|
3
|
+
const EMOJI_RE = /\p{Extended_Pictographic}/u;
|
|
3
4
|
function text(t) {
|
|
4
5
|
return { content: [{ type: 'text', text: t }] };
|
|
5
6
|
}
|
|
@@ -76,6 +77,16 @@ function formatJiraError(status, method, path, details) {
|
|
|
76
77
|
return `${prefix}. Conflict. Refresh and retry. ${details}`.trim();
|
|
77
78
|
return details ? `${prefix}. ${details}` : prefix;
|
|
78
79
|
}
|
|
80
|
+
function validateCommentBody(body) {
|
|
81
|
+
const trimmed = body.trim();
|
|
82
|
+
if (!trimmed) {
|
|
83
|
+
throw new Error('Jira comment body must not be empty.');
|
|
84
|
+
}
|
|
85
|
+
if (EMOJI_RE.test(trimmed)) {
|
|
86
|
+
throw new Error('Jira comments must not include emoji. Use concise plain text only.');
|
|
87
|
+
}
|
|
88
|
+
return trimmed;
|
|
89
|
+
}
|
|
79
90
|
export class JiraClient {
|
|
80
91
|
baseUrl;
|
|
81
92
|
headers;
|
|
@@ -246,7 +257,7 @@ export class JiraClient {
|
|
|
246
257
|
return text(`${data.total} comment(s) on ${issueKey}${page}:\n\n${blocks.join('\n\n')}`);
|
|
247
258
|
}
|
|
248
259
|
async addComment(args) {
|
|
249
|
-
await this.request('POST', `/issue/${args.issueKey}/comment`, { body: args.body });
|
|
260
|
+
await this.request('POST', `/issue/${args.issueKey}/comment`, { body: validateCommentBody(args.body) });
|
|
250
261
|
return text(`Comment added to ${args.issueKey}.`);
|
|
251
262
|
}
|
|
252
263
|
async transitionIssue(args) {
|