@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 CHANGED
@@ -1,15 +1,15 @@
1
1
  #!/usr/bin/env node
2
- import { Server as f } from "@modelcontextprotocol/sdk/server/index.js";
3
- import { StdioServerTransport as h } from "@modelcontextprotocol/sdk/server/stdio.js";
4
- import { ListPromptsRequestSchema as g, GetPromptRequestSchema as I, McpError as c, ErrorCode as p, ListToolsRequestSchema as w, CallToolRequestSchema as _ } from "@modelcontextprotocol/sdk/types.js";
5
- class y {
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 a;
11
- i === "bearer" ? a = `Bearer ${s}` : a = `Basic ${Buffer.from(`${e}:${s}`).toString("base64")}`, this.headers = new Headers({
12
- Authorization: a,
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 a = Object.entries(i.errors).map(([r, n]) => `${r}: ${n}`).join("; ");
29
- a && (s = a);
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 r = await t.text();
35
- r && (s = r.substring(0, 500));
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 = [], a = (r) => {
51
- if (r.type === "inlineCard" && r.attrs?.url) {
52
- const n = r.attrs.url.match(/\/browse\/([A-Z]+-\d+)/);
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
- r.type === "text" && r.text && (r.text.match(/[A-Z]+-\d+/g) || []).forEach((o) => {
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
- }), r.content && r.content.forEach(a);
67
+ }), a.content && a.content.forEach(r);
68
68
  };
69
- return t.forEach(a), [...new Map(i.map((r) => [r.key, r])).values()];
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((a) => {
122
- const r = a.inwardIssue || a.outwardIssue, n = a.type.inward || a.type.outward;
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: r.key,
125
- summary: r.fields?.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
- return s.ok || await this.handleFetchError(s, t), s.json();
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 (a) => {
204
- const r = await this.fetchJson(
205
- `/rest/api/3/issue/${a.key}/comment`
206
- ), n = this.cleanIssue(a), o = r.comments.map(
207
- (d) => this.cleanComment(d)
208
- ), u = o.flatMap(
209
- (d) => d.mentions
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
- ...u
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 a = this.cleanIssue(s), r = i.comments.map(
246
+ const r = this.cleanIssue(s), a = i.comments.map(
246
247
  (o) => this.cleanComment(o)
247
- ), n = r.flatMap(
248
+ ), n = a.flatMap(
248
249
  (o) => o.mentions
249
250
  );
250
- if (a.relatedIssues = [...a.relatedIssues, ...n], a.comments = r, a.epicLink)
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/${a.epicLink.key}?fields=summary`
254
+ `/rest/api/3/issue/${r.epicLink.key}?fields=summary`
254
255
  );
255
- a.epicLink.summary = o.fields?.summary;
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 a;
260
+ return r;
260
261
  }
261
- async createIssue(t, e, s, i, a) {
262
- const r = {
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
- ...a
273
+ ...r
273
274
  }
274
275
  };
275
276
  return this.fetchJson("/rest/api/3/issue", {
276
277
  method: "POST",
277
- body: JSON.stringify(r)
278
+ body: JSON.stringify(a)
278
279
  });
279
280
  }
280
281
  async getCreateMeta(t, e, s) {
281
- let a = (await this.fetchJson(
282
+ let r = (await this.fetchJson(
282
283
  `/rest/api/3/issue/createmeta/${t}/issuetypes`
283
284
  )).issueTypes || [];
284
- return e && (a = a.filter(
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
- a.map(async (n) => {
290
+ r.map(async (n) => {
290
291
  try {
291
- let u = (await this.fetchJson(
292
+ let p = (await this.fetchJson(
292
293
  `/rest/api/3/issue/createmeta/${t}/issuetypes/${n.id}`
293
294
  )).fields || [];
294
- return s && (u = u.map((d) => ({
295
- fieldId: d.fieldId,
296
- name: d.name,
297
- required: d.required,
298
- schema: d.schema,
299
- hasDefaultValue: d.hasDefaultValue,
300
- allowedValuesCount: d.allowedValues?.length ?? 0
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: u
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((r) => r.fieldId === s)?.allowedValues ?? [];
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 a = new Headers(this.headers);
373
- a.delete("Content-Type"), a.set("X-Atlassian-Token", "no-check");
374
- const r = await fetch(
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: a,
379
+ headers: r,
379
380
  body: i
380
381
  }
381
382
  );
382
- r.ok || await this.handleFetchError(r);
383
- const o = (await r.json())[0];
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
- }, a = await this.fetchJson(
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: a.id,
424
- author: a.author.displayName,
425
- created: a.created,
426
- updated: a.updated,
427
- body: this.extractTextContent(a.body.content)
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((a) => ({
451
- accountId: a.accountId,
452
- displayName: a.displayName,
453
- emailAddress: a.emailAddress,
454
- active: a.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 y {
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 a = (await this.fetchJson(
515
+ let r = (await this.fetchJson(
493
516
  `/rest/api/3/issue/createmeta/${t}/issuetypes`
494
517
  )).values || [];
495
- return e && (a = a.filter(
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
- a.map(async (n) => {
523
+ r.map(async (n) => {
501
524
  try {
502
- let u = (await this.fetchJson(
525
+ let p = (await this.fetchJson(
503
526
  `/rest/api/3/issue/createmeta/${t}/issuetypes/${n.id}`
504
527
  )).values || [];
505
- return s && (u = u.map((d) => ({
506
- fieldId: d.fieldId,
507
- name: d.name,
508
- required: d.required,
509
- schema: d.schema,
510
- hasDefaultValue: d.hasDefaultValue,
511
- allowedValuesCount: d.allowedValues?.length ?? 0
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: u
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((r) => r.fieldId === s)?.allowedValues ?? [];
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((a) => ({
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: a.key || a.name,
548
- displayName: a.displayName,
549
- emailAddress: a.emailAddress,
550
- active: a.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((r) => ({
598
- key: r,
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 A() {
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 m = {
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 f(
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: a } = A();
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
- a
884
- ) : this.jiraApi = new y(
906
+ r
907
+ ) : this.jiraApi = new m(
885
908
  e,
886
909
  s,
887
910
  t,
888
- a
889
- ), this.setupToolHandlers(), this.server.onerror = (r) => {
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(m).map(([t, { description: e }]) => ({
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 = m[e];
924
+ const { name: e } = t.params, s = y[e];
902
925
  if (!s)
903
- throw new c(p.InvalidParams, `Unknown prompt: ${e}`);
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
- p.InvalidParams,
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
- p.InvalidParams,
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
- p.InvalidParams,
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
- p.InvalidParams,
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
- p.InvalidParams,
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
- p.InvalidParams,
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
- p.InvalidParams,
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
- p.InvalidParams,
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
- p.InvalidParams,
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
- p.InvalidParams,
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
- p.InvalidParams,
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
- p.InvalidParams,
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
- p.InvalidParams,
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
- p.InvalidParams,
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
- p.MethodNotFound,
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
- p.InternalError,
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 h();
1706
+ const t = new f();
1533
1707
  await this.server.connect(t);
1534
1708
  }
1535
1709
  }
1536
- const T = process.argv[1]?.endsWith("index.js") || process.argv[1]?.endsWith("index.ts") || process.argv[1]?.endsWith("jira-mcp");
1537
- T && new S().run().catch(() => {
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