mcp-jira-stdio 1.6.0 → 1.6.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/index.js CHANGED
@@ -440,7 +440,7 @@ var require_package2 = __commonJS({
440
440
  "package.json"(exports, module) {
441
441
  module.exports = {
442
442
  name: "mcp-jira-stdio",
443
- version: "1.6.0",
443
+ version: "1.6.1",
444
444
  description: "Model Context Protocol (MCP) server for Jira API integration",
445
445
  author: "graslt",
446
446
  license: "MIT",
@@ -559,7 +559,8 @@ var TOOL_NAMES = {
559
559
  GET_PROJECT_INFO: "jira_get_project_info",
560
560
  CREATE_SUBTASK: "jira_create_subtask",
561
561
  GET_CREATE_META: "jira_get_create_meta",
562
- GET_CUSTOM_FIELDS: "jira_get_custom_fields"
562
+ GET_CUSTOM_FIELDS: "jira_get_custom_fields",
563
+ CREATE_ISSUE_LINK: "jira_create_issue_link"
563
564
  };
564
565
 
565
566
  // src/utils/logger.ts
@@ -836,6 +837,13 @@ var GetCreateMetaInputSchema = z.object({
836
837
  var GetCustomFieldsInputSchema = z.object({
837
838
  projectKey: z.string().optional().describe("Project key to filter custom fields (optional)").refine((v) => v ? isValidProjectKey(v) : true, "Invalid project key format")
838
839
  });
840
+ var CreateIssueLinkInputSchema = z.object({
841
+ fromIssue: z.string().describe("Source issue key").refine((v) => isValidIssueKey(v), "Invalid issue key format"),
842
+ toIssue: z.string().describe("Target issue key").refine((v) => isValidIssueKey(v), "Invalid issue key format"),
843
+ linkType: z.string().describe(
844
+ 'Link type: "blocks", "is blocked by", "relates", "duplicates", "clones", or custom link type name'
845
+ )
846
+ });
839
847
  function ensureAdfDescription(desc, format = "markdown") {
840
848
  if (!desc) return desc;
841
849
  if (typeof desc === "object") return desc;
@@ -1016,8 +1024,8 @@ async function searchIssues(options) {
1016
1024
  if (options.fields) {
1017
1025
  data.fields = options.fields;
1018
1026
  }
1019
- if (options.expand) {
1020
- data.expand = options.expand;
1027
+ if (options.expand && options.expand.length > 0) {
1028
+ data.expand = options.expand.join(",");
1021
1029
  }
1022
1030
  const config = {
1023
1031
  method: "POST",
@@ -1263,6 +1271,40 @@ async function getFields() {
1263
1271
  };
1264
1272
  return await makeJiraRequest(config);
1265
1273
  }
1274
+ async function createIssueLink(fromIssue, toIssue, linkType) {
1275
+ const linkTypeMap = {
1276
+ blocks: "Blocks",
1277
+ "is blocked by": "Blocks",
1278
+ relates: "Relates",
1279
+ "relates to": "Relates",
1280
+ duplicates: "Duplicate",
1281
+ duplicate: "Duplicate",
1282
+ "is duplicated by": "Duplicate",
1283
+ clones: "Cloners",
1284
+ "is cloned by": "Cloners"
1285
+ };
1286
+ const normalizedLinkType = linkType.toLowerCase();
1287
+ const jiraLinkType = linkTypeMap[normalizedLinkType] || linkType;
1288
+ const isInward = normalizedLinkType === "is blocked by" || normalizedLinkType === "is duplicated by" || normalizedLinkType === "is cloned by";
1289
+ const data = {
1290
+ type: {
1291
+ name: jiraLinkType
1292
+ }
1293
+ };
1294
+ if (isInward) {
1295
+ data.inwardIssue = { key: fromIssue };
1296
+ data.outwardIssue = { key: toIssue };
1297
+ } else {
1298
+ data.outwardIssue = { key: fromIssue };
1299
+ data.inwardIssue = { key: toIssue };
1300
+ }
1301
+ const config = {
1302
+ method: "POST",
1303
+ url: "/issueLink",
1304
+ data
1305
+ };
1306
+ await makeJiraRequest(config);
1307
+ }
1266
1308
 
1267
1309
  // src/utils/redaction.ts
1268
1310
  function isRedactionEnabled() {
@@ -1392,14 +1434,15 @@ function formatSearchResultsResponse(result) {
1392
1434
  return `\u2022 **${issue.key}** - ${summary}
1393
1435
  Status: ${status} | Assignee: ${assignee} | Priority: ${priority}`;
1394
1436
  }).join("\n\n");
1437
+ const total = result.total ?? result.issues.length;
1395
1438
  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;
1439
+ const rangeEnd = Math.min(result.startAt + result.maxResults, total);
1440
+ const showRangeInline = total > result.maxResults || result.startAt > 0;
1398
1441
  let paginationInfo = "";
1399
1442
  if (showRangeInline) {
1400
1443
  paginationInfo = `
1401
1444
 
1402
- *Showing ${rangeStart}-${rangeEnd} of ${result.total} results*`;
1445
+ *Showing ${rangeStart}-${rangeEnd} of ${total} results*`;
1403
1446
  }
1404
1447
  if (result.nextPageToken && !result.isLast) {
1405
1448
  paginationInfo += `
@@ -1411,7 +1454,7 @@ function formatSearchResultsResponse(result) {
1411
1454
  content: [
1412
1455
  {
1413
1456
  type: "text",
1414
- text: `Found ${result.total} issue(s)${showRangeInline ? ` (showing ${rangeStart}-${rangeEnd})` : ""}:
1457
+ text: `Found ${total} issue(s)${showRangeInline ? ` (showing ${rangeStart}-${rangeEnd})` : ""}:
1415
1458
 
1416
1459
  ${issuesList}${paginationInfo}`
1417
1460
  }
@@ -1888,7 +1931,7 @@ async function handleSearchIssues(input) {
1888
1931
  if (validated.fields !== void 0) searchParams.fields = validated.fields;
1889
1932
  if (validated.expand !== void 0) searchParams.expand = validated.expand;
1890
1933
  const result = await searchIssues(searchParams);
1891
- log5.info(`Found ${result.total} issue(s), showing ${result.issues.length}`);
1934
+ log5.info(`Found ${result.total ?? 0} issue(s), showing ${result.issues.length}`);
1892
1935
  return formatSearchResultsResponse(result);
1893
1936
  } catch (error) {
1894
1937
  log5.error("Error in handleSearchIssues:", error);
@@ -1940,7 +1983,9 @@ async function handleGetMyIssues(input) {
1940
1983
  if (validated.fields !== void 0) getParams.fields = validated.fields;
1941
1984
  if (validated.expand !== void 0) getParams.expand = validated.expand;
1942
1985
  const result = await getMyIssues(getParams);
1943
- log6.info(`Found ${result.total} issue(s) assigned to you, showing ${result.issues.length}`);
1986
+ log6.info(
1987
+ `Found ${result.total ?? 0} issue(s) assigned to you, showing ${result.issues.length}`
1988
+ );
1944
1989
  return formatSearchResultsResponse(result);
1945
1990
  } catch (error) {
1946
1991
  log6.error("Error in handleGetMyIssues:", error);
@@ -2532,6 +2577,47 @@ async function handleGetCustomFields(input) {
2532
2577
  }
2533
2578
  }
2534
2579
 
2580
+ // src/tools/create-issue-link.ts
2581
+ var log18 = createLogger("tool:create-issue-link");
2582
+ var createIssueLinkTool = {
2583
+ name: TOOL_NAMES.CREATE_ISSUE_LINK,
2584
+ 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.',
2585
+ inputSchema: {
2586
+ type: "object",
2587
+ properties: {
2588
+ fromIssue: {
2589
+ type: "string",
2590
+ description: "Source issue key (e.g., PROJECT-123)"
2591
+ },
2592
+ toIssue: {
2593
+ type: "string",
2594
+ description: "Target issue key (e.g., PROJECT-456)"
2595
+ },
2596
+ linkType: {
2597
+ type: "string",
2598
+ description: 'Link type: "blocks" (fromIssue blocks toIssue), "is blocked by", "relates", "duplicates", "clones", or custom link type name'
2599
+ }
2600
+ },
2601
+ required: ["fromIssue", "toIssue", "linkType"]
2602
+ }
2603
+ };
2604
+ async function handleCreateIssueLink(input) {
2605
+ try {
2606
+ const validated = validateInput(CreateIssueLinkInputSchema, input);
2607
+ log18.info(
2608
+ `Creating ${validated.linkType} link from ${validated.fromIssue} to ${validated.toIssue}...`
2609
+ );
2610
+ await createIssueLink(validated.fromIssue, validated.toIssue, validated.linkType);
2611
+ log18.info("Issue link created successfully");
2612
+ return formatSuccessResponse(
2613
+ `Link created: ${validated.fromIssue} ${validated.linkType} ${validated.toIssue}`
2614
+ );
2615
+ } catch (error) {
2616
+ log18.error("Error in handleCreateIssueLink:", error);
2617
+ return handleError(error);
2618
+ }
2619
+ }
2620
+
2535
2621
  // src/index.ts
2536
2622
  if (process.env.NODE_ENV !== "production") {
2537
2623
  try {
@@ -2540,7 +2626,7 @@ if (process.env.NODE_ENV !== "production") {
2540
2626
  } catch {
2541
2627
  }
2542
2628
  }
2543
- var log18 = createLogger("server");
2629
+ var log19 = createLogger("server");
2544
2630
  var toolHandlers = /* @__PURE__ */ new Map([
2545
2631
  [TOOL_NAMES.GET_VISIBLE_PROJECTS, handleGetVisibleProjects],
2546
2632
  [TOOL_NAMES.GET_ISSUE, handleGetIssue],
@@ -2556,7 +2642,8 @@ var toolHandlers = /* @__PURE__ */ new Map([
2556
2642
  [TOOL_NAMES.GET_PROJECT_INFO, handleGetProjectInfo],
2557
2643
  [TOOL_NAMES.CREATE_SUBTASK, handleCreateSubtask],
2558
2644
  [TOOL_NAMES.GET_CREATE_META, handleGetCreateMeta],
2559
- [TOOL_NAMES.GET_CUSTOM_FIELDS, handleGetCustomFields]
2645
+ [TOOL_NAMES.GET_CUSTOM_FIELDS, handleGetCustomFields],
2646
+ [TOOL_NAMES.CREATE_ISSUE_LINK, handleCreateIssueLink]
2560
2647
  ]);
2561
2648
  var allTools = [
2562
2649
  getVisibleProjectsTool,
@@ -2573,7 +2660,8 @@ var allTools = [
2573
2660
  getProjectInfoTool,
2574
2661
  createSubtaskTool,
2575
2662
  getCreateMetaTool,
2576
- getCustomFieldsTool
2663
+ getCustomFieldsTool,
2664
+ createIssueLinkTool
2577
2665
  ];
2578
2666
  async function main() {
2579
2667
  const isDryRun = (process.env.DRY_RUN || "").toLowerCase() === "1" || (process.env.DRY_RUN || "").toLowerCase() === "true";
@@ -2644,7 +2732,7 @@ async function main() {
2644
2732
  console.error("\u{1F680} Jira MCP server running on stdio");
2645
2733
  }
2646
2734
  main().catch((error) => {
2647
- log18.error("Fatal error:", error);
2735
+ log19.error("Fatal error:", error);
2648
2736
  process.exit(1);
2649
2737
  });
2650
2738
  //# sourceMappingURL=index.js.map