mcp-jira-stdio 1.7.1 → 1.8.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
@@ -4,6 +4,7 @@ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
4
4
  import { ListToolsRequestSchema, CallToolRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema } from '@modelcontextprotocol/sdk/types.js';
5
5
  import axios from 'axios';
6
6
  import { z } from 'zod';
7
+ import FormData from 'form-data';
7
8
  import mdToAdf from 'md-to-adf';
8
9
 
9
10
  var __create = Object.create;
@@ -440,7 +441,7 @@ var require_package2 = __commonJS({
440
441
  "package.json"(exports$1, module) {
441
442
  module.exports = {
442
443
  name: "mcp-jira-stdio",
443
- version: "1.7.1",
444
+ version: "1.8.1",
444
445
  description: "Model Context Protocol (MCP) server for Jira API integration",
445
446
  author: "graslt",
446
447
  license: "MIT",
@@ -491,6 +492,7 @@ var require_package2 = __commonJS({
491
492
  dependencies: {
492
493
  "@modelcontextprotocol/sdk": "^1.17.1",
493
494
  axios: "^1.7.0",
495
+ "form-data": "^4.0.5",
494
496
  "md-to-adf": "^0.5.0",
495
497
  zod: "^3.22.4"
496
498
  },
@@ -548,6 +550,13 @@ var ERROR_MESSAGES = {
548
550
  NETWORK_ERROR: "Network error occurred while connecting to Jira.",
549
551
  VALIDATION_ERROR: "Input validation failed."
550
552
  };
553
+ var ATTACHMENT_CONFIG = {
554
+ MAX_FILE_SIZE: 50 * 1024 * 1024,
555
+ // 50 MB - Jira attachment size limit
556
+ MAX_BASE64_SIZE: 10 * 1024 * 1024,
557
+ // 10 MB for base64 content (token limit warning)
558
+ ALLOWED_PROTOCOLS: ["http:", "https:"]
559
+ };
551
560
  var TOOL_NAMES = {
552
561
  GET_VISIBLE_PROJECTS: "jira_get_visible_projects",
553
562
  GET_ISSUE: "jira_get_issue",
@@ -565,7 +574,10 @@ var TOOL_NAMES = {
565
574
  GET_CREATE_META: "jira_get_create_meta",
566
575
  GET_CUSTOM_FIELDS: "jira_get_custom_fields",
567
576
  CREATE_ISSUE_LINK: "jira_create_issue_link",
568
- GET_COMMENTS: "jira_get_comments"
577
+ GET_COMMENTS: "jira_get_comments",
578
+ ADD_ATTACHMENT: "jira_add_attachment",
579
+ GET_ATTACHMENTS: "jira_get_attachments",
580
+ DELETE_ATTACHMENT: "jira_delete_attachment"
569
581
  };
570
582
 
571
583
  // src/utils/logger.ts
@@ -605,6 +617,7 @@ function createLogger(scope) {
605
617
 
606
618
  // src/utils/jira-auth.ts
607
619
  var jiraClient = null;
620
+ var jiraMultipartClient = null;
608
621
  var log = createLogger("jira-auth");
609
622
  function validateAuth() {
610
623
  const baseUrl = process.env.JIRA_BASE_URL;
@@ -707,6 +720,96 @@ async function makeJiraRequest(config, retryConfig = {
707
720
  }
708
721
  throw lastError;
709
722
  }
723
+ function getMultipartClient() {
724
+ if (jiraMultipartClient) {
725
+ return jiraMultipartClient;
726
+ }
727
+ const auth = validateAuth();
728
+ jiraMultipartClient = axios.create({
729
+ baseURL: `${auth.baseUrl}${JIRA_CONFIG.BASE_PATH}`,
730
+ timeout: JIRA_CONFIG.DEFAULT_TIMEOUT,
731
+ headers: {
732
+ Accept: "application/json",
733
+ "X-Atlassian-Token": "no-check"
734
+ },
735
+ auth: {
736
+ username: auth.email,
737
+ password: auth.apiToken
738
+ }
739
+ });
740
+ jiraMultipartClient.interceptors.request.use(
741
+ (config) => {
742
+ log.debug(
743
+ `Multipart Request: ${config.method?.toUpperCase()} ${config.baseURL}${config.url}`
744
+ );
745
+ return config;
746
+ },
747
+ (error) => {
748
+ log.error("Multipart request error:", error);
749
+ return Promise.reject(error);
750
+ }
751
+ );
752
+ jiraMultipartClient.interceptors.response.use(
753
+ (response) => {
754
+ log.debug(`Multipart Response: ${response.status} ${response.config.url}`);
755
+ return response;
756
+ },
757
+ (error) => {
758
+ log.error(`Jira API multipart error: ${error.response?.status} ${error.config?.url}`);
759
+ if (error.response?.status === 401) {
760
+ throw new Error(ERROR_MESSAGES.INVALID_CREDENTIALS);
761
+ } else if (error.response?.status === 404) {
762
+ throw new Error("Resource not found or insufficient permissions");
763
+ } else if (error.response?.status === 429) {
764
+ throw new Error(ERROR_MESSAGES.RATE_LIMIT_EXCEEDED);
765
+ } else if (!error.response) {
766
+ throw new Error(ERROR_MESSAGES.NETWORK_ERROR);
767
+ }
768
+ return Promise.reject(error);
769
+ }
770
+ );
771
+ return jiraMultipartClient;
772
+ }
773
+ async function makeMultipartRequest(url, formData, retryConfig = {
774
+ maxRetries: JIRA_CONFIG.MAX_RETRIES,
775
+ retryDelay: JIRA_CONFIG.RETRY_DELAY
776
+ }) {
777
+ const client = getMultipartClient();
778
+ let lastError;
779
+ for (let attempt = 0; attempt <= retryConfig.maxRetries; attempt++) {
780
+ try {
781
+ const response = await client.post(url, formData, {
782
+ headers: {
783
+ ...formData.getHeaders()
784
+ }
785
+ });
786
+ return response.data;
787
+ } catch (error) {
788
+ lastError = error;
789
+ if (error.response?.status === 401 || error.response?.status === 403 || error.response?.status === 404 || error.response?.status >= 400 && error.response?.status < 500 && error.response?.status !== 429) {
790
+ break;
791
+ }
792
+ if (attempt === retryConfig.maxRetries) {
793
+ break;
794
+ }
795
+ const retryAfterHeader = error.response?.headers?.["retry-after"];
796
+ let delayMs = 0;
797
+ if (retryAfterHeader) {
798
+ const seconds = Number(retryAfterHeader);
799
+ delayMs = !Number.isNaN(seconds) ? seconds * 1e3 : retryConfig.retryDelay;
800
+ } else {
801
+ const base = retryConfig.retryDelay * Math.pow(2, attempt);
802
+ const jitter = Math.floor(Math.random() * (retryConfig.retryDelay / 2));
803
+ delayMs = base + jitter;
804
+ }
805
+ log.warn(
806
+ `Retrying multipart request in ${delayMs}ms (attempt ${attempt + 1}/${retryConfig.maxRetries})`
807
+ );
808
+ await new Promise((resolve) => setTimeout(resolve, delayMs));
809
+ }
810
+ }
811
+ throw lastError;
812
+ }
710
813
  function validateInput(schema, input) {
711
814
  try {
712
815
  return schema.parse(input);
@@ -855,6 +958,33 @@ var GetCommentsInputSchema = z.object({
855
958
  orderBy: z.enum(["created", "-created", "+created"]).optional().describe('Sort order for comments: "created" (oldest first), "-created" (newest first)'),
856
959
  startAt: z.number().min(0).optional().describe("Index of first comment to return (for pagination)")
857
960
  });
961
+ var AddAttachmentInputSchema = z.object({
962
+ issueKey: z.string().describe("Issue key to add attachment to (e.g., PROJECT-123)").refine((v) => isValidIssueKey(v), "Invalid issue key format"),
963
+ filename: z.string().min(1).describe("Name of the file to attach"),
964
+ // Option 1: Remote URL (efficient - ~60 tokens)
965
+ fileUrl: z.string().url().optional().describe(
966
+ "URL to download file from (efficient for remote files, ~60 tokens). Upload large files to Dropbox/S3/imgur first, then use URL."
967
+ ),
968
+ // Option 2: Base64 content (for small files or backwards compatibility)
969
+ content: z.string().optional().describe(
970
+ "Base64-encoded or plain text content. WARNING: HIGH token cost (~330k tokens for 1MB file). Use fileUrl for large files or upload to cloud storage first."
971
+ ),
972
+ isBase64: z.boolean().optional().default(true).describe(
973
+ "Whether content is base64-encoded (default: true). Set to false for plain text files."
974
+ )
975
+ }).refine(
976
+ (data) => {
977
+ const sources = [data.fileUrl, data.content].filter(Boolean);
978
+ return sources.length === 1;
979
+ },
980
+ { message: "Exactly one of fileUrl or content must be provided" }
981
+ );
982
+ var GetAttachmentsInputSchema = z.object({
983
+ issueKey: z.string().describe("Issue key to get attachments for (e.g., PROJECT-123)").refine((v) => isValidIssueKey(v), "Invalid issue key format")
984
+ });
985
+ var DeleteAttachmentInputSchema = z.object({
986
+ attachmentId: z.string().min(1).describe("ID of the attachment to delete")
987
+ });
858
988
  function ensureAdfDescription(desc, format = "markdown") {
859
989
  if (!desc) return desc;
860
990
  if (typeof desc === "object") return desc;
@@ -1334,6 +1464,63 @@ async function createIssueLink(fromIssue, toIssue, linkType) {
1334
1464
  };
1335
1465
  await makeJiraRequest(config);
1336
1466
  }
1467
+ async function addAttachment(issueKey, filename, content, isBase64 = true) {
1468
+ const formData = new FormData();
1469
+ let fileBuffer;
1470
+ if (isBase64) {
1471
+ fileBuffer = Buffer.from(content, "base64");
1472
+ } else {
1473
+ fileBuffer = Buffer.from(content, "utf-8");
1474
+ }
1475
+ formData.append("file", fileBuffer, {
1476
+ filename,
1477
+ contentType: "application/octet-stream"
1478
+ });
1479
+ return await makeMultipartRequest(`/issue/${issueKey}/attachments`, formData);
1480
+ }
1481
+ async function addAttachmentFromUrl(issueKey, fileUrl, filename) {
1482
+ const axios2 = (await import('axios')).default;
1483
+ const urlObj = new URL(fileUrl);
1484
+ if (urlObj.hostname === "localhost" || urlObj.hostname.startsWith("127.")) {
1485
+ throw new Error("Cannot download from localhost (SSRF protection)");
1486
+ }
1487
+ if (urlObj.hostname.startsWith("192.168.") || urlObj.hostname.startsWith("10.")) {
1488
+ throw new Error("Cannot download from private network (SSRF protection)");
1489
+ }
1490
+ if (urlObj.hostname.startsWith("172.")) {
1491
+ const parts = urlObj.hostname.split(".");
1492
+ const secondOctet = parts[1] ? parseInt(parts[1]) : 0;
1493
+ if (secondOctet >= 16 && secondOctet <= 31) {
1494
+ throw new Error("Cannot download from private network (SSRF protection)");
1495
+ }
1496
+ }
1497
+ const response = await axios2.get(fileUrl, {
1498
+ responseType: "arraybuffer",
1499
+ maxContentLength: 50 * 1024 * 1024,
1500
+ // 50 MB limit
1501
+ timeout: 3e4
1502
+ // 30 seconds
1503
+ });
1504
+ const fileBuffer = Buffer.from(response.data);
1505
+ const formData = new FormData();
1506
+ formData.append("file", fileBuffer, {
1507
+ filename,
1508
+ contentType: response.headers["content-type"] || "application/octet-stream"
1509
+ });
1510
+ return await makeMultipartRequest(`/issue/${issueKey}/attachments`, formData);
1511
+ }
1512
+ async function getAttachments(issueKey) {
1513
+ const issue = await getIssue(issueKey, { fields: ["attachment"] });
1514
+ const attachments = issue.fields.attachment || [];
1515
+ return attachments;
1516
+ }
1517
+ async function deleteAttachment(attachmentId) {
1518
+ const config = {
1519
+ method: "DELETE",
1520
+ url: `/attachment/${attachmentId}`
1521
+ };
1522
+ await makeJiraRequest(config);
1523
+ }
1337
1524
 
1338
1525
  // src/utils/redaction.ts
1339
1526
  function isRedactionEnabled() {
@@ -1814,6 +2001,67 @@ ${fieldsList}`
1814
2001
  ]
1815
2002
  };
1816
2003
  }
2004
+ function formatFileSize(bytes) {
2005
+ if (bytes < 1024) return `${bytes} B`;
2006
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
2007
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
2008
+ }
2009
+ function formatAttachmentResponse(attachment, issueKey) {
2010
+ const authorName = attachment.author?.displayName || "Unknown";
2011
+ const createdDate = attachment.created ? new Date(attachment.created).toISOString() : "N/A";
2012
+ return {
2013
+ content: [
2014
+ {
2015
+ type: "text",
2016
+ text: `**Attachment uploaded successfully to ${issueKey}**
2017
+
2018
+ **Filename:** ${attachment.filename}
2019
+ **ID:** ${attachment.id}
2020
+ **Size:** ${formatFileSize(attachment.size)}
2021
+ **MIME Type:** ${attachment.mimeType}
2022
+ **Author:** ${authorName}
2023
+ **Created:** ${createdDate}
2024
+ **Download URL:** ${attachment.content}
2025
+ ${attachment.thumbnail ? `**Thumbnail:** ${attachment.thumbnail}` : ""}
2026
+
2027
+ \u{1F4A1} **Tip:** To embed this image in a comment or description, use: \`!${attachment.filename}!\` or \`!${attachment.filename}|thumbnail!\``
2028
+ }
2029
+ ]
2030
+ };
2031
+ }
2032
+ function formatAttachmentsListResponse(attachments, issueKey) {
2033
+ if (!attachments || attachments.length === 0) {
2034
+ return {
2035
+ content: [
2036
+ {
2037
+ type: "text",
2038
+ text: `No attachments found for issue ${issueKey}.`
2039
+ }
2040
+ ]
2041
+ };
2042
+ }
2043
+ const attachmentsList = attachments.map((attachment) => {
2044
+ const authorName = attachment.author?.displayName || "Unknown";
2045
+ const createdDate = attachment.created ? new Date(attachment.created).toISOString() : "N/A";
2046
+ const thumbnailInfo = attachment.thumbnail ? " (has thumbnail)" : "";
2047
+ return `\u2022 **${attachment.filename}** (ID: ${attachment.id})
2048
+ Size: ${formatFileSize(attachment.size)} | Type: ${attachment.mimeType}${thumbnailInfo}
2049
+ Author: ${authorName} | Created: ${createdDate}
2050
+ URL: ${attachment.content}`;
2051
+ }).join("\n\n");
2052
+ return {
2053
+ content: [
2054
+ {
2055
+ type: "text",
2056
+ text: `**Attachments for ${issueKey}** (${attachments.length} attachment${attachments.length !== 1 ? "s" : ""}):
2057
+
2058
+ ${attachmentsList}
2059
+
2060
+ \u{1F4A1} **Tip:** To embed an image in a comment or description, use: \`!filename.png!\` or \`!filename.png|thumbnail!\``
2061
+ }
2062
+ ]
2063
+ };
2064
+ }
1817
2065
 
1818
2066
  // src/utils/error-handler.ts
1819
2067
  var log2 = createLogger("error-handler");
@@ -2740,6 +2988,148 @@ async function handleGetComments(input) {
2740
2988
  }
2741
2989
  }
2742
2990
 
2991
+ // src/tools/add-attachment.ts
2992
+ var log20 = createLogger("tool:add-attachment");
2993
+ var addAttachmentTool = {
2994
+ name: TOOL_NAMES.ADD_ATTACHMENT,
2995
+ description: `Uploads an attachment (image, document, etc.) to a Jira issue.
2996
+
2997
+ **EFFICIENT METHOD (recommended for large files):**
2998
+ - fileUrl: Provide URL to remote file - MINIMAL tokens (~60 tokens)
2999
+ Example: Upload to Dropbox/S3/imgur first, then provide URL
3000
+
3001
+ **DIRECT METHOD (for small files):**
3002
+ - content: Base64 encoded content - WARNING: HIGH token cost (~330,000 tokens for 1MB file)
3003
+ Only suitable for small files (< 500 KB)
3004
+
3005
+ Returns attachment metadata including ID and download URL. To reference the image in a comment or description, use wiki markup: !filename.png! or !filename.png|thumbnail!`,
3006
+ inputSchema: {
3007
+ type: "object",
3008
+ properties: {
3009
+ issueKey: {
3010
+ type: "string",
3011
+ description: "Issue key to add attachment to (e.g., PROJECT-123)"
3012
+ },
3013
+ filename: {
3014
+ type: "string",
3015
+ description: "Name of the file to attach (e.g., screenshot.png, document.pdf)",
3016
+ minLength: 1
3017
+ },
3018
+ fileUrl: {
3019
+ type: "string",
3020
+ description: "URL to download file from (efficient for remote files, ~60 tokens). Upload large files to Dropbox/S3/imgur first, then use URL.",
3021
+ format: "uri"
3022
+ },
3023
+ content: {
3024
+ type: "string",
3025
+ description: "Base64-encoded or plain text content. WARNING: HIGH token cost (~330k tokens for 1MB file). Use fileUrl for large files or upload to cloud storage first."
3026
+ },
3027
+ isBase64: {
3028
+ type: "boolean",
3029
+ description: "Whether content is base64-encoded (default: true). Set to false for plain text files.",
3030
+ default: true
3031
+ }
3032
+ },
3033
+ required: ["issueKey", "filename"]
3034
+ }
3035
+ };
3036
+ async function handleAddAttachment(input) {
3037
+ try {
3038
+ const validated = validateInput(AddAttachmentInputSchema, input);
3039
+ const { issueKey, filename, fileUrl, content, isBase64 } = validated;
3040
+ log20.info(`Adding attachment "${filename}" to issue ${issueKey}...`);
3041
+ let attachments;
3042
+ if (fileUrl) {
3043
+ log20.info("Using URL method (efficient - ~60 tokens)");
3044
+ attachments = await addAttachmentFromUrl(issueKey, fileUrl, filename);
3045
+ } else if (content) {
3046
+ if (content.length > ATTACHMENT_CONFIG.MAX_BASE64_SIZE) {
3047
+ log20.warn(
3048
+ `Large base64 content detected (${content.length} chars = ~${Math.round(content.length / 4)} tokens). Consider uploading to cloud storage and using fileUrl instead.`
3049
+ );
3050
+ }
3051
+ log20.info("Using base64 content method (HIGH token cost for large files)");
3052
+ attachments = await addAttachment(issueKey, filename, content, isBase64);
3053
+ } else {
3054
+ throw new Error("No file source provided (fileUrl or content required)");
3055
+ }
3056
+ if (!attachments || attachments.length === 0) {
3057
+ throw new Error("Failed to upload attachment - no attachment returned from Jira");
3058
+ }
3059
+ log20.info(`Added attachment "${filename}" to ${issueKey}`);
3060
+ return formatAttachmentResponse(attachments[0], issueKey);
3061
+ } catch (error) {
3062
+ log20.error("Error in handleAddAttachment:", error);
3063
+ return handleError(error);
3064
+ }
3065
+ }
3066
+
3067
+ // src/tools/get-attachments.ts
3068
+ var log21 = createLogger("tool:get-attachments");
3069
+ var getAttachmentsTool = {
3070
+ name: TOOL_NAMES.GET_ATTACHMENTS,
3071
+ description: "Lists all attachments on a Jira issue. Returns attachment metadata including filename, size, MIME type, author, and download URL.",
3072
+ inputSchema: {
3073
+ type: "object",
3074
+ properties: {
3075
+ issueKey: {
3076
+ type: "string",
3077
+ description: "Issue key to get attachments for (e.g., PROJECT-123)"
3078
+ }
3079
+ },
3080
+ required: ["issueKey"]
3081
+ }
3082
+ };
3083
+ async function handleGetAttachments(input) {
3084
+ try {
3085
+ const validated = validateInput(GetAttachmentsInputSchema, input);
3086
+ log21.info(`Getting attachments for issue ${validated.issueKey}...`);
3087
+ const attachments = await getAttachments(validated.issueKey);
3088
+ log21.info(`Found ${attachments.length} attachment(s) for ${validated.issueKey}`);
3089
+ return formatAttachmentsListResponse(attachments, validated.issueKey);
3090
+ } catch (error) {
3091
+ log21.error("Error in handleGetAttachments:", error);
3092
+ return handleError(error);
3093
+ }
3094
+ }
3095
+
3096
+ // src/tools/delete-attachment.ts
3097
+ var log22 = createLogger("tool:delete-attachment");
3098
+ var deleteAttachmentTool = {
3099
+ name: TOOL_NAMES.DELETE_ATTACHMENT,
3100
+ description: "Deletes an attachment from Jira by its attachment ID. Use jira_get_attachments to find attachment IDs.",
3101
+ inputSchema: {
3102
+ type: "object",
3103
+ properties: {
3104
+ attachmentId: {
3105
+ type: "string",
3106
+ description: "ID of the attachment to delete (can be found using jira_get_attachments)",
3107
+ minLength: 1
3108
+ }
3109
+ },
3110
+ required: ["attachmentId"]
3111
+ }
3112
+ };
3113
+ async function handleDeleteAttachment(input) {
3114
+ try {
3115
+ const validated = validateInput(DeleteAttachmentInputSchema, input);
3116
+ log22.info(`Deleting attachment ${validated.attachmentId}...`);
3117
+ await deleteAttachment(validated.attachmentId);
3118
+ log22.info(`Deleted attachment ${validated.attachmentId}`);
3119
+ return {
3120
+ content: [
3121
+ {
3122
+ type: "text",
3123
+ text: `Successfully deleted attachment ${validated.attachmentId}`
3124
+ }
3125
+ ]
3126
+ };
3127
+ } catch (error) {
3128
+ log22.error("Error in handleDeleteAttachment:", error);
3129
+ return handleError(error);
3130
+ }
3131
+ }
3132
+
2743
3133
  // src/index.ts
2744
3134
  if (process.env.NODE_ENV !== "production") {
2745
3135
  try {
@@ -2748,7 +3138,7 @@ if (process.env.NODE_ENV !== "production") {
2748
3138
  } catch {
2749
3139
  }
2750
3140
  }
2751
- var log20 = createLogger("server");
3141
+ var log23 = createLogger("server");
2752
3142
  var toolHandlers = /* @__PURE__ */ new Map([
2753
3143
  [TOOL_NAMES.GET_VISIBLE_PROJECTS, handleGetVisibleProjects],
2754
3144
  [TOOL_NAMES.GET_ISSUE, handleGetIssue],
@@ -2766,7 +3156,10 @@ var toolHandlers = /* @__PURE__ */ new Map([
2766
3156
  [TOOL_NAMES.GET_CREATE_META, handleGetCreateMeta],
2767
3157
  [TOOL_NAMES.GET_CUSTOM_FIELDS, handleGetCustomFields],
2768
3158
  [TOOL_NAMES.CREATE_ISSUE_LINK, handleCreateIssueLink],
2769
- [TOOL_NAMES.GET_COMMENTS, handleGetComments]
3159
+ [TOOL_NAMES.GET_COMMENTS, handleGetComments],
3160
+ [TOOL_NAMES.ADD_ATTACHMENT, handleAddAttachment],
3161
+ [TOOL_NAMES.GET_ATTACHMENTS, handleGetAttachments],
3162
+ [TOOL_NAMES.DELETE_ATTACHMENT, handleDeleteAttachment]
2770
3163
  ]);
2771
3164
  var allTools = [
2772
3165
  getVisibleProjectsTool,
@@ -2785,7 +3178,10 @@ var allTools = [
2785
3178
  getCreateMetaTool,
2786
3179
  getCustomFieldsTool,
2787
3180
  createIssueLinkTool,
2788
- getCommentsTool
3181
+ getCommentsTool,
3182
+ addAttachmentTool,
3183
+ getAttachmentsTool,
3184
+ deleteAttachmentTool
2789
3185
  ];
2790
3186
  async function main() {
2791
3187
  const isDryRun = (process.env.DRY_RUN || "").toLowerCase() === "1" || (process.env.DRY_RUN || "").toLowerCase() === "true";
@@ -2856,7 +3252,7 @@ async function main() {
2856
3252
  console.error("\u{1F680} Jira MCP server running on stdio");
2857
3253
  }
2858
3254
  main().catch((error) => {
2859
- log20.error("Fatal error:", error);
3255
+ log23.error("Fatal error:", error);
2860
3256
  process.exit(1);
2861
3257
  });
2862
3258
  //# sourceMappingURL=index.js.map