@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 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/blockers, and optional diff |
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 blocker comments and blocker counts |
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 (`NORMAL` / `BLOCKER`) |
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 ?? current.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: 'Comment text (plain text or Jira wiki markup)' },
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/blockers, and optional diff.',
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, blocker comments, and blocker counts with pagination. You can pass projectKey/repoSlug or project/repo.',
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. Use BLOCKER for blocker comments.', default: 'ALL' },
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: 'Comment text' },
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 set severity to BLOCKER. You can pass projectKey/repoSlug or project/repo.',
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) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stubbedev/atlassian-mcp",
3
- "version": "0.1.0",
3
+ "version": "0.1.4",
4
4
  "description": "MCP server for self-hosted Jira and Bitbucket",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",