@rokealvo/jira-mcp 1.2.2 → 1.3.0
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/build/index.js +296 -122
- package/build/index.js.map +1 -1
- package/build/services/jira-api.d.ts +6 -1
- package/build/types/jira.d.ts +43 -0
- package/package.json +1 -1
package/build/index.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { Server as
|
|
3
|
-
import { StdioServerTransport as
|
|
4
|
-
import { ListPromptsRequestSchema as g, GetPromptRequestSchema as I, McpError as c, ErrorCode as
|
|
5
|
-
class
|
|
2
|
+
import { Server as h } from "@modelcontextprotocol/sdk/server/index.js";
|
|
3
|
+
import { StdioServerTransport as f } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
+
import { ListPromptsRequestSchema as g, GetPromptRequestSchema as I, McpError as c, ErrorCode as d, ListToolsRequestSchema as w, CallToolRequestSchema as _ } from "@modelcontextprotocol/sdk/types.js";
|
|
5
|
+
class m {
|
|
6
6
|
baseUrl;
|
|
7
7
|
headers;
|
|
8
8
|
constructor(t, e, s, i = "basic") {
|
|
9
9
|
this.baseUrl = t;
|
|
10
|
-
let
|
|
11
|
-
i === "bearer" ?
|
|
12
|
-
Authorization:
|
|
10
|
+
let r;
|
|
11
|
+
i === "bearer" ? r = `Bearer ${s}` : r = `Basic ${Buffer.from(`${e}:${s}`).toString("base64")}`, this.headers = new Headers({
|
|
12
|
+
Authorization: r,
|
|
13
13
|
Accept: "application/json",
|
|
14
14
|
"Content-Type": "application/json"
|
|
15
15
|
});
|
|
@@ -25,14 +25,14 @@ class y {
|
|
|
25
25
|
else if (i.errorMessage)
|
|
26
26
|
s = i.errorMessage;
|
|
27
27
|
else if (i.errors && typeof i.errors == "object") {
|
|
28
|
-
const
|
|
29
|
-
|
|
28
|
+
const r = Object.entries(i.errors).map(([a, n]) => `${a}: ${n}`).join("; ");
|
|
29
|
+
r && (s = r);
|
|
30
30
|
}
|
|
31
31
|
s === t.statusText && Object.keys(i).length > 0 && (s = JSON.stringify(i));
|
|
32
32
|
} catch {
|
|
33
33
|
try {
|
|
34
|
-
const
|
|
35
|
-
|
|
34
|
+
const a = await t.text();
|
|
35
|
+
a && (s = a.substring(0, 500));
|
|
36
36
|
} catch {
|
|
37
37
|
}
|
|
38
38
|
}
|
|
@@ -47,9 +47,9 @@ class y {
|
|
|
47
47
|
* Looks for nodes that were auto-converted to issue links
|
|
48
48
|
*/
|
|
49
49
|
extractIssueMentions(t, e, s) {
|
|
50
|
-
const i = [],
|
|
51
|
-
if (
|
|
52
|
-
const n =
|
|
50
|
+
const i = [], r = (a) => {
|
|
51
|
+
if (a.type === "inlineCard" && a.attrs?.url) {
|
|
52
|
+
const n = a.attrs.url.match(/\/browse\/([A-Z]+-\d+)/);
|
|
53
53
|
n && i.push({
|
|
54
54
|
key: n[1],
|
|
55
55
|
type: "mention",
|
|
@@ -57,16 +57,16 @@ class y {
|
|
|
57
57
|
commentId: s
|
|
58
58
|
});
|
|
59
59
|
}
|
|
60
|
-
|
|
60
|
+
a.type === "text" && a.text && (a.text.match(/[A-Z]+-\d+/g) || []).forEach((o) => {
|
|
61
61
|
i.push({
|
|
62
62
|
key: o,
|
|
63
63
|
type: "mention",
|
|
64
64
|
source: e,
|
|
65
65
|
commentId: s
|
|
66
66
|
});
|
|
67
|
-
}),
|
|
67
|
+
}), a.content && a.content.forEach(r);
|
|
68
68
|
};
|
|
69
|
-
return t.forEach(
|
|
69
|
+
return t.forEach(r), [...new Map(i.map((a) => [a.key, a])).values()];
|
|
70
70
|
}
|
|
71
71
|
cleanComment(t) {
|
|
72
72
|
const e = t.body?.content ? this.extractTextContent(t.body.content) : "", s = t.body?.content ? this.extractIssueMentions(t.body.content, "comment", t.id) : [];
|
|
@@ -118,11 +118,11 @@ class y {
|
|
|
118
118
|
i.length > 0 && (s.relatedIssues = i);
|
|
119
119
|
}
|
|
120
120
|
if (t.fields?.issuelinks?.length > 0) {
|
|
121
|
-
const i = t.fields.issuelinks.map((
|
|
122
|
-
const
|
|
121
|
+
const i = t.fields.issuelinks.map((r) => {
|
|
122
|
+
const a = r.inwardIssue || r.outwardIssue, n = r.type.inward || r.type.outward;
|
|
123
123
|
return {
|
|
124
|
-
key:
|
|
125
|
-
summary:
|
|
124
|
+
key: a.key,
|
|
125
|
+
summary: a.fields?.summary,
|
|
126
126
|
type: "link",
|
|
127
127
|
relationship: n,
|
|
128
128
|
source: "description"
|
|
@@ -154,7 +154,8 @@ class y {
|
|
|
154
154
|
...e,
|
|
155
155
|
headers: this.headers
|
|
156
156
|
});
|
|
157
|
-
|
|
157
|
+
if (s.ok || await this.handleFetchError(s, t), !(s.status === 204 || s.headers.get("content-length") === "0"))
|
|
158
|
+
return s.json();
|
|
158
159
|
}
|
|
159
160
|
async searchIssues(t) {
|
|
160
161
|
const e = new URLSearchParams({
|
|
@@ -200,17 +201,17 @@ class y {
|
|
|
200
201
|
expand: "names,renderedFields"
|
|
201
202
|
}), s = await this.fetchJson(`/rest/api/3/search?${e}`);
|
|
202
203
|
return await Promise.all(
|
|
203
|
-
s.issues.map(async (
|
|
204
|
-
const
|
|
205
|
-
`/rest/api/3/issue/${
|
|
206
|
-
), n = this.cleanIssue(
|
|
207
|
-
(
|
|
208
|
-
),
|
|
209
|
-
(
|
|
204
|
+
s.issues.map(async (r) => {
|
|
205
|
+
const a = await this.fetchJson(
|
|
206
|
+
`/rest/api/3/issue/${r.key}/comment`
|
|
207
|
+
), n = this.cleanIssue(r), o = a.comments.map(
|
|
208
|
+
(u) => this.cleanComment(u)
|
|
209
|
+
), p = o.flatMap(
|
|
210
|
+
(u) => u.mentions
|
|
210
211
|
);
|
|
211
212
|
return n.relatedIssues = [
|
|
212
213
|
...n.relatedIssues,
|
|
213
|
-
...
|
|
214
|
+
...p
|
|
214
215
|
], n.comments = o, n;
|
|
215
216
|
})
|
|
216
217
|
);
|
|
@@ -242,24 +243,24 @@ class y {
|
|
|
242
243
|
} catch (o) {
|
|
243
244
|
throw o instanceof Error && o.message.includes("(Status: 404)") ? new Error(`Issue not found: ${t}`) : o;
|
|
244
245
|
}
|
|
245
|
-
const
|
|
246
|
+
const r = this.cleanIssue(s), a = i.comments.map(
|
|
246
247
|
(o) => this.cleanComment(o)
|
|
247
|
-
), n =
|
|
248
|
+
), n = a.flatMap(
|
|
248
249
|
(o) => o.mentions
|
|
249
250
|
);
|
|
250
|
-
if (
|
|
251
|
+
if (r.relatedIssues = [...r.relatedIssues, ...n], r.comments = a, r.epicLink)
|
|
251
252
|
try {
|
|
252
253
|
const o = await this.fetchJson(
|
|
253
|
-
`/rest/api/3/issue/${
|
|
254
|
+
`/rest/api/3/issue/${r.epicLink.key}?fields=summary`
|
|
254
255
|
);
|
|
255
|
-
|
|
256
|
+
r.epicLink.summary = o.fields?.summary;
|
|
256
257
|
} catch (o) {
|
|
257
258
|
console.error("Failed to fetch epic details:", o);
|
|
258
259
|
}
|
|
259
|
-
return
|
|
260
|
+
return r;
|
|
260
261
|
}
|
|
261
|
-
async createIssue(t, e, s, i,
|
|
262
|
-
const
|
|
262
|
+
async createIssue(t, e, s, i, r) {
|
|
263
|
+
const a = {
|
|
263
264
|
fields: {
|
|
264
265
|
project: {
|
|
265
266
|
key: t
|
|
@@ -269,41 +270,41 @@ class y {
|
|
|
269
270
|
name: e
|
|
270
271
|
},
|
|
271
272
|
...i && { description: i },
|
|
272
|
-
...
|
|
273
|
+
...r
|
|
273
274
|
}
|
|
274
275
|
};
|
|
275
276
|
return this.fetchJson("/rest/api/3/issue", {
|
|
276
277
|
method: "POST",
|
|
277
|
-
body: JSON.stringify(
|
|
278
|
+
body: JSON.stringify(a)
|
|
278
279
|
});
|
|
279
280
|
}
|
|
280
281
|
async getCreateMeta(t, e, s) {
|
|
281
|
-
let
|
|
282
|
+
let r = (await this.fetchJson(
|
|
282
283
|
`/rest/api/3/issue/createmeta/${t}/issuetypes`
|
|
283
284
|
)).issueTypes || [];
|
|
284
|
-
return e && (
|
|
285
|
+
return e && (r = r.filter(
|
|
285
286
|
(n) => n.name.toLowerCase() === e.toLowerCase()
|
|
286
287
|
)), {
|
|
287
288
|
projectKey: t,
|
|
288
289
|
issueTypes: await Promise.all(
|
|
289
|
-
|
|
290
|
+
r.map(async (n) => {
|
|
290
291
|
try {
|
|
291
|
-
let
|
|
292
|
+
let p = (await this.fetchJson(
|
|
292
293
|
`/rest/api/3/issue/createmeta/${t}/issuetypes/${n.id}`
|
|
293
294
|
)).fields || [];
|
|
294
|
-
return s && (
|
|
295
|
-
fieldId:
|
|
296
|
-
name:
|
|
297
|
-
required:
|
|
298
|
-
schema:
|
|
299
|
-
hasDefaultValue:
|
|
300
|
-
allowedValuesCount:
|
|
295
|
+
return s && (p = p.map((u) => ({
|
|
296
|
+
fieldId: u.fieldId,
|
|
297
|
+
name: u.name,
|
|
298
|
+
required: u.required,
|
|
299
|
+
schema: u.schema,
|
|
300
|
+
hasDefaultValue: u.hasDefaultValue,
|
|
301
|
+
allowedValuesCount: u.allowedValues?.length ?? 0
|
|
301
302
|
}))), {
|
|
302
303
|
id: n.id,
|
|
303
304
|
name: n.name,
|
|
304
305
|
description: n.description,
|
|
305
306
|
subtask: n.subtask,
|
|
306
|
-
fields:
|
|
307
|
+
fields: p
|
|
307
308
|
};
|
|
308
309
|
} catch (o) {
|
|
309
310
|
return {
|
|
@@ -322,7 +323,7 @@ class y {
|
|
|
322
323
|
async getFieldOptions(t, e, s) {
|
|
323
324
|
return ((await this.fetchJson(
|
|
324
325
|
`/rest/api/3/issue/createmeta/${t}/issuetypes/${e}`
|
|
325
|
-
)).fields || []).find((
|
|
326
|
+
)).fields || []).find((a) => a.fieldId === s)?.allowedValues ?? [];
|
|
326
327
|
}
|
|
327
328
|
async updateIssue(t, e) {
|
|
328
329
|
await this.fetchJson(`/rest/api/3/issue/${t}`, {
|
|
@@ -369,18 +370,18 @@ class y {
|
|
|
369
370
|
async addAttachment(t, e, s) {
|
|
370
371
|
const i = new FormData();
|
|
371
372
|
i.append("file", new Blob([new Uint8Array(e)]), s);
|
|
372
|
-
const
|
|
373
|
-
|
|
374
|
-
const
|
|
373
|
+
const r = new Headers(this.headers);
|
|
374
|
+
r.delete("Content-Type"), r.set("X-Atlassian-Token", "no-check");
|
|
375
|
+
const a = await fetch(
|
|
375
376
|
`${this.baseUrl}/rest/api/3/issue/${t}/attachments`,
|
|
376
377
|
{
|
|
377
378
|
method: "POST",
|
|
378
|
-
headers:
|
|
379
|
+
headers: r,
|
|
379
380
|
body: i
|
|
380
381
|
}
|
|
381
382
|
);
|
|
382
|
-
|
|
383
|
-
const o = (await
|
|
383
|
+
a.ok || await this.handleFetchError(a);
|
|
384
|
+
const o = (await a.json())[0];
|
|
384
385
|
return {
|
|
385
386
|
id: o.id,
|
|
386
387
|
filename: o.filename
|
|
@@ -412,7 +413,7 @@ class y {
|
|
|
412
413
|
async addCommentToIssue(t, e) {
|
|
413
414
|
const i = {
|
|
414
415
|
body: this.createAdfFromBody(e)
|
|
415
|
-
},
|
|
416
|
+
}, r = await this.fetchJson(
|
|
416
417
|
`/rest/api/3/issue/${t}/comment`,
|
|
417
418
|
{
|
|
418
419
|
method: "POST",
|
|
@@ -420,11 +421,11 @@ class y {
|
|
|
420
421
|
}
|
|
421
422
|
);
|
|
422
423
|
return {
|
|
423
|
-
id:
|
|
424
|
-
author:
|
|
425
|
-
created:
|
|
426
|
-
updated:
|
|
427
|
-
body: this.extractTextContent(
|
|
424
|
+
id: r.id,
|
|
425
|
+
author: r.author.displayName,
|
|
426
|
+
created: r.created,
|
|
427
|
+
updated: r.updated,
|
|
428
|
+
body: this.extractTextContent(r.body.content)
|
|
428
429
|
};
|
|
429
430
|
}
|
|
430
431
|
async getServerInfo() {
|
|
@@ -447,11 +448,11 @@ class y {
|
|
|
447
448
|
query: t,
|
|
448
449
|
maxResults: e.toString()
|
|
449
450
|
});
|
|
450
|
-
return (await this.fetchJson(`/rest/api/3/user/search?${s}`)).map((
|
|
451
|
-
accountId:
|
|
452
|
-
displayName:
|
|
453
|
-
emailAddress:
|
|
454
|
-
active:
|
|
451
|
+
return (await this.fetchJson(`/rest/api/3/user/search?${s}`)).map((r) => ({
|
|
452
|
+
accountId: r.accountId,
|
|
453
|
+
displayName: r.displayName,
|
|
454
|
+
emailAddress: r.emailAddress,
|
|
455
|
+
active: r.active
|
|
455
456
|
}));
|
|
456
457
|
}
|
|
457
458
|
async deleteIssue(t) {
|
|
@@ -473,8 +474,30 @@ class y {
|
|
|
473
474
|
mimeType: e.mimeType
|
|
474
475
|
};
|
|
475
476
|
}
|
|
477
|
+
// ============================================================================
|
|
478
|
+
// TM4J / Zephyr Scale API Methods
|
|
479
|
+
// ============================================================================
|
|
480
|
+
tm4jBasePath = "/rest/atm/1.0";
|
|
481
|
+
async getTestCase(t) {
|
|
482
|
+
return this.fetchJson(`${this.tm4jBasePath}/testcase/${t}`);
|
|
483
|
+
}
|
|
484
|
+
async searchTestCases(t, e = 50) {
|
|
485
|
+
const s = encodeURIComponent(t);
|
|
486
|
+
return this.fetchJson(
|
|
487
|
+
`${this.tm4jBasePath}/testcase/search?query=${s}&maxResults=${e}`
|
|
488
|
+
);
|
|
489
|
+
}
|
|
490
|
+
async getTestRun(t) {
|
|
491
|
+
return this.fetchJson(`${this.tm4jBasePath}/testrun/${t}`);
|
|
492
|
+
}
|
|
493
|
+
async searchTestRuns(t, e = 50) {
|
|
494
|
+
const s = encodeURIComponent(t);
|
|
495
|
+
return this.fetchJson(
|
|
496
|
+
`${this.tm4jBasePath}/testrun/search?query=${s}&maxResults=${e}`
|
|
497
|
+
);
|
|
498
|
+
}
|
|
476
499
|
}
|
|
477
|
-
class b extends
|
|
500
|
+
class b extends m {
|
|
478
501
|
constructor(t, e, s, i = "basic") {
|
|
479
502
|
super(t, e, s, i);
|
|
480
503
|
}
|
|
@@ -489,32 +512,32 @@ class b extends y {
|
|
|
489
512
|
}
|
|
490
513
|
// Override getCreateMeta to use API v2 endpoints for Jira Server 9.0+
|
|
491
514
|
async getCreateMeta(t, e, s) {
|
|
492
|
-
let
|
|
515
|
+
let r = (await this.fetchJson(
|
|
493
516
|
`/rest/api/3/issue/createmeta/${t}/issuetypes`
|
|
494
517
|
)).values || [];
|
|
495
|
-
return e && (
|
|
518
|
+
return e && (r = r.filter(
|
|
496
519
|
(n) => n.name.toLowerCase() === e.toLowerCase()
|
|
497
520
|
)), {
|
|
498
521
|
projectKey: t,
|
|
499
522
|
issueTypes: await Promise.all(
|
|
500
|
-
|
|
523
|
+
r.map(async (n) => {
|
|
501
524
|
try {
|
|
502
|
-
let
|
|
525
|
+
let p = (await this.fetchJson(
|
|
503
526
|
`/rest/api/3/issue/createmeta/${t}/issuetypes/${n.id}`
|
|
504
527
|
)).values || [];
|
|
505
|
-
return s && (
|
|
506
|
-
fieldId:
|
|
507
|
-
name:
|
|
508
|
-
required:
|
|
509
|
-
schema:
|
|
510
|
-
hasDefaultValue:
|
|
511
|
-
allowedValuesCount:
|
|
528
|
+
return s && (p = p.map((u) => ({
|
|
529
|
+
fieldId: u.fieldId,
|
|
530
|
+
name: u.name,
|
|
531
|
+
required: u.required,
|
|
532
|
+
schema: u.schema,
|
|
533
|
+
hasDefaultValue: u.hasDefaultValue,
|
|
534
|
+
allowedValuesCount: u.allowedValues?.length ?? 0
|
|
512
535
|
}))), {
|
|
513
536
|
id: n.id,
|
|
514
537
|
name: n.name,
|
|
515
538
|
description: n.description,
|
|
516
539
|
subtask: n.subtask,
|
|
517
|
-
fields:
|
|
540
|
+
fields: p
|
|
518
541
|
};
|
|
519
542
|
} catch (o) {
|
|
520
543
|
return {
|
|
@@ -534,7 +557,7 @@ class b extends y {
|
|
|
534
557
|
async getFieldOptions(t, e, s) {
|
|
535
558
|
return ((await this.fetchJson(
|
|
536
559
|
`/rest/api/3/issue/createmeta/${t}/issuetypes/${e}`
|
|
537
|
-
)).values || []).find((
|
|
560
|
+
)).values || []).find((a) => a.fieldId === s)?.allowedValues ?? [];
|
|
538
561
|
}
|
|
539
562
|
// Override getUsers for Jira Server (uses 'username' parameter instead of 'query')
|
|
540
563
|
async getUsers(t, e = 50) {
|
|
@@ -542,12 +565,12 @@ class b extends y {
|
|
|
542
565
|
username: t,
|
|
543
566
|
maxResults: e.toString()
|
|
544
567
|
});
|
|
545
|
-
return (await this.fetchJson(`/rest/api/3/user/search?${s}`)).map((
|
|
568
|
+
return (await this.fetchJson(`/rest/api/3/user/search?${s}`)).map((r) => ({
|
|
546
569
|
// Jira Server uses 'name' or 'key' instead of 'accountId'
|
|
547
|
-
accountId:
|
|
548
|
-
displayName:
|
|
549
|
-
emailAddress:
|
|
550
|
-
active:
|
|
570
|
+
accountId: r.key || r.name,
|
|
571
|
+
displayName: r.displayName,
|
|
572
|
+
emailAddress: r.emailAddress,
|
|
573
|
+
active: r.active
|
|
551
574
|
}));
|
|
552
575
|
}
|
|
553
576
|
// Override addCommentToIssue for Jira Server (uses plain text instead of ADF)
|
|
@@ -594,15 +617,15 @@ class b extends y {
|
|
|
594
617
|
extractIssueMentionsFromText(t, e, s) {
|
|
595
618
|
if (!t) return [];
|
|
596
619
|
const i = t.match(/[A-Z]+-\d+/g) || [];
|
|
597
|
-
return [...new Set(i)].map((
|
|
598
|
-
key:
|
|
620
|
+
return [...new Set(i)].map((a) => ({
|
|
621
|
+
key: a,
|
|
599
622
|
type: "mention",
|
|
600
623
|
source: e,
|
|
601
624
|
commentId: s
|
|
602
625
|
}));
|
|
603
626
|
}
|
|
604
627
|
}
|
|
605
|
-
function
|
|
628
|
+
function T() {
|
|
606
629
|
const l = process.env.JIRA_API_TOKEN ?? "", t = process.env.JIRA_BASE_URL ?? "", e = process.env.JIRA_USER_EMAIL ?? "", s = process.env.JIRA_TYPE === "server" ? "server" : "cloud", i = process.env.JIRA_AUTH_TYPE === "bearer" ? "bearer" : "basic";
|
|
607
630
|
if (!l || !t || !e)
|
|
608
631
|
throw new Error(
|
|
@@ -610,7 +633,7 @@ function A() {
|
|
|
610
633
|
);
|
|
611
634
|
return { JIRA_API_TOKEN: l, JIRA_BASE_URL: t, JIRA_USER_EMAIL: e, JIRA_TYPE: s, JIRA_AUTH_TYPE: i };
|
|
612
635
|
}
|
|
613
|
-
const
|
|
636
|
+
const y = {
|
|
614
637
|
jira_guidelines: {
|
|
615
638
|
description: "Общие правила и регламенты работы с Jira в компании",
|
|
616
639
|
content: `# Регламент работы с Jira
|
|
@@ -863,7 +886,7 @@ class S {
|
|
|
863
886
|
server;
|
|
864
887
|
jiraApi;
|
|
865
888
|
constructor() {
|
|
866
|
-
this.server = new
|
|
889
|
+
this.server = new h(
|
|
867
890
|
{
|
|
868
891
|
name: "jira-mcp",
|
|
869
892
|
version: "0.3.0"
|
|
@@ -875,32 +898,32 @@ class S {
|
|
|
875
898
|
}
|
|
876
899
|
}
|
|
877
900
|
);
|
|
878
|
-
const { JIRA_API_TOKEN: t, JIRA_BASE_URL: e, JIRA_USER_EMAIL: s, JIRA_TYPE: i, JIRA_AUTH_TYPE:
|
|
901
|
+
const { JIRA_API_TOKEN: t, JIRA_BASE_URL: e, JIRA_USER_EMAIL: s, JIRA_TYPE: i, JIRA_AUTH_TYPE: r } = T();
|
|
879
902
|
i === "server" ? this.jiraApi = new b(
|
|
880
903
|
e,
|
|
881
904
|
s,
|
|
882
905
|
t,
|
|
883
|
-
|
|
884
|
-
) : this.jiraApi = new
|
|
906
|
+
r
|
|
907
|
+
) : this.jiraApi = new m(
|
|
885
908
|
e,
|
|
886
909
|
s,
|
|
887
910
|
t,
|
|
888
|
-
|
|
889
|
-
), this.setupToolHandlers(), this.server.onerror = (
|
|
911
|
+
r
|
|
912
|
+
), this.setupToolHandlers(), this.server.onerror = (a) => {
|
|
890
913
|
}, process.on("SIGINT", async () => {
|
|
891
914
|
await this.server.close(), process.exit(0);
|
|
892
915
|
});
|
|
893
916
|
}
|
|
894
917
|
setupToolHandlers() {
|
|
895
918
|
this.server.setRequestHandler(g, async () => ({
|
|
896
|
-
prompts: Object.entries(
|
|
919
|
+
prompts: Object.entries(y).map(([t, { description: e }]) => ({
|
|
897
920
|
name: t,
|
|
898
921
|
description: e
|
|
899
922
|
}))
|
|
900
923
|
})), this.server.setRequestHandler(I, async (t) => {
|
|
901
|
-
const { name: e } = t.params, s =
|
|
924
|
+
const { name: e } = t.params, s = y[e];
|
|
902
925
|
if (!s)
|
|
903
|
-
throw new c(
|
|
926
|
+
throw new c(d.InvalidParams, `Unknown prompt: ${e}`);
|
|
904
927
|
return {
|
|
905
928
|
messages: [
|
|
906
929
|
{
|
|
@@ -1027,7 +1050,13 @@ See prompt "update_issue_workflow" for guidelines.`,
|
|
|
1027
1050
|
- Only transitions returned by this tool are valid
|
|
1028
1051
|
- Transition availability depends on workflow rules (e.g., user can only have one task "В работе")
|
|
1029
1052
|
- If desired transition is not in the list, inform the user — do NOT guess or invent transition names/IDs
|
|
1030
|
-
- Returns array with: id (use this for transition_issue), name, to.name (target status)
|
|
1053
|
+
- Returns array with: id (use this for transition_issue), name, to.name (target status)
|
|
1054
|
+
|
|
1055
|
+
⚠️ STATUS MISMATCH RULE: When user requests a specific status (e.g., "в работу", "на ревью"):
|
|
1056
|
+
1. Compare available transitions with the requested status
|
|
1057
|
+
2. If exact match NOT found — ALWAYS ASK the user which status to use
|
|
1058
|
+
3. Present the list of available transitions to choose from
|
|
1059
|
+
4. NEVER silently choose a "similar" status on your own`,
|
|
1031
1060
|
inputSchema: {
|
|
1032
1061
|
type: "object",
|
|
1033
1062
|
properties: {
|
|
@@ -1051,6 +1080,13 @@ See prompt "update_issue_workflow" for guidelines.`,
|
|
|
1051
1080
|
- Current status may not allow direct transition to target status
|
|
1052
1081
|
- User permissions may limit available transitions
|
|
1053
1082
|
|
|
1083
|
+
⚠️ STATUS MISMATCH RULE:
|
|
1084
|
+
- If user requested a specific status that is NOT in available transitions:
|
|
1085
|
+
- STOP and ASK user which available status to use
|
|
1086
|
+
- List all available transitions with their target statuses
|
|
1087
|
+
- Explain why requested status may be unavailable
|
|
1088
|
+
- NEVER silently substitute a different status — this confuses users!
|
|
1089
|
+
|
|
1054
1090
|
⚠️ CANCELLATION RULE: When transitioning to "Отмена" (Cancel), you MUST first:
|
|
1055
1091
|
1. Ask user for cancellation reason
|
|
1056
1092
|
2. Add comment with the reason using add_comment
|
|
@@ -1234,6 +1270,83 @@ Remember to add a comment with cancellation reason first.`,
|
|
|
1234
1270
|
required: ["attachmentId"],
|
|
1235
1271
|
additionalProperties: !1
|
|
1236
1272
|
}
|
|
1273
|
+
},
|
|
1274
|
+
// ====================================================================
|
|
1275
|
+
// TM4J / Zephyr Scale Tools
|
|
1276
|
+
// ====================================================================
|
|
1277
|
+
{
|
|
1278
|
+
name: "search_test_cases",
|
|
1279
|
+
description: `Search TM4J/Zephyr Scale test cases using query.
|
|
1280
|
+
|
|
1281
|
+
Supported query fields: projectKey (required), folder, status, priority, component.
|
|
1282
|
+
Example: projectKey = "IM3" AND status = "Approved"
|
|
1283
|
+
|
|
1284
|
+
⚠️ LIMITATION: Full-text search is NOT supported. Queries like name ~ "keyword" do NOT work.
|
|
1285
|
+
To search by name: fetch all test cases for project, then filter results on your side.`,
|
|
1286
|
+
inputSchema: {
|
|
1287
|
+
type: "object",
|
|
1288
|
+
properties: {
|
|
1289
|
+
query: {
|
|
1290
|
+
type: "string",
|
|
1291
|
+
description: 'TM4J query string (e.g., projectKey = "IM3")'
|
|
1292
|
+
},
|
|
1293
|
+
maxResults: {
|
|
1294
|
+
type: "number",
|
|
1295
|
+
description: "Maximum number of results to return (default: 50)"
|
|
1296
|
+
}
|
|
1297
|
+
},
|
|
1298
|
+
required: ["query"],
|
|
1299
|
+
additionalProperties: !1
|
|
1300
|
+
}
|
|
1301
|
+
},
|
|
1302
|
+
{
|
|
1303
|
+
name: "get_test_case",
|
|
1304
|
+
description: "Get a TM4J/Zephyr Scale test case by key (e.g., IM3-T9). Returns test case details including steps.",
|
|
1305
|
+
inputSchema: {
|
|
1306
|
+
type: "object",
|
|
1307
|
+
properties: {
|
|
1308
|
+
testCaseKey: {
|
|
1309
|
+
type: "string",
|
|
1310
|
+
description: "Test case key (e.g., IM3-T9)"
|
|
1311
|
+
}
|
|
1312
|
+
},
|
|
1313
|
+
required: ["testCaseKey"],
|
|
1314
|
+
additionalProperties: !1
|
|
1315
|
+
}
|
|
1316
|
+
},
|
|
1317
|
+
{
|
|
1318
|
+
name: "search_test_runs",
|
|
1319
|
+
description: 'Search TM4J/Zephyr Scale test runs/cycles. Example: projectKey = "IM3"',
|
|
1320
|
+
inputSchema: {
|
|
1321
|
+
type: "object",
|
|
1322
|
+
properties: {
|
|
1323
|
+
query: {
|
|
1324
|
+
type: "string",
|
|
1325
|
+
description: 'TM4J query string (e.g., projectKey = "IM3")'
|
|
1326
|
+
},
|
|
1327
|
+
maxResults: {
|
|
1328
|
+
type: "number",
|
|
1329
|
+
description: "Maximum number of results to return (default: 50)"
|
|
1330
|
+
}
|
|
1331
|
+
},
|
|
1332
|
+
required: ["query"],
|
|
1333
|
+
additionalProperties: !1
|
|
1334
|
+
}
|
|
1335
|
+
},
|
|
1336
|
+
{
|
|
1337
|
+
name: "get_test_run",
|
|
1338
|
+
description: "Get a TM4J/Zephyr Scale test run by key. Returns test run details including all test case items and their execution status.",
|
|
1339
|
+
inputSchema: {
|
|
1340
|
+
type: "object",
|
|
1341
|
+
properties: {
|
|
1342
|
+
testRunKey: {
|
|
1343
|
+
type: "string",
|
|
1344
|
+
description: "Test run key"
|
|
1345
|
+
}
|
|
1346
|
+
},
|
|
1347
|
+
required: ["testRunKey"],
|
|
1348
|
+
additionalProperties: !1
|
|
1349
|
+
}
|
|
1237
1350
|
}
|
|
1238
1351
|
]
|
|
1239
1352
|
})), this.server.setRequestHandler(_, async (t) => {
|
|
@@ -1243,7 +1356,7 @@ Remember to add a comment with cancellation reason first.`,
|
|
|
1243
1356
|
case "search_issues": {
|
|
1244
1357
|
if (!e.searchString || typeof e.searchString != "string")
|
|
1245
1358
|
throw new c(
|
|
1246
|
-
|
|
1359
|
+
d.InvalidParams,
|
|
1247
1360
|
"Search string is required"
|
|
1248
1361
|
);
|
|
1249
1362
|
const s = await this.jiraApi.searchIssues(e.searchString);
|
|
@@ -1256,7 +1369,7 @@ Remember to add a comment with cancellation reason first.`,
|
|
|
1256
1369
|
case "get_epic_children": {
|
|
1257
1370
|
if (!e.epicKey || typeof e.epicKey != "string")
|
|
1258
1371
|
throw new c(
|
|
1259
|
-
|
|
1372
|
+
d.InvalidParams,
|
|
1260
1373
|
"Epic key is required"
|
|
1261
1374
|
);
|
|
1262
1375
|
const s = await this.jiraApi.getEpicChildren(e.epicKey);
|
|
@@ -1269,7 +1382,7 @@ Remember to add a comment with cancellation reason first.`,
|
|
|
1269
1382
|
case "get_issue": {
|
|
1270
1383
|
if (!e.issueId || typeof e.issueId != "string")
|
|
1271
1384
|
throw new c(
|
|
1272
|
-
|
|
1385
|
+
d.InvalidParams,
|
|
1273
1386
|
"Issue ID is required"
|
|
1274
1387
|
);
|
|
1275
1388
|
const s = await this.jiraApi.getIssueWithComments(
|
|
@@ -1284,7 +1397,7 @@ Remember to add a comment with cancellation reason first.`,
|
|
|
1284
1397
|
case "create_issue": {
|
|
1285
1398
|
if (!e.projectKey || typeof e.projectKey != "string" || !e.issueType || typeof e.issueType != "string" || !e.summary || typeof e.summary != "string")
|
|
1286
1399
|
throw new c(
|
|
1287
|
-
|
|
1400
|
+
d.InvalidParams,
|
|
1288
1401
|
"projectKey, issueType, and summary are required"
|
|
1289
1402
|
);
|
|
1290
1403
|
const s = await this.jiraApi.createIssue(
|
|
@@ -1303,7 +1416,7 @@ Remember to add a comment with cancellation reason first.`,
|
|
|
1303
1416
|
case "update_issue": {
|
|
1304
1417
|
if (!e.issueKey || typeof e.issueKey != "string" || !e.fields || typeof e.fields != "object")
|
|
1305
1418
|
throw new c(
|
|
1306
|
-
|
|
1419
|
+
d.InvalidParams,
|
|
1307
1420
|
"issueKey and fields object are required"
|
|
1308
1421
|
);
|
|
1309
1422
|
return await this.jiraApi.updateIssue(e.issueKey, e.fields), {
|
|
@@ -1322,7 +1435,7 @@ Remember to add a comment with cancellation reason first.`,
|
|
|
1322
1435
|
case "get_transitions": {
|
|
1323
1436
|
if (!e.issueKey || typeof e.issueKey != "string")
|
|
1324
1437
|
throw new c(
|
|
1325
|
-
|
|
1438
|
+
d.InvalidParams,
|
|
1326
1439
|
"Issue key is required"
|
|
1327
1440
|
);
|
|
1328
1441
|
const s = await this.jiraApi.getTransitions(e.issueKey);
|
|
@@ -1335,7 +1448,7 @@ Remember to add a comment with cancellation reason first.`,
|
|
|
1335
1448
|
case "transition_issue": {
|
|
1336
1449
|
if (!e.issueKey || typeof e.issueKey != "string" || !e.transitionId || typeof e.transitionId != "string")
|
|
1337
1450
|
throw new c(
|
|
1338
|
-
|
|
1451
|
+
d.InvalidParams,
|
|
1339
1452
|
"issueKey and transitionId are required"
|
|
1340
1453
|
);
|
|
1341
1454
|
return await this.jiraApi.transitionIssue(
|
|
@@ -1360,7 +1473,7 @@ Remember to add a comment with cancellation reason first.`,
|
|
|
1360
1473
|
case "add_attachment": {
|
|
1361
1474
|
if (!e.issueKey || typeof e.issueKey != "string" || !e.fileContent || typeof e.fileContent != "string" || !e.filename || typeof e.filename != "string")
|
|
1362
1475
|
throw new c(
|
|
1363
|
-
|
|
1476
|
+
d.InvalidParams,
|
|
1364
1477
|
"issueKey, fileContent, and filename are required"
|
|
1365
1478
|
);
|
|
1366
1479
|
const s = Buffer.from(e.fileContent, "base64"), i = await this.jiraApi.addAttachment(
|
|
@@ -1388,7 +1501,7 @@ Remember to add a comment with cancellation reason first.`,
|
|
|
1388
1501
|
case "add_comment": {
|
|
1389
1502
|
if (!e.issueIdOrKey || typeof e.issueIdOrKey != "string" || !e.body || typeof e.body != "string")
|
|
1390
1503
|
throw new c(
|
|
1391
|
-
|
|
1504
|
+
d.InvalidParams,
|
|
1392
1505
|
"issueIdOrKey and body are required"
|
|
1393
1506
|
);
|
|
1394
1507
|
const s = await this.jiraApi.addCommentToIssue(
|
|
@@ -1404,7 +1517,7 @@ Remember to add a comment with cancellation reason first.`,
|
|
|
1404
1517
|
case "get_create_meta": {
|
|
1405
1518
|
if (!e.projectKey || typeof e.projectKey != "string")
|
|
1406
1519
|
throw new c(
|
|
1407
|
-
|
|
1520
|
+
d.InvalidParams,
|
|
1408
1521
|
"projectKey is required"
|
|
1409
1522
|
);
|
|
1410
1523
|
const s = await this.jiraApi.getCreateMeta(
|
|
@@ -1421,7 +1534,7 @@ Remember to add a comment with cancellation reason first.`,
|
|
|
1421
1534
|
case "get_field_options": {
|
|
1422
1535
|
if (!e.projectKey || typeof e.projectKey != "string" || !e.issueTypeId || typeof e.issueTypeId != "string" || !e.fieldId || typeof e.fieldId != "string")
|
|
1423
1536
|
throw new c(
|
|
1424
|
-
|
|
1537
|
+
d.InvalidParams,
|
|
1425
1538
|
"projectKey, issueTypeId, and fieldId are required"
|
|
1426
1539
|
);
|
|
1427
1540
|
const s = await this.jiraApi.getFieldOptions(
|
|
@@ -1454,7 +1567,7 @@ Remember to add a comment with cancellation reason first.`,
|
|
|
1454
1567
|
case "get_users": {
|
|
1455
1568
|
if (!e.query || typeof e.query != "string")
|
|
1456
1569
|
throw new c(
|
|
1457
|
-
|
|
1570
|
+
d.InvalidParams,
|
|
1458
1571
|
"query is required"
|
|
1459
1572
|
);
|
|
1460
1573
|
const s = await this.jiraApi.getUsers(
|
|
@@ -1470,7 +1583,7 @@ Remember to add a comment with cancellation reason first.`,
|
|
|
1470
1583
|
case "delete_issue": {
|
|
1471
1584
|
if (!e.issueKey || typeof e.issueKey != "string")
|
|
1472
1585
|
throw new c(
|
|
1473
|
-
|
|
1586
|
+
d.InvalidParams,
|
|
1474
1587
|
"issueKey is required"
|
|
1475
1588
|
);
|
|
1476
1589
|
return await this.jiraApi.deleteIssue(e.issueKey), {
|
|
@@ -1489,7 +1602,7 @@ Remember to add a comment with cancellation reason first.`,
|
|
|
1489
1602
|
case "get_attachment": {
|
|
1490
1603
|
if (!e.attachmentId || typeof e.attachmentId != "string")
|
|
1491
1604
|
throw new c(
|
|
1492
|
-
|
|
1605
|
+
d.InvalidParams,
|
|
1493
1606
|
"attachmentId is required"
|
|
1494
1607
|
);
|
|
1495
1608
|
const s = await this.jiraApi.getAttachment(e.attachmentId);
|
|
@@ -1514,27 +1627,88 @@ Remember to add a comment with cancellation reason first.`,
|
|
|
1514
1627
|
]
|
|
1515
1628
|
};
|
|
1516
1629
|
}
|
|
1630
|
+
// ================================================================
|
|
1631
|
+
// TM4J / Zephyr Scale Tool Handlers
|
|
1632
|
+
// ================================================================
|
|
1633
|
+
case "search_test_cases": {
|
|
1634
|
+
if (!e.query || typeof e.query != "string")
|
|
1635
|
+
throw new c(
|
|
1636
|
+
d.InvalidParams,
|
|
1637
|
+
"query is required"
|
|
1638
|
+
);
|
|
1639
|
+
const s = await this.jiraApi.searchTestCases(
|
|
1640
|
+
e.query,
|
|
1641
|
+
typeof e.maxResults == "number" ? e.maxResults : 50
|
|
1642
|
+
);
|
|
1643
|
+
return {
|
|
1644
|
+
content: [
|
|
1645
|
+
{ type: "text", text: JSON.stringify(s, null, 2) }
|
|
1646
|
+
]
|
|
1647
|
+
};
|
|
1648
|
+
}
|
|
1649
|
+
case "get_test_case": {
|
|
1650
|
+
if (!e.testCaseKey || typeof e.testCaseKey != "string")
|
|
1651
|
+
throw new c(
|
|
1652
|
+
d.InvalidParams,
|
|
1653
|
+
"testCaseKey is required"
|
|
1654
|
+
);
|
|
1655
|
+
const s = await this.jiraApi.getTestCase(e.testCaseKey);
|
|
1656
|
+
return {
|
|
1657
|
+
content: [
|
|
1658
|
+
{ type: "text", text: JSON.stringify(s, null, 2) }
|
|
1659
|
+
]
|
|
1660
|
+
};
|
|
1661
|
+
}
|
|
1662
|
+
case "search_test_runs": {
|
|
1663
|
+
if (!e.query || typeof e.query != "string")
|
|
1664
|
+
throw new c(
|
|
1665
|
+
d.InvalidParams,
|
|
1666
|
+
"query is required"
|
|
1667
|
+
);
|
|
1668
|
+
const s = await this.jiraApi.searchTestRuns(
|
|
1669
|
+
e.query,
|
|
1670
|
+
typeof e.maxResults == "number" ? e.maxResults : 50
|
|
1671
|
+
);
|
|
1672
|
+
return {
|
|
1673
|
+
content: [
|
|
1674
|
+
{ type: "text", text: JSON.stringify(s, null, 2) }
|
|
1675
|
+
]
|
|
1676
|
+
};
|
|
1677
|
+
}
|
|
1678
|
+
case "get_test_run": {
|
|
1679
|
+
if (!e.testRunKey || typeof e.testRunKey != "string")
|
|
1680
|
+
throw new c(
|
|
1681
|
+
d.InvalidParams,
|
|
1682
|
+
"testRunKey is required"
|
|
1683
|
+
);
|
|
1684
|
+
const s = await this.jiraApi.getTestRun(e.testRunKey);
|
|
1685
|
+
return {
|
|
1686
|
+
content: [
|
|
1687
|
+
{ type: "text", text: JSON.stringify(s, null, 2) }
|
|
1688
|
+
]
|
|
1689
|
+
};
|
|
1690
|
+
}
|
|
1517
1691
|
default:
|
|
1518
1692
|
throw new c(
|
|
1519
|
-
|
|
1693
|
+
d.MethodNotFound,
|
|
1520
1694
|
`Unknown tool: ${t.params.name}`
|
|
1521
1695
|
);
|
|
1522
1696
|
}
|
|
1523
1697
|
} catch (e) {
|
|
1524
1698
|
throw e instanceof c ? e : new c(
|
|
1525
|
-
|
|
1699
|
+
d.InternalError,
|
|
1526
1700
|
e instanceof Error ? e.message : "Unknown error occurred"
|
|
1527
1701
|
);
|
|
1528
1702
|
}
|
|
1529
1703
|
});
|
|
1530
1704
|
}
|
|
1531
1705
|
async run() {
|
|
1532
|
-
const t = new
|
|
1706
|
+
const t = new f();
|
|
1533
1707
|
await this.server.connect(t);
|
|
1534
1708
|
}
|
|
1535
1709
|
}
|
|
1536
|
-
const
|
|
1537
|
-
|
|
1710
|
+
const A = process.argv[1]?.endsWith("index.js") || process.argv[1]?.endsWith("index.ts") || process.argv[1]?.endsWith("jira-mcp");
|
|
1711
|
+
A && new S().run().catch(() => {
|
|
1538
1712
|
});
|
|
1539
1713
|
export {
|
|
1540
1714
|
S as JiraServer
|