mcp-jira-stdio 1.12.0 → 1.12.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$1, module) {
441
441
  module.exports = {
442
442
  name: "mcp-jira-stdio",
443
- version: "1.12.0",
443
+ version: "1.12.1",
444
444
  mcpName: "io.github.freema/mcp-jira-stdio",
445
445
  description: "Model Context Protocol (MCP) server for Jira API integration",
446
446
  author: "graslt",
@@ -546,6 +546,8 @@ var JIRA_CONFIG = {
546
546
  var ERROR_MESSAGES = {
547
547
  AUTH_REQUIRED: "Jira authentication required. Set JIRA_BASE_URL, JIRA_EMAIL, and JIRA_API_TOKEN environment variables.",
548
548
  INVALID_CREDENTIALS: "Invalid Jira credentials. Please check your email and API token.",
549
+ PROJECT_NOT_FOUND: "Project not found or you don't have permission to access it.",
550
+ ISSUE_NOT_FOUND: "Issue not found or you don't have permission to access it.",
549
551
  RATE_LIMIT_EXCEEDED: "Jira API rate limit exceeded. Please wait and try again.",
550
552
  NETWORK_ERROR: "Network error occurred while connecting to Jira.",
551
553
  VALIDATION_ERROR: "Input validation failed."
@@ -597,23 +599,75 @@ function getLevel() {
597
599
  return env;
598
600
  return "info";
599
601
  }
602
+ var REDACTED = "[REDACTED]";
603
+ var SECRET_KEYS = /^(auth|authorization|proxy-authorization|password|apitoken|api_token|token|cookie|set-cookie)$/i;
604
+ function describeError(err) {
605
+ const e = err;
606
+ const out = {
607
+ name: e?.name ?? "Error",
608
+ message: e?.message ?? String(err)
609
+ };
610
+ if (e?.code) out.code = e.code;
611
+ const status = e?.response?.status ?? e?.status;
612
+ if (status) out.status = status;
613
+ const config = e?.config;
614
+ if (config?.method) out.method = String(config.method).toUpperCase();
615
+ if (config?.url) out.url = `${config.baseURL ?? ""}${config.url}`;
616
+ const data = e?.response?.data;
617
+ if (Array.isArray(data?.errorMessages) && data.errorMessages.length > 0) {
618
+ out.errorMessages = data.errorMessages;
619
+ }
620
+ if (data?.errors && typeof data.errors === "object" && Object.keys(data.errors).length > 0) {
621
+ out.errors = data.errors;
622
+ }
623
+ return out;
624
+ }
625
+ function redactDeep(value, seen, depth) {
626
+ if (value instanceof Error) return describeError(value);
627
+ if (value === null || typeof value !== "object") return value;
628
+ if (seen.has(value) || depth > 8) return "[Circular]";
629
+ seen.add(value);
630
+ if (Array.isArray(value)) return value.map((v) => redactDeep(v, seen, depth + 1));
631
+ const out = {};
632
+ for (const [key, v] of Object.entries(value)) {
633
+ out[key] = SECRET_KEYS.test(key) ? REDACTED : redactDeep(v, seen, depth + 1);
634
+ }
635
+ return out;
636
+ }
637
+ function scrubSecrets(text) {
638
+ let out = text.replace(/(\/\/)[^/@\s]+@/g, `$1${REDACTED}@`).replace(/\b(Basic|Bearer)\s+[A-Za-z0-9+/=._~-]{16,}/g, `$1 ${REDACTED}`);
639
+ const token = process.env.JIRA_API_TOKEN;
640
+ if (token && token.length >= 8) {
641
+ out = out.split(token).join(REDACTED);
642
+ }
643
+ return out;
644
+ }
645
+ function formatLogArg(arg) {
646
+ if (typeof arg === "string") return scrubSecrets(arg);
647
+ let text;
648
+ try {
649
+ text = JSON.stringify(redactDeep(arg, /* @__PURE__ */ new WeakSet(), 0)) ?? String(arg);
650
+ } catch {
651
+ text = String(arg);
652
+ }
653
+ return scrubSecrets(text);
654
+ }
600
655
  function createLogger(scope) {
601
656
  const level = getLevel();
602
657
  const min = levels[level];
603
- const fmt = (msg) => typeof msg === "string" ? msg : JSON.stringify(msg);
604
658
  const prefix = (lvl) => `[${lvl.toUpperCase()}] ${scope}:`;
605
659
  return {
606
660
  debug: (...args) => {
607
- if (min <= levels.debug) console.error(prefix("debug"), ...args.map(fmt));
661
+ if (min <= levels.debug) console.error(prefix("debug"), ...args.map(formatLogArg));
608
662
  },
609
663
  info: (...args) => {
610
- if (min <= levels.info) console.error(prefix("info"), ...args.map(fmt));
664
+ if (min <= levels.info) console.error(prefix("info"), ...args.map(formatLogArg));
611
665
  },
612
666
  warn: (...args) => {
613
- if (min <= levels.warn) console.error(prefix("warn"), ...args.map(fmt));
667
+ if (min <= levels.warn) console.error(prefix("warn"), ...args.map(formatLogArg));
614
668
  },
615
669
  error: (...args) => {
616
- if (min <= levels.error) console.error(prefix("error"), ...args.map(fmt));
670
+ if (min <= levels.error) console.error(prefix("error"), ...args.map(formatLogArg));
617
671
  }
618
672
  };
619
673
  }
@@ -934,7 +988,7 @@ var GetProjectInfoInputSchema = z.object({
934
988
  expand: z.array(z.string()).optional().describe("Additional project details to include")
935
989
  });
936
990
  var CreateSubtaskInputSchema = z.object({
937
- parentIssueKey: z.string().describe("Parent issue key"),
991
+ parentIssueKey: z.string().describe("Parent issue key").refine((v) => isValidIssueKey(v), "Invalid issue key format"),
938
992
  summary: z.string().min(1).describe("Subtask summary/title"),
939
993
  description: z.union([z.string(), z.any()]).optional().describe("Subtask description. Accepts plain text or ADF object."),
940
994
  priority: z.string().optional().describe("Subtask priority"),
@@ -990,7 +1044,7 @@ var GetAttachmentsInputSchema = z.object({
990
1044
  issueKey: z.string().describe("Issue key to get attachments for (e.g., PROJECT-123)").refine((v) => isValidIssueKey(v), "Invalid issue key format")
991
1045
  });
992
1046
  var DeleteAttachmentInputSchema = z.object({
993
- attachmentId: z.string().min(1).describe("ID of the attachment to delete")
1047
+ attachmentId: z.string().regex(/^\d+$/, "Attachment ID must be numeric").describe("ID of the attachment to delete")
994
1048
  });
995
1049
  var GetTransitionsInputSchema = z.object({
996
1050
  issueKey: z.string().describe("Issue key to get available transitions for (e.g., PROJECT-123)").refine((v) => isValidIssueKey(v), "Invalid issue key format")
@@ -1023,6 +1077,12 @@ var GetIssueGraphInputSchema = z.object({
1023
1077
  ),
1024
1078
  direction: z.enum(["all", "inward", "outward"]).default("all").describe('Which link directions to follow: "all", "inward", or "outward" (default: "all")')
1025
1079
  });
1080
+ function pathSegment(value) {
1081
+ if (typeof value !== "string" || value === "" || value === "." || value === "..") {
1082
+ throw new Error(`Invalid identifier: ${JSON.stringify(value)}`);
1083
+ }
1084
+ return encodeURIComponent(value);
1085
+ }
1026
1086
  function ensureAdfDescription(desc, format = "markdown") {
1027
1087
  if (!desc) return desc;
1028
1088
  if (typeof desc === "object") return desc;
@@ -1198,7 +1258,7 @@ async function getIssue(issueKey, options = {}) {
1198
1258
  }
1199
1259
  const config = {
1200
1260
  method: "GET",
1201
- url: `/issue/${issueKey}`,
1261
+ url: `/issue/${pathSegment(issueKey)}`,
1202
1262
  params
1203
1263
  };
1204
1264
  return await makeJiraRequest(config);
@@ -1296,7 +1356,7 @@ async function updateIssue(issueKey, updates) {
1296
1356
  }
1297
1357
  const config = {
1298
1358
  method: "PUT",
1299
- url: `/issue/${issueKey}`,
1359
+ url: `/issue/${pathSegment(issueKey)}`,
1300
1360
  data: { fields }
1301
1361
  };
1302
1362
  await makeJiraRequest(config);
@@ -1315,7 +1375,7 @@ async function getMyIssues(options = {}) {
1315
1375
  async function getIssueTypes(projectKey) {
1316
1376
  let url = "/issuetype";
1317
1377
  if (projectKey) {
1318
- url = `/project/${projectKey}/issuetype`;
1378
+ url = `/project/${pathSegment(projectKey)}/issuetype`;
1319
1379
  }
1320
1380
  const config = {
1321
1381
  method: "GET",
@@ -1357,9 +1417,9 @@ async function getPriorities() {
1357
1417
  async function getStatuses(options = {}) {
1358
1418
  let url = "/status";
1359
1419
  if (options.projectKey && options.issueTypeId) {
1360
- url = `/project/${options.projectKey}/statuses`;
1420
+ url = `/project/${pathSegment(options.projectKey)}/statuses`;
1361
1421
  } else if (options.projectKey) {
1362
- url = `/project/${options.projectKey}/statuses`;
1422
+ url = `/project/${pathSegment(options.projectKey)}/statuses`;
1363
1423
  }
1364
1424
  const config = {
1365
1425
  method: "GET",
@@ -1387,7 +1447,7 @@ async function addComment(issueKey, body, visibility, format) {
1387
1447
  }
1388
1448
  const config = {
1389
1449
  method: "POST",
1390
- url: `/issue/${issueKey}/comment`,
1450
+ url: `/issue/${pathSegment(issueKey)}/comment`,
1391
1451
  data
1392
1452
  };
1393
1453
  return await makeJiraRequest(config);
@@ -1405,7 +1465,7 @@ async function getComments(issueKey, options = {}) {
1405
1465
  }
1406
1466
  const config = {
1407
1467
  method: "GET",
1408
- url: `/issue/${issueKey}/comment`,
1468
+ url: `/issue/${pathSegment(issueKey)}/comment`,
1409
1469
  params
1410
1470
  };
1411
1471
  return await makeJiraRequest(config);
@@ -1417,7 +1477,7 @@ async function getProjectDetails(projectKey, expand) {
1417
1477
  }
1418
1478
  const config = {
1419
1479
  method: "GET",
1420
- url: `/project/${projectKey}`,
1480
+ url: `/project/${pathSegment(projectKey)}`,
1421
1481
  params
1422
1482
  };
1423
1483
  return await makeJiraRequest(config);
@@ -1533,7 +1593,10 @@ async function addAttachment(issueKey, filename, content, isBase64 = true) {
1533
1593
  filename,
1534
1594
  contentType: "application/octet-stream"
1535
1595
  });
1536
- return await makeMultipartRequest(`/issue/${issueKey}/attachments`, formData);
1596
+ return await makeMultipartRequest(
1597
+ `/issue/${pathSegment(issueKey)}/attachments`,
1598
+ formData
1599
+ );
1537
1600
  }
1538
1601
  async function addAttachmentFromUrl(issueKey, fileUrl, filename) {
1539
1602
  const axios2 = (await import('axios')).default;
@@ -1564,7 +1627,10 @@ async function addAttachmentFromUrl(issueKey, fileUrl, filename) {
1564
1627
  filename,
1565
1628
  contentType: typeof response.headers["content-type"] === "string" ? response.headers["content-type"] : "application/octet-stream"
1566
1629
  });
1567
- return await makeMultipartRequest(`/issue/${issueKey}/attachments`, formData);
1630
+ return await makeMultipartRequest(
1631
+ `/issue/${pathSegment(issueKey)}/attachments`,
1632
+ formData
1633
+ );
1568
1634
  }
1569
1635
  async function getAttachments(issueKey) {
1570
1636
  const issue = await getIssue(issueKey, { fields: ["attachment"] });
@@ -1574,14 +1640,14 @@ async function getAttachments(issueKey) {
1574
1640
  async function deleteAttachment(attachmentId) {
1575
1641
  const config = {
1576
1642
  method: "DELETE",
1577
- url: `/attachment/${attachmentId}`
1643
+ url: `/attachment/${pathSegment(attachmentId)}`
1578
1644
  };
1579
1645
  await makeJiraRequest(config);
1580
1646
  }
1581
1647
  async function getTransitions(issueKey) {
1582
1648
  const config = {
1583
1649
  method: "GET",
1584
- url: `/issue/${issueKey}/transitions`
1650
+ url: `/issue/${pathSegment(issueKey)}/transitions`
1585
1651
  };
1586
1652
  const response = await makeJiraRequest(config);
1587
1653
  return response.transitions;
@@ -1609,7 +1675,7 @@ async function transitionIssue(issueKey, transitionId, options = {}) {
1609
1675
  }
1610
1676
  const config = {
1611
1677
  method: "POST",
1612
- url: `/issue/${issueKey}/transitions`,
1678
+ url: `/issue/${pathSegment(issueKey)}/transitions`,
1613
1679
  data
1614
1680
  };
1615
1681
  await makeJiraRequest(config);
@@ -2266,6 +2332,10 @@ async function handleGetIssue(input) {
2266
2332
  const validated = validateInput(GetIssueInputSchema, input);
2267
2333
  const trimmedInput = (validated.issueKey || "").trim();
2268
2334
  const key = extractIssueKey(trimmedInput) || trimmedInput;
2335
+ if (!/^[A-Za-z][A-Za-z0-9_]*-\d+$/.test(key) && !/^\d+$/.test(key)) {
2336
+ throw new Error(`${ERROR_MESSAGES.VALIDATION_ERROR}
2337
+ issueKey: Invalid issue key format`);
2338
+ }
2269
2339
  log4.info(`Getting issue ${key}...`);
2270
2340
  const getParams = {};
2271
2341
  if (validated.expand !== void 0) getParams.expand = validated.expand;