@stubbedev/atlassian-mcp 0.3.3 → 0.3.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
@@ -27,11 +27,12 @@ A [Model Context Protocol](https://modelcontextprotocol.io) (MCP) server for **s
27
27
 
28
28
  | Tool | Description |
29
29
  |---|---|
30
- | `jira_search` | Discover resources: `issues`, `projects`, `issue_types`, `boards`, `sprints`, `board_overview`, or `users` via `resource` param |
30
+ | `jira_search` | Discover resources: `issues`, `projects`, `issue_types`, `boards`, `sprints`, `board_overview`, `versions`, or `users` via `resource` param |
31
31
  | `jira_get` | Full details for one issue: summary, description, status, sprint, transitions, comments, and attachment list |
32
32
  | `jira_get_attachment` | Fetch a Jira attachment by ID; images are auto-resized via sharp and returned inline so the model can see them, text/JSON inline, larger/binary files via `saveTo` |
33
33
  | `jira_mutate` | Create, update, transition, comment, link, add to sprint, or log work — all in one call |
34
34
  | `jira_comment` | Add, update, or delete a comment on an issue (`action`: `add` / `update` / `delete`) |
35
+ | `jira_version` | Manage fix versions/releases (`action`: `create` / `update` / `release` / `archive` / `delete`) |
35
36
 
36
37
  ### Bitbucket
37
38
 
@@ -62,6 +63,10 @@ A [Model Context Protocol](https://modelcontextprotocol.io) (MCP) server for **s
62
63
  - "what's in the current sprint?" → `jira_search` with `resource=board_overview`
63
64
  - "move FOO-123 to In Progress" → `jira_mutate` with `transitionName="In Progress"`
64
65
  - "log 2h on FOO-123" → `jira_mutate` with `worklog`
66
+ - "create version 9.1.0 in PAY" → `jira_version` with `action=create`, `projectKey=PAY`, `name=9.1.0`
67
+ - "list releases for PAY" → `jira_search` with `resource=versions`, `project=PAY`
68
+ - "release version 12345" → `jira_version` with `action=release`, `id=12345`
69
+ - "set fix version 9.1.0 on FOO-123" → `jira_mutate` with `update.fixVersion=9.1.0`
65
70
 
66
71
  ---
67
72
 
package/dist/bitbucket.js CHANGED
@@ -810,6 +810,15 @@ export class BitbucketClient {
810
810
  await this.request('DELETE', `${this.rp(projectKey, repoSlug)}/pull-requests/${args.prId}/approve`);
811
811
  return text(`Approval removed from PR #${args.prId}.\n${this.pullRequestUrl(projectKey, repoSlug, args.prId)}`);
812
812
  }
813
+ async needsWorkPr(args) {
814
+ const { projectKey, repoSlug } = this.resolveProjectAndRepo(args.projectKey, args.repoSlug);
815
+ const userSlug = await this.getCurrentUsername();
816
+ const data = await this.request('PUT', `${this.rp(projectKey, repoSlug)}/pull-requests/${args.prId}/participants/${encodeURIComponent(userSlug)}`, { user: { name: userSlug }, approved: false, status: 'NEEDS_WORK' });
817
+ const url = this.pullRequestUrl(projectKey, repoSlug, args.prId);
818
+ if (!data)
819
+ return text(`Marked PR #${args.prId} as Needs work.\n${url}`);
820
+ return text(`Marked PR #${args.prId} as Needs work as ${data.user.displayName}.\n${url}`);
821
+ }
813
822
  async declinePr(args) {
814
823
  const { projectKey, repoSlug } = this.resolveProjectAndRepo(args.projectKey, args.repoSlug);
815
824
  const pr = await this.request('GET', `${this.rp(projectKey, repoSlug)}/pull-requests/${args.prId}`);
package/dist/index.js CHANGED
@@ -206,11 +206,11 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
206
206
  },
207
207
  {
208
208
  name: 'jira_search',
209
- description: 'Discover Jira resources. Use when asked "find tickets for...", "what\'s in the backlog", "show me my issues", "list projects", or "which board is for project X". Set resource:\n• "issues" (default) — search by text, JQL, project, status, assignee, issue type, or mine=true for your queue\n• "projects" — list all projects and their keys\n• "issue_types" — valid types and statuses for a project\n• "boards" — list boards (pass project to filter by project key); use this to find the boardId before fetching sprints or board_overview\n• "sprints" — sprints for a board (pass boardId); if you don\'t know the boardId, first use resource=boards\n• "board_overview" — active/future sprints with their issues for a board (pass boardId); use when asked "what\'s in the sprint", "show me the board", or "what\'s everyone working on"\n• "users" — find users by name/email (pass query)',
209
+ description: 'Discover Jira resources. Use when asked "find tickets for...", "what\'s in the backlog", "show me my issues", "list projects", or "which board is for project X". Set resource:\n• "issues" (default) — search by text, JQL, project, status, assignee, issue type, or mine=true for your queue\n• "projects" — list all projects and their keys\n• "issue_types" — valid types and statuses for a project\n• "boards" — list boards (pass project to filter by project key); use this to find the boardId before fetching sprints or board_overview\n• "sprints" — sprints for a board (pass boardId); if you don\'t know the boardId, first use resource=boards\n• "board_overview" — active/future sprints with their issues for a board (pass boardId); use when asked "what\'s in the sprint", "show me the board", or "what\'s everyone working on"\n• "versions" — list fix versions/releases for a project (pass project); use this to find the exact version name or id before setting fixVersion or releasing a version\n• "users" — find users by name/email (pass query)',
210
210
  inputSchema: {
211
211
  type: 'object',
212
212
  properties: {
213
- resource: { type: 'string', enum: ['issues', 'projects', 'issue_types', 'boards', 'sprints', 'board_overview', 'users'], description: 'What to search (default: issues)' },
213
+ resource: { type: 'string', enum: ['issues', 'projects', 'issue_types', 'boards', 'sprints', 'board_overview', 'versions', 'users'], description: 'What to search (default: issues)' },
214
214
  mine: { type: 'boolean', description: 'Return issues assigned to you (resource=issues only)' },
215
215
  query: { type: 'string', description: 'Text search or user name query' },
216
216
  jql: { type: 'string', description: 'Raw JQL (resource=issues only, overrides other filters)' },
@@ -331,6 +331,25 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
331
331
  },
332
332
  required: ['issueKey'],
333
333
  },
334
+ },
335
+ {
336
+ name: 'jira_version',
337
+ description: 'Manage Jira fix versions (releases). Use when asked to "create version 9.1.0", "release version X", "archive version X", "rename a version", or "delete a version". action defaults to "create". For create pass projectKey + name. For update/release/archive/delete pass id (look it up via jira_search resource=versions). "release" sets released=true and defaults releaseDate to today. Once a version exists you can set it on tickets via jira_mutate update.fixVersion.',
338
+ inputSchema: {
339
+ type: 'object',
340
+ properties: {
341
+ action: { type: 'string', enum: ['create', 'update', 'release', 'archive', 'delete'], description: 'Operation (default: create)' },
342
+ projectKey: { type: 'string', description: 'Jira project code (required for create when not auto-resolvable)' },
343
+ project: { type: 'string', description: 'Alias for projectKey' },
344
+ id: { type: 'string', description: 'Version id (required for update/release/archive/delete; look up via jira_search resource=versions)' },
345
+ name: { type: 'string', description: 'Version name, e.g. "9.1.0" (required for create; optional rename for update)' },
346
+ description: { type: 'string', description: 'Version description (optional)' },
347
+ startDate: { type: 'string', description: 'Start date in YYYY-MM-DD (optional)' },
348
+ releaseDate: { type: 'string', description: 'Release date in YYYY-MM-DD (optional; defaults to today on action=release)' },
349
+ released: { type: 'boolean', description: 'Released flag (optional; action=release forces true)' },
350
+ archived: { type: 'boolean', description: 'Archived flag (optional; action=archive forces true)' },
351
+ },
352
+ },
334
353
  }
335
354
  ] : []),
336
355
  ...(bitbucket ? [{
@@ -383,7 +402,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
383
402
  },
384
403
  {
385
404
  name: 'bitbucket_mutate',
386
- description: 'Use when asked to "open a PR for this branch", "create a pull request", "approve this PR", "merge it", "ship it", or "decline this PR". Auto-targets the open PR for the current branch when prId is omitted. Handles create, update, approve/unapprove, decline, and merge in one call.',
405
+ description: 'Use when asked to "open a PR for this branch", "create a pull request", "approve this PR", "request changes / mark needs work", "merge it", "ship it", or "decline this PR". Auto-targets the open PR for the current branch when prId is omitted. Handles create, update, approve/unapprove, needs_work, decline, and merge in one call. needs_work sets your reviewer status to "Needs work" (Bitbucket Server\'s changes-requested signal); revert with action=unapprove.',
387
406
  inputSchema: {
388
407
  type: 'object',
389
408
  properties: {
@@ -392,7 +411,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
392
411
  repoSlug: { type: 'string', description: 'Repository slug (usually auto-detected)' },
393
412
  repo: { type: 'string', description: 'Alias for repoSlug' },
394
413
  prId: { type: 'number', description: 'Target PR number (optional, auto-resolved from branch)' },
395
- action: { type: 'string', enum: ['approve', 'unapprove', 'decline', 'merge'], description: 'Lifecycle action to perform (optional)' },
414
+ action: { type: 'string', enum: ['approve', 'unapprove', 'needs_work', 'decline', 'merge'], description: 'Lifecycle action to perform (optional). needs_work = mark your reviewer status as "Needs work" (changes requested).' },
396
415
  mergeStrategy: { type: 'string', enum: ['MERGE_COMMIT', 'SQUASH', 'FAST_FORWARD'], description: 'Merge strategy (action=merge only)' },
397
416
  mergeMessage: { type: 'string', description: 'Custom merge commit message (action=merge only)' },
398
417
  declineMessage: { type: 'string', description: 'Decline message (action=decline only)' },
@@ -758,6 +777,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
758
777
  return await jira.getSprints({ boardId: a.boardId, state: a.sprintState, maxResults: a.maxResults, startAt: a.startAt });
759
778
  if (resource === 'board_overview')
760
779
  return await jira.boardOverview({ boardId: a.boardId, sprintState: a.sprintState, sprintMaxResults: a.maxResults, sprintStartAt: a.startAt, includeIssues: a.includeIssues, assignee: a.assignee, status: a.status });
780
+ if (resource === 'versions')
781
+ return await jira.listVersions({ projectKey: a.projectKey ?? a.project, maxResults: a.maxResults });
761
782
  if (resource === 'users')
762
783
  return await jira.searchUsers({ query: a.query ?? '', maxResults: a.maxResults });
763
784
  // issues (default)
@@ -790,6 +811,12 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
790
811
  return await jira.deleteComment({ issueKey: a.issueKey, commentId: a.commentId });
791
812
  return await jira.addComment({ issueKey: a.issueKey, body: a.body });
792
813
  }
814
+ case 'jira_version': {
815
+ if (!jira)
816
+ throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${name}`);
817
+ const a = normalizeJiraProjectArgs(args);
818
+ return await jira.mutateVersion(a);
819
+ }
793
820
  // Bitbucket
794
821
  case 'bitbucket_search': {
795
822
  if (!bitbucket)
@@ -820,6 +847,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
820
847
  return await bitbucket.approvePr(a);
821
848
  if (action === 'unapprove')
822
849
  return await bitbucket.unapprovePr(a);
850
+ if (action === 'needs_work')
851
+ return await bitbucket.needsWorkPr(a);
823
852
  if (action === 'decline')
824
853
  return await bitbucket.declinePr({ ...a, message: a.declineMessage });
825
854
  if (action === 'merge')
package/dist/jira.js CHANGED
@@ -761,4 +761,96 @@ export class JiraClient {
761
761
  await this.transitionIssueInternal(args.issueKey, transitionId);
762
762
  return text(`Transitioned ${args.issueKey} using transition ${transitionId}.\n${this.issueUrl(args.issueKey)}`);
763
763
  }
764
+ async listVersions(args) {
765
+ const projectKey = await this.resolveProjectKey(args.projectKey);
766
+ const data = await this.request('GET', `/project/${encodeURIComponent(projectKey)}/versions`);
767
+ if (!data || data.length === 0)
768
+ return text(`No versions in ${projectKey}.`);
769
+ const sorted = [...data].sort((a, b) => {
770
+ if (a.released !== b.released)
771
+ return a.released ? 1 : -1;
772
+ if (a.archived !== b.archived)
773
+ return a.archived ? 1 : -1;
774
+ const ad = a.releaseDate ?? '';
775
+ const bd = b.releaseDate ?? '';
776
+ return bd.localeCompare(ad);
777
+ });
778
+ const limit = args.maxResults ?? sorted.length;
779
+ const shown = sorted.slice(0, limit);
780
+ const lines = shown.map((v, i) => {
781
+ const tags = [];
782
+ if (v.released)
783
+ tags.push('released');
784
+ if (v.archived)
785
+ tags.push('archived');
786
+ const tagStr = tags.length ? ` [${tags.join(', ')}]` : '';
787
+ const dateParts = [v.startDate ? `start ${v.startDate}` : '', v.releaseDate ? `release ${v.releaseDate}` : ''].filter(Boolean);
788
+ const dateStr = dateParts.length ? ` (${dateParts.join(', ')})` : '';
789
+ return `${i + 1}. [${v.id}] ${v.name}${tagStr}${dateStr}`;
790
+ });
791
+ const more = data.length > shown.length ? `\n...and ${data.length - shown.length} more (raise maxResults).` : '';
792
+ return text(`${data.length} version(s) in ${projectKey}:\n${lines.join('\n')}${more}`);
793
+ }
794
+ async mutateVersion(args) {
795
+ const action = args.action ?? 'create';
796
+ if (action === 'create') {
797
+ const projectKey = await this.resolveProjectKey(args.projectKey);
798
+ const name = args.name?.trim();
799
+ if (!name)
800
+ throw new Error('name is required to create a version.');
801
+ const body = { project: projectKey, name };
802
+ if (args.description !== undefined)
803
+ body.description = args.description;
804
+ if (args.releaseDate)
805
+ body.releaseDate = args.releaseDate;
806
+ if (args.startDate)
807
+ body.startDate = args.startDate;
808
+ if (args.released !== undefined)
809
+ body.released = args.released;
810
+ if (args.archived !== undefined)
811
+ body.archived = args.archived;
812
+ const created = await this.request('POST', '/version', body);
813
+ if (!created)
814
+ throw new Error('Jira returned no body when creating version.');
815
+ return text(`Created version [${created.id}] ${created.name} in ${projectKey}.`);
816
+ }
817
+ const id = args.id?.trim();
818
+ if (!id)
819
+ throw new Error(`version id is required for action=${action}.`);
820
+ if (action === 'delete') {
821
+ await this.request('DELETE', `/version/${encodeURIComponent(id)}`);
822
+ return text(`Deleted version ${id}.`);
823
+ }
824
+ const body = {};
825
+ if (args.name !== undefined)
826
+ body.name = args.name;
827
+ if (args.description !== undefined)
828
+ body.description = args.description;
829
+ if (args.startDate !== undefined)
830
+ body.startDate = args.startDate;
831
+ if (args.releaseDate !== undefined)
832
+ body.releaseDate = args.releaseDate;
833
+ if (args.released !== undefined)
834
+ body.released = args.released;
835
+ if (args.archived !== undefined)
836
+ body.archived = args.archived;
837
+ if (action === 'release') {
838
+ body.released = true;
839
+ if (body.releaseDate === undefined)
840
+ body.releaseDate = new Date().toISOString().slice(0, 10);
841
+ }
842
+ else if (action === 'archive') {
843
+ body.archived = true;
844
+ }
845
+ if (Object.keys(body).length === 0) {
846
+ throw new Error('Nothing to update.');
847
+ }
848
+ const updated = await this.request('PUT', `/version/${encodeURIComponent(id)}`, body);
849
+ const label = updated ? `[${updated.id}] ${updated.name}` : id;
850
+ if (action === 'release')
851
+ return text(`Released version ${label} on ${body.releaseDate}.`);
852
+ if (action === 'archive')
853
+ return text(`Archived version ${label}.`);
854
+ return text(`Updated version ${label}.`);
855
+ }
764
856
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stubbedev/atlassian-mcp",
3
- "version": "0.3.3",
3
+ "version": "0.3.5",
4
4
  "description": "MCP server for self-hosted Jira and Bitbucket",
5
5
  "license": "MIT",
6
6
  "type": "module",