@stubbedev/atlassian-mcp 0.3.5 → 0.3.6
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 +2 -0
- package/dist/index.js +3 -1
- package/dist/jira.js +62 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -67,6 +67,8 @@ A [Model Context Protocol](https://modelcontextprotocol.io) (MCP) server for **s
|
|
|
67
67
|
- "list releases for PAY" → `jira_search` with `resource=versions`, `project=PAY`
|
|
68
68
|
- "release version 12345" → `jira_version` with `action=release`, `id=12345`
|
|
69
69
|
- "set fix version 9.1.0 on FOO-123" → `jira_mutate` with `update.fixVersion=9.1.0`
|
|
70
|
+
- "create a task under epic FOO-100" → `jira_mutate` with `create.issueType=Task`, `create.parent=FOO-100` (auto-detects Epic and sets Epic Link)
|
|
71
|
+
- "move FOO-123 under epic FOO-100" → `jira_mutate` with `update.epicLink=FOO-100`
|
|
70
72
|
|
|
71
73
|
---
|
|
72
74
|
|
package/dist/index.js
CHANGED
|
@@ -261,7 +261,8 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
261
261
|
priority: { type: 'string', description: 'Priority name (optional)' },
|
|
262
262
|
labels: { type: 'array', items: { type: 'string' }, description: 'Labels to apply (optional)' },
|
|
263
263
|
fixVersion: { type: 'string', description: 'Fix version name (optional)' },
|
|
264
|
-
parent: { type: 'string', description: 'Parent issue key
|
|
264
|
+
parent: { type: 'string', description: 'Parent issue key. For Sub-task issue types this sets the Jira parent. If the key points to an Epic, the Epic Link custom field is set automatically instead (Jira Server epic membership).' },
|
|
265
|
+
epicLink: { type: 'string', description: 'Epic issue key to attach the new issue to as an epic child (Jira Server Epic Link). Overrides parent if both are passed.' },
|
|
265
266
|
},
|
|
266
267
|
required: ['issueType', 'summary'],
|
|
267
268
|
},
|
|
@@ -274,6 +275,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
274
275
|
priority: { type: 'string', description: 'New priority name (optional)' },
|
|
275
276
|
labels: { type: 'array', items: { type: 'string' }, description: 'Replace label set (pass [] to clear)' },
|
|
276
277
|
fixVersion: { type: 'string', description: 'Fix version name, or empty string to clear (optional)' },
|
|
278
|
+
epicLink: { type: 'string', description: 'Epic issue key to set as parent epic (Jira Server Epic Link), or empty string to clear.' },
|
|
277
279
|
},
|
|
278
280
|
},
|
|
279
281
|
sprintId: { type: 'number', description: 'Sprint ID to add the issue into (optional)' },
|
package/dist/jira.js
CHANGED
|
@@ -97,6 +97,8 @@ export class JiraClient {
|
|
|
97
97
|
currentUserCache;
|
|
98
98
|
projectsCache;
|
|
99
99
|
issueLinkingEnabled;
|
|
100
|
+
epicLinkFieldIdCache;
|
|
101
|
+
issueTypeCache = new Map();
|
|
100
102
|
constructor(baseUrl, token) {
|
|
101
103
|
this.baseUrl = baseUrl.replace(/\/$/, '');
|
|
102
104
|
this.headers = {
|
|
@@ -128,7 +130,17 @@ export class JiraClient {
|
|
|
128
130
|
const details = parseJiraErrorDetails(errText);
|
|
129
131
|
throw new Error(formatJiraError(res.status, method, path, details));
|
|
130
132
|
}
|
|
131
|
-
|
|
133
|
+
if (res.status === 204)
|
|
134
|
+
return null;
|
|
135
|
+
const raw = await res.text();
|
|
136
|
+
if (!raw)
|
|
137
|
+
return null;
|
|
138
|
+
try {
|
|
139
|
+
return JSON.parse(raw);
|
|
140
|
+
}
|
|
141
|
+
catch {
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
132
144
|
}
|
|
133
145
|
async request(method, path, body) {
|
|
134
146
|
return this.requestWithBase('/rest/api/2', method, path, body);
|
|
@@ -159,6 +171,24 @@ export class JiraClient {
|
|
|
159
171
|
this.issueLinkingEnabled = config?.issueLinkingEnabled ?? false;
|
|
160
172
|
return this.issueLinkingEnabled;
|
|
161
173
|
}
|
|
174
|
+
async getEpicLinkFieldId() {
|
|
175
|
+
if (this.epicLinkFieldIdCache !== undefined)
|
|
176
|
+
return this.epicLinkFieldIdCache;
|
|
177
|
+
const fields = (await this.request('GET', '/field')) ?? [];
|
|
178
|
+
const match = fields.find((f) => f.schema?.custom === 'com.pyxis.greenhopper.jira:gh-epic-link');
|
|
179
|
+
this.epicLinkFieldIdCache = match?.id ?? null;
|
|
180
|
+
return this.epicLinkFieldIdCache;
|
|
181
|
+
}
|
|
182
|
+
async getIssueType(issueKey) {
|
|
183
|
+
const cached = this.issueTypeCache.get(issueKey);
|
|
184
|
+
if (cached)
|
|
185
|
+
return cached;
|
|
186
|
+
const data = await this.request('GET', `/issue/${encodeURIComponent(issueKey)}?fields=issuetype`);
|
|
187
|
+
const name = data?.fields?.issuetype?.name ?? null;
|
|
188
|
+
if (name)
|
|
189
|
+
this.issueTypeCache.set(issueKey, name);
|
|
190
|
+
return name;
|
|
191
|
+
}
|
|
162
192
|
async assertOwnComment(comment) {
|
|
163
193
|
const me = await this.getCurrentUser();
|
|
164
194
|
const commentAuthorName = this.normalizeIdentity(comment.author.name);
|
|
@@ -200,8 +230,23 @@ export class JiraClient {
|
|
|
200
230
|
fields.labels = args.labels;
|
|
201
231
|
if (args.fixVersion)
|
|
202
232
|
fields.fixVersions = [{ name: args.fixVersion }];
|
|
203
|
-
|
|
204
|
-
|
|
233
|
+
let epicLinkTarget = args.epicLink?.trim() || undefined;
|
|
234
|
+
if (args.parent) {
|
|
235
|
+
const parentType = await this.getIssueType(args.parent);
|
|
236
|
+
if (parentType === 'Epic') {
|
|
237
|
+
epicLinkTarget = epicLinkTarget ?? args.parent;
|
|
238
|
+
}
|
|
239
|
+
else {
|
|
240
|
+
fields.parent = { key: args.parent };
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
if (epicLinkTarget) {
|
|
244
|
+
const epicFieldId = await this.getEpicLinkFieldId();
|
|
245
|
+
if (!epicFieldId) {
|
|
246
|
+
throw new Error('Epic Link custom field not found on this Jira instance. Set it manually in the Jira UI.');
|
|
247
|
+
}
|
|
248
|
+
fields[epicFieldId] = epicLinkTarget;
|
|
249
|
+
}
|
|
205
250
|
return this.request('POST', '/issue', { fields });
|
|
206
251
|
}
|
|
207
252
|
async updateIssueFieldsInternal(args) {
|
|
@@ -218,6 +263,13 @@ export class JiraClient {
|
|
|
218
263
|
fields.labels = args.labels;
|
|
219
264
|
if (args.fixVersion !== undefined)
|
|
220
265
|
fields.fixVersions = args.fixVersion ? [{ name: args.fixVersion }] : [];
|
|
266
|
+
if (args.epicLink !== undefined) {
|
|
267
|
+
const epicFieldId = await this.getEpicLinkFieldId();
|
|
268
|
+
if (!epicFieldId) {
|
|
269
|
+
throw new Error('Epic Link custom field not found on this Jira instance. Set it manually in the Jira UI.');
|
|
270
|
+
}
|
|
271
|
+
fields[epicFieldId] = args.epicLink ? args.epicLink : null;
|
|
272
|
+
}
|
|
221
273
|
if (Object.keys(fields).length === 0)
|
|
222
274
|
return false;
|
|
223
275
|
await this.request('PUT', `/issue/${encodeURIComponent(args.issueKey)}`, { fields });
|
|
@@ -401,11 +453,16 @@ export class JiraClient {
|
|
|
401
453
|
const includeSprint = args.includeSprint ?? true;
|
|
402
454
|
const commentsMaxResults = args.commentsMaxResults ?? 10;
|
|
403
455
|
const commentsStartAt = args.commentsStartAt ?? 0;
|
|
404
|
-
const
|
|
456
|
+
const baseFields = 'summary,description,status,assignee,priority,issuetype,labels,components,parent,fixVersions,issuelinks,subtasks,attachment';
|
|
457
|
+
const epicFieldId = await this.getEpicLinkFieldId();
|
|
458
|
+
const fields = epicFieldId ? `${baseFields},${epicFieldId}` : baseFields;
|
|
405
459
|
const issue = await this.request('GET', `/issue/${encodeURIComponent(args.issueKey)}?fields=${fields}`);
|
|
406
460
|
if (!issue)
|
|
407
461
|
return text('Issue not found.');
|
|
408
462
|
const f = issue.fields;
|
|
463
|
+
const epicLinkKey = epicFieldId && typeof f[epicFieldId] === 'string'
|
|
464
|
+
? f[epicFieldId]
|
|
465
|
+
: undefined;
|
|
409
466
|
const lines = [
|
|
410
467
|
`Issue: ${issue.key} — ${f.summary}`,
|
|
411
468
|
`URL: ${this.issueUrl(issue.key)}`,
|
|
@@ -416,6 +473,7 @@ export class JiraClient {
|
|
|
416
473
|
`Labels: ${f.labels?.join(', ') || 'None'}`,
|
|
417
474
|
`Components: ${f.components?.map((c) => c.name).join(', ') || 'None'}`,
|
|
418
475
|
...(f.parent ? [`Parent: [${f.parent.key}] ${f.parent.fields.summary} (${f.parent.fields.issuetype.name})`] : []),
|
|
476
|
+
...(epicLinkKey ? [`Epic Link: ${epicLinkKey}`] : []),
|
|
419
477
|
...(f.fixVersions?.length ? [`Fix Vers: ${f.fixVersions.map((v) => v.name).join(', ')}`] : []),
|
|
420
478
|
...(f.subtasks?.length ? [`Subtasks: ${f.subtasks.map((s) => `[${s.key}] ${s.fields.summary} (${s.fields.status.name})`).join(', ')}`] : []),
|
|
421
479
|
...(f.issuelinks?.length ? [
|