mcp-jira-stdio 1.6.0 → 1.7.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/dist/index.js CHANGED
@@ -40,7 +40,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
40
40
 
41
41
  // node_modules/dotenv/package.json
42
42
  var require_package = __commonJS({
43
- "node_modules/dotenv/package.json"(exports, module) {
43
+ "node_modules/dotenv/package.json"(exports$1, module) {
44
44
  module.exports = {
45
45
  name: "dotenv",
46
46
  version: "17.2.3",
@@ -108,7 +108,7 @@ var require_package = __commonJS({
108
108
 
109
109
  // node_modules/dotenv/lib/main.js
110
110
  var require_main = __commonJS({
111
- "node_modules/dotenv/lib/main.js"(exports, module) {
111
+ "node_modules/dotenv/lib/main.js"(exports$1, module) {
112
112
  var fs = __require("fs");
113
113
  var path = __require("path");
114
114
  var os = __require("os");
@@ -437,10 +437,10 @@ var require_main = __commonJS({
437
437
 
438
438
  // package.json
439
439
  var require_package2 = __commonJS({
440
- "package.json"(exports, module) {
440
+ "package.json"(exports$1, module) {
441
441
  module.exports = {
442
442
  name: "mcp-jira-stdio",
443
- version: "1.6.0",
443
+ version: "1.7.0",
444
444
  description: "Model Context Protocol (MCP) server for Jira API integration",
445
445
  author: "graslt",
446
446
  license: "MIT",
@@ -486,29 +486,29 @@ var require_package2 = __commonJS({
486
486
  "ai"
487
487
  ],
488
488
  engines: {
489
- node: ">=18.0.0"
489
+ node: ">=20.0.0"
490
490
  },
491
491
  dependencies: {
492
492
  "@modelcontextprotocol/sdk": "^1.17.1",
493
493
  axios: "^1.7.0",
494
- "md-to-adf": "^0.6.4",
494
+ "md-to-adf": "^0.5.0",
495
495
  zod: "^3.22.4"
496
496
  },
497
497
  devDependencies: {
498
498
  "@types/node": "^22.5.0",
499
499
  "@typescript-eslint/eslint-plugin": "^8.0.0",
500
500
  "@typescript-eslint/parser": "^8.0.0",
501
- "@vitest/coverage-v8": "^3.2.4",
502
- "@vitest/ui": "^3.2.4",
501
+ "@vitest/coverage-v8": "^4.0.10",
502
+ "@vitest/ui": "^4.0.10",
503
503
  dotenv: "^17.2.1",
504
504
  eslint: "^9.9.0",
505
505
  "eslint-config-prettier": "^9.1.0",
506
506
  "eslint-plugin-prettier": "^5.2.0",
507
507
  prettier: "^3.5.0",
508
- tsup: "^8.0.0",
508
+ tsup: "^8.5.1",
509
509
  tsx: "^4.16.0",
510
510
  typescript: "^5.5.0",
511
- vitest: "^3.2.4"
511
+ vitest: "^4.0.10"
512
512
  },
513
513
  files: [
514
514
  "dist",
@@ -526,6 +526,10 @@ var require_package2 = __commonJS({
526
526
  homepage: "https://github.com/freema/mcp-jira-stdio#readme",
527
527
  publishConfig: {
528
528
  access: "public"
529
+ },
530
+ overrides: {
531
+ glob: ">=11.0.4",
532
+ marked: ">=4.0.10"
529
533
  }
530
534
  };
531
535
  }
@@ -559,7 +563,8 @@ var TOOL_NAMES = {
559
563
  GET_PROJECT_INFO: "jira_get_project_info",
560
564
  CREATE_SUBTASK: "jira_create_subtask",
561
565
  GET_CREATE_META: "jira_get_create_meta",
562
- GET_CUSTOM_FIELDS: "jira_get_custom_fields"
566
+ GET_CUSTOM_FIELDS: "jira_get_custom_fields",
567
+ CREATE_ISSUE_LINK: "jira_create_issue_link"
563
568
  };
564
569
 
565
570
  // src/utils/logger.ts
@@ -836,6 +841,13 @@ var GetCreateMetaInputSchema = z.object({
836
841
  var GetCustomFieldsInputSchema = z.object({
837
842
  projectKey: z.string().optional().describe("Project key to filter custom fields (optional)").refine((v) => v ? isValidProjectKey(v) : true, "Invalid project key format")
838
843
  });
844
+ var CreateIssueLinkInputSchema = z.object({
845
+ fromIssue: z.string().describe("Source issue key").refine((v) => isValidIssueKey(v), "Invalid issue key format"),
846
+ toIssue: z.string().describe("Target issue key").refine((v) => isValidIssueKey(v), "Invalid issue key format"),
847
+ linkType: z.string().describe(
848
+ 'Link type: "blocks", "is blocked by", "relates", "duplicates", "clones", or custom link type name'
849
+ )
850
+ });
839
851
  function ensureAdfDescription(desc, format = "markdown") {
840
852
  if (!desc) return desc;
841
853
  if (typeof desc === "object") return desc;
@@ -1016,8 +1028,8 @@ async function searchIssues(options) {
1016
1028
  if (options.fields) {
1017
1029
  data.fields = options.fields;
1018
1030
  }
1019
- if (options.expand) {
1020
- data.expand = options.expand;
1031
+ if (options.expand && options.expand.length > 0) {
1032
+ data.expand = options.expand.join(",");
1021
1033
  }
1022
1034
  const config = {
1023
1035
  method: "POST",
@@ -1263,6 +1275,40 @@ async function getFields() {
1263
1275
  };
1264
1276
  return await makeJiraRequest(config);
1265
1277
  }
1278
+ async function createIssueLink(fromIssue, toIssue, linkType) {
1279
+ const linkTypeMap = {
1280
+ blocks: "Blocks",
1281
+ "is blocked by": "Blocks",
1282
+ relates: "Relates",
1283
+ "relates to": "Relates",
1284
+ duplicates: "Duplicate",
1285
+ duplicate: "Duplicate",
1286
+ "is duplicated by": "Duplicate",
1287
+ clones: "Cloners",
1288
+ "is cloned by": "Cloners"
1289
+ };
1290
+ const normalizedLinkType = linkType.toLowerCase();
1291
+ const jiraLinkType = linkTypeMap[normalizedLinkType] || linkType;
1292
+ const isInward = normalizedLinkType === "is blocked by" || normalizedLinkType === "is duplicated by" || normalizedLinkType === "is cloned by";
1293
+ const data = {
1294
+ type: {
1295
+ name: jiraLinkType
1296
+ }
1297
+ };
1298
+ if (isInward) {
1299
+ data.inwardIssue = { key: fromIssue };
1300
+ data.outwardIssue = { key: toIssue };
1301
+ } else {
1302
+ data.outwardIssue = { key: fromIssue };
1303
+ data.inwardIssue = { key: toIssue };
1304
+ }
1305
+ const config = {
1306
+ method: "POST",
1307
+ url: "/issueLink",
1308
+ data
1309
+ };
1310
+ await makeJiraRequest(config);
1311
+ }
1266
1312
 
1267
1313
  // src/utils/redaction.ts
1268
1314
  function isRedactionEnabled() {
@@ -1392,14 +1438,15 @@ function formatSearchResultsResponse(result) {
1392
1438
  return `\u2022 **${issue.key}** - ${summary}
1393
1439
  Status: ${status} | Assignee: ${assignee} | Priority: ${priority}`;
1394
1440
  }).join("\n\n");
1441
+ const total = result.total ?? result.issues.length;
1395
1442
  const rangeStart = result.startAt + 1;
1396
- const rangeEnd = Math.min(result.startAt + result.maxResults, result.total);
1397
- const showRangeInline = result.total > result.maxResults || result.startAt > 0;
1443
+ const rangeEnd = Math.min(result.startAt + result.maxResults, total);
1444
+ const showRangeInline = total > result.maxResults || result.startAt > 0;
1398
1445
  let paginationInfo = "";
1399
1446
  if (showRangeInline) {
1400
1447
  paginationInfo = `
1401
1448
 
1402
- *Showing ${rangeStart}-${rangeEnd} of ${result.total} results*`;
1449
+ *Showing ${rangeStart}-${rangeEnd} of ${total} results*`;
1403
1450
  }
1404
1451
  if (result.nextPageToken && !result.isLast) {
1405
1452
  paginationInfo += `
@@ -1411,7 +1458,7 @@ function formatSearchResultsResponse(result) {
1411
1458
  content: [
1412
1459
  {
1413
1460
  type: "text",
1414
- text: `Found ${result.total} issue(s)${showRangeInline ? ` (showing ${rangeStart}-${rangeEnd})` : ""}:
1461
+ text: `Found ${total} issue(s)${showRangeInline ? ` (showing ${rangeStart}-${rangeEnd})` : ""}:
1415
1462
 
1416
1463
  ${issuesList}${paginationInfo}`
1417
1464
  }
@@ -1888,7 +1935,7 @@ async function handleSearchIssues(input) {
1888
1935
  if (validated.fields !== void 0) searchParams.fields = validated.fields;
1889
1936
  if (validated.expand !== void 0) searchParams.expand = validated.expand;
1890
1937
  const result = await searchIssues(searchParams);
1891
- log5.info(`Found ${result.total} issue(s), showing ${result.issues.length}`);
1938
+ log5.info(`Found ${result.total ?? 0} issue(s), showing ${result.issues.length}`);
1892
1939
  return formatSearchResultsResponse(result);
1893
1940
  } catch (error) {
1894
1941
  log5.error("Error in handleSearchIssues:", error);
@@ -1940,7 +1987,9 @@ async function handleGetMyIssues(input) {
1940
1987
  if (validated.fields !== void 0) getParams.fields = validated.fields;
1941
1988
  if (validated.expand !== void 0) getParams.expand = validated.expand;
1942
1989
  const result = await getMyIssues(getParams);
1943
- log6.info(`Found ${result.total} issue(s) assigned to you, showing ${result.issues.length}`);
1990
+ log6.info(
1991
+ `Found ${result.total ?? 0} issue(s) assigned to you, showing ${result.issues.length}`
1992
+ );
1944
1993
  return formatSearchResultsResponse(result);
1945
1994
  } catch (error) {
1946
1995
  log6.error("Error in handleGetMyIssues:", error);
@@ -2532,6 +2581,47 @@ async function handleGetCustomFields(input) {
2532
2581
  }
2533
2582
  }
2534
2583
 
2584
+ // src/tools/create-issue-link.ts
2585
+ var log18 = createLogger("tool:create-issue-link");
2586
+ var createIssueLinkTool = {
2587
+ name: TOOL_NAMES.CREATE_ISSUE_LINK,
2588
+ description: 'Creates a link between two Jira issues. Supports common link types like "blocks", "relates", "duplicates", and "clones". Use this to establish relationships between issues.',
2589
+ inputSchema: {
2590
+ type: "object",
2591
+ properties: {
2592
+ fromIssue: {
2593
+ type: "string",
2594
+ description: "Source issue key (e.g., PROJECT-123)"
2595
+ },
2596
+ toIssue: {
2597
+ type: "string",
2598
+ description: "Target issue key (e.g., PROJECT-456)"
2599
+ },
2600
+ linkType: {
2601
+ type: "string",
2602
+ description: 'Link type: "blocks" (fromIssue blocks toIssue), "is blocked by", "relates", "duplicates", "clones", or custom link type name'
2603
+ }
2604
+ },
2605
+ required: ["fromIssue", "toIssue", "linkType"]
2606
+ }
2607
+ };
2608
+ async function handleCreateIssueLink(input) {
2609
+ try {
2610
+ const validated = validateInput(CreateIssueLinkInputSchema, input);
2611
+ log18.info(
2612
+ `Creating ${validated.linkType} link from ${validated.fromIssue} to ${validated.toIssue}...`
2613
+ );
2614
+ await createIssueLink(validated.fromIssue, validated.toIssue, validated.linkType);
2615
+ log18.info("Issue link created successfully");
2616
+ return formatSuccessResponse(
2617
+ `Link created: ${validated.fromIssue} ${validated.linkType} ${validated.toIssue}`
2618
+ );
2619
+ } catch (error) {
2620
+ log18.error("Error in handleCreateIssueLink:", error);
2621
+ return handleError(error);
2622
+ }
2623
+ }
2624
+
2535
2625
  // src/index.ts
2536
2626
  if (process.env.NODE_ENV !== "production") {
2537
2627
  try {
@@ -2540,7 +2630,7 @@ if (process.env.NODE_ENV !== "production") {
2540
2630
  } catch {
2541
2631
  }
2542
2632
  }
2543
- var log18 = createLogger("server");
2633
+ var log19 = createLogger("server");
2544
2634
  var toolHandlers = /* @__PURE__ */ new Map([
2545
2635
  [TOOL_NAMES.GET_VISIBLE_PROJECTS, handleGetVisibleProjects],
2546
2636
  [TOOL_NAMES.GET_ISSUE, handleGetIssue],
@@ -2556,7 +2646,8 @@ var toolHandlers = /* @__PURE__ */ new Map([
2556
2646
  [TOOL_NAMES.GET_PROJECT_INFO, handleGetProjectInfo],
2557
2647
  [TOOL_NAMES.CREATE_SUBTASK, handleCreateSubtask],
2558
2648
  [TOOL_NAMES.GET_CREATE_META, handleGetCreateMeta],
2559
- [TOOL_NAMES.GET_CUSTOM_FIELDS, handleGetCustomFields]
2649
+ [TOOL_NAMES.GET_CUSTOM_FIELDS, handleGetCustomFields],
2650
+ [TOOL_NAMES.CREATE_ISSUE_LINK, handleCreateIssueLink]
2560
2651
  ]);
2561
2652
  var allTools = [
2562
2653
  getVisibleProjectsTool,
@@ -2573,7 +2664,8 @@ var allTools = [
2573
2664
  getProjectInfoTool,
2574
2665
  createSubtaskTool,
2575
2666
  getCreateMetaTool,
2576
- getCustomFieldsTool
2667
+ getCustomFieldsTool,
2668
+ createIssueLinkTool
2577
2669
  ];
2578
2670
  async function main() {
2579
2671
  const isDryRun = (process.env.DRY_RUN || "").toLowerCase() === "1" || (process.env.DRY_RUN || "").toLowerCase() === "true";
@@ -2644,7 +2736,7 @@ async function main() {
2644
2736
  console.error("\u{1F680} Jira MCP server running on stdio");
2645
2737
  }
2646
2738
  main().catch((error) => {
2647
- log18.error("Fatal error:", error);
2739
+ log19.error("Fatal error:", error);
2648
2740
  process.exit(1);
2649
2741
  });
2650
2742
  //# sourceMappingURL=index.js.map