@stubbedev/atlassian-mcp 0.1.18 → 0.2.1

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/dist/jira.js CHANGED
@@ -180,6 +180,12 @@ export class JiraClient {
180
180
  fields.assignee = { name: args.assignee };
181
181
  if (args.priority)
182
182
  fields.priority = { name: args.priority };
183
+ if (args.labels?.length)
184
+ fields.labels = args.labels;
185
+ if (args.fixVersion)
186
+ fields.fixVersions = [{ name: args.fixVersion }];
187
+ if (args.parent)
188
+ fields.parent = { key: args.parent };
183
189
  return this.request('POST', '/issue', { fields });
184
190
  }
185
191
  async updateIssueFieldsInternal(args) {
@@ -192,6 +198,10 @@ export class JiraClient {
192
198
  fields.assignee = { name: args.assignee };
193
199
  if (args.priority !== undefined)
194
200
  fields.priority = { name: args.priority };
201
+ if (args.labels !== undefined)
202
+ fields.labels = args.labels;
203
+ if (args.fixVersion !== undefined)
204
+ fields.fixVersions = args.fixVersion ? [{ name: args.fixVersion }] : [];
195
205
  if (Object.keys(fields).length === 0)
196
206
  return false;
197
207
  await this.request('PUT', `/issue/${args.issueKey}`, { fields });
@@ -319,6 +329,16 @@ export class JiraClient {
319
329
  .map((u, i) => `${i + 1}. ${u.displayName} (${u.name}) — ${u.emailAddress}`);
320
330
  return text(`${lines.length} user(s) found:\n${lines.join('\n')}`);
321
331
  }
332
+ async getIssueFields(issueKey) {
333
+ const data = await this.request('GET', `/issue/${issueKey}?fields=summary,status,issuetype`);
334
+ if (!data)
335
+ throw new Error(`Issue ${issueKey} not found`);
336
+ return {
337
+ summary: data.fields.summary,
338
+ status: data.fields.status.name,
339
+ type: data.fields.issuetype.name,
340
+ };
341
+ }
322
342
  async getIssue(args) {
323
343
  const fields = 'summary,description,status,assignee,priority,issuetype,labels,components';
324
344
  const data = await this.request('GET', `/issue/${args.issueKey}?fields=${fields}`);
@@ -346,7 +366,7 @@ export class JiraClient {
346
366
  const includeSprint = args.includeSprint ?? true;
347
367
  const commentsMaxResults = args.commentsMaxResults ?? 10;
348
368
  const commentsStartAt = args.commentsStartAt ?? 0;
349
- const fields = 'summary,description,status,assignee,priority,issuetype,labels,components';
369
+ const fields = 'summary,description,status,assignee,priority,issuetype,labels,components,parent,fixVersions,issuelinks,subtasks';
350
370
  const issue = await this.request('GET', `/issue/${args.issueKey}?fields=${fields}`);
351
371
  if (!issue)
352
372
  return text('Issue not found.');
@@ -360,6 +380,18 @@ export class JiraClient {
360
380
  `Assignee: ${f.assignee?.displayName ?? 'Unassigned'}`,
361
381
  `Labels: ${f.labels?.join(', ') || 'None'}`,
362
382
  `Components: ${f.components?.map((c) => c.name).join(', ') || 'None'}`,
383
+ ...(f.parent ? [`Parent: [${f.parent.key}] ${f.parent.fields.summary} (${f.parent.fields.issuetype.name})`] : []),
384
+ ...(f.fixVersions?.length ? [`Fix Vers: ${f.fixVersions.map((v) => v.name).join(', ')}`] : []),
385
+ ...(f.subtasks?.length ? [`Subtasks: ${f.subtasks.map((s) => `[${s.key}] ${s.fields.summary} (${s.fields.status.name})`).join(', ')}`] : []),
386
+ ...(f.issuelinks?.length ? [
387
+ `Links: ${f.issuelinks.map((l) => {
388
+ if (l.outwardIssue)
389
+ return `${l.type.outward} → [${l.outwardIssue.key}] ${l.outwardIssue.fields.summary}`;
390
+ if (l.inwardIssue)
391
+ return `${l.type.inward} ← [${l.inwardIssue.key}] ${l.inwardIssue.fields.summary}`;
392
+ return l.type.name;
393
+ }).join('; ')}`,
394
+ ] : []),
363
395
  ];
364
396
  if (includeSprint) {
365
397
  try {
@@ -550,6 +582,10 @@ export class JiraClient {
550
582
  await this.addIssuesToSprintInternal(args.sprintId, [issueKey]);
551
583
  actions.push(`added to sprint ${args.sprintId}`);
552
584
  }
585
+ if (args.removeFromSprint) {
586
+ await this.requestAgile('POST', '/backlog/issue', { issues: [issueKey] });
587
+ actions.push('moved to backlog');
588
+ }
553
589
  if (args.transitionId || args.transitionName) {
554
590
  const transitionId = await this.resolveTransitionId(issueKey, args.transitionId, args.transitionName);
555
591
  await this.transitionIssueInternal(issueKey, transitionId);
@@ -559,6 +595,24 @@ export class JiraClient {
559
595
  await this.request('POST', `/issue/${issueKey}/comment`, { body: validateCommentBody(args.comment) });
560
596
  actions.push('added comment');
561
597
  }
598
+ if (args.link) {
599
+ const dir = args.link.direction ?? 'outward';
600
+ await this.request('POST', '/issueLink', {
601
+ type: { name: args.link.linkType },
602
+ outwardIssue: { key: dir === 'outward' ? issueKey : args.link.targetIssueKey },
603
+ inwardIssue: { key: dir === 'outward' ? args.link.targetIssueKey : issueKey },
604
+ });
605
+ actions.push(`linked ${args.link.linkType} → ${args.link.targetIssueKey}`);
606
+ }
607
+ if (args.worklog) {
608
+ const wBody = { timeSpent: args.worklog.timeSpent };
609
+ if (args.worklog.comment)
610
+ wBody.comment = args.worklog.comment;
611
+ if (args.worklog.started)
612
+ wBody.started = args.worklog.started;
613
+ await this.request('POST', `/issue/${issueKey}/worklog`, wBody);
614
+ actions.push(`logged ${args.worklog.timeSpent}`);
615
+ }
562
616
  if (actions.length === 0) {
563
617
  return text('Nothing to mutate.');
564
618
  }
@@ -606,6 +660,23 @@ export class JiraClient {
606
660
  await this.request('DELETE', path);
607
661
  return text(`Comment ${commentId} deleted from ${args.issueKey}.`);
608
662
  }
663
+ async getBoards(args) {
664
+ const params = new URLSearchParams({
665
+ maxResults: String(args.maxResults ?? 25),
666
+ startAt: String(args.startAt ?? 0),
667
+ });
668
+ if (args.projectKey)
669
+ params.set('projectKeyOrId', args.projectKey);
670
+ const data = await this.requestAgile('GET', `/board?${params}`);
671
+ if (!data || data.values.length === 0)
672
+ return text('No boards found.');
673
+ const lines = data.values.map((b, i) => {
674
+ const projectHint = b.location?.projectKey ? ` [${b.location.projectKey}]` : '';
675
+ return `${(args.startAt ?? 0) + i + 1}. [${b.id}] ${b.name} (${b.type})${projectHint} | ${this.boardUrl(b.id)}`;
676
+ });
677
+ const page = data.isLast ? '' : ` (use startAt=${(args.startAt ?? 0) + data.values.length} for next page)`;
678
+ return text(`${data.values.length} board(s)${page}:\n${lines.join('\n')}`);
679
+ }
609
680
  async transitionIssue(args) {
610
681
  const transitionId = await this.resolveTransitionId(args.issueKey, args.transitionId, args.transitionName);
611
682
  await this.transitionIssueInternal(args.issueKey, transitionId);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stubbedev/atlassian-mcp",
3
- "version": "0.1.18",
3
+ "version": "0.2.1",
4
4
  "description": "MCP server for self-hosted Jira and Bitbucket",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -14,20 +14,25 @@
14
14
  "atlassian-mcp": "dist/index.js"
15
15
  },
16
16
  "scripts": {
17
- "prepare": "tsc",
18
17
  "build": "tsc",
19
18
  "start": "node dist/index.js",
20
- "dev": "tsc --watch",
19
+ "dev": "tsx src/index.ts",
20
+ "release:patch": "npm version patch && git push origin && git push origin --tags && gh release create $(git describe --tags) --generate-notes",
21
+ "release:minor": "npm version minor && git push origin && git push origin --tags && gh release create $(git describe --tags) --generate-notes",
22
+ "release:major": "npm version major && git push origin && git push origin --tags && gh release create $(git describe --tags) --generate-notes",
21
23
  "smoke": "npm run build && npm run smoke:tools",
22
- "smoke:tools": "printf '%s\\n' '{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"tools/list\",\"params\":{}}' | node dist/index.js"
24
+ "smoke:tools": "printf '%s\\n' '{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"tools/list\",\"params\":{}}' | node dist/index.js",
25
+ "smoke:validate": "printf '%s\\n' '{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"tools/list\",\"params\":{}}' | node dist/index.js | node -e \"let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{try{const r=JSON.parse(d.trim());if(r.error)throw new Error('server error: '+r.error.message);const t=r.result?.tools;if(!Array.isArray(t)||!t.length)throw new Error('no tools in response');const bad=t.filter(x=>!x.name||!x.inputSchema);if(bad.length)throw new Error('malformed tools: '+bad.map(x=>x.name??'(unnamed)').join(', '));console.log('smoke OK — '+t.length+' tool(s): '+t.map(x=>x.name).join(', '))}catch(e){console.error('smoke FAIL:',e.message);process.exit(1)}})\"",
26
+ "prerelease": "npm run build && npm run smoke:validate && ncu"
23
27
  },
24
28
  "dependencies": {
25
29
  "@modelcontextprotocol/sdk": "^1.29.0",
26
- "dotenv": "^16.0.0"
30
+ "dotenv": "^17.4.2"
27
31
  },
28
32
  "devDependencies": {
29
- "typescript": "^5.0.0",
30
- "@types/node": "^22.0.0"
33
+ "@types/node": "^25.6.0",
34
+ "npm-check-updates": "^21.0.2",
35
+ "typescript": "^6.0.3"
31
36
  },
32
37
  "engines": {
33
38
  "node": ">=18.0.0"