mcp-jira-stdio 1.5.5 → 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/README.md CHANGED
@@ -21,10 +21,13 @@ A Model Context Protocol (MCP) server for Jira API integration. Enables reading,
21
21
  The fastest way to add this MCP server to Claude Code:
22
22
 
23
23
  ```bash
24
- claude mcp add jira npx mcp-jira-stdio@latest
24
+ claude mcp add jira npx mcp-jira-stdio@latest \
25
+ --env JIRA_BASE_URL=https://yourcompany.atlassian.net \
26
+ --env JIRA_EMAIL=your-email@example.com \
27
+ --env JIRA_API_TOKEN=your-api-token
25
28
  ```
26
29
 
27
- You will be prompted to enter your Jira credentials:
30
+ Replace the values with your actual Jira credentials:
28
31
  - **JIRA_BASE_URL**: Your Jira instance URL (e.g., `https://yourcompany.atlassian.net`)
29
32
  - **JIRA_EMAIL**: Your Jira account email
30
33
  - **JIRA_API_TOKEN**: Your Jira API token ([generate here](https://id.atlassian.com/manage-profile/security/api-tokens))
@@ -119,7 +122,10 @@ task jira:projects
119
122
  Use the quick install command (recommended):
120
123
 
121
124
  ```bash
122
- claude mcp add jira npx mcp-jira-stdio@latest
125
+ claude mcp add jira npx mcp-jira-stdio@latest \
126
+ --env JIRA_BASE_URL=https://yourcompany.atlassian.net \
127
+ --env JIRA_EMAIL=your-email@example.com \
128
+ --env JIRA_API_TOKEN=your-api-token
123
129
  ```
124
130
 
125
131
  #### For Claude Desktop
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 mdToAdf from 'md-to-adf';
7
8
 
8
9
  var __create = Object.create;
9
10
  var __defProp = Object.defineProperty;
@@ -439,7 +440,7 @@ var require_package2 = __commonJS({
439
440
  "package.json"(exports, module) {
440
441
  module.exports = {
441
442
  name: "mcp-jira-stdio",
442
- version: "1.5.5",
443
+ version: "1.6.1",
443
444
  description: "Model Context Protocol (MCP) server for Jira API integration",
444
445
  author: "graslt",
445
446
  license: "MIT",
@@ -490,6 +491,7 @@ var require_package2 = __commonJS({
490
491
  dependencies: {
491
492
  "@modelcontextprotocol/sdk": "^1.17.1",
492
493
  axios: "^1.7.0",
494
+ "md-to-adf": "^0.6.4",
493
495
  zod: "^3.22.4"
494
496
  },
495
497
  devDependencies: {
@@ -557,7 +559,8 @@ var TOOL_NAMES = {
557
559
  GET_PROJECT_INFO: "jira_get_project_info",
558
560
  CREATE_SUBTASK: "jira_create_subtask",
559
561
  GET_CREATE_META: "jira_get_create_meta",
560
- GET_CUSTOM_FIELDS: "jira_get_custom_fields"
562
+ GET_CUSTOM_FIELDS: "jira_get_custom_fields",
563
+ CREATE_ISSUE_LINK: "jira_create_issue_link"
561
564
  };
562
565
 
563
566
  // src/utils/logger.ts
@@ -759,7 +762,10 @@ var CreateIssueInputSchema = z.object({
759
762
  customFields: z.record(z.any()).optional().describe(
760
763
  'Additional Jira field mappings, e.g. { "customfield_12345": value }. Use for required custom fields.'
761
764
  ),
762
- returnIssue: z.boolean().optional().describe("When false, skip fetching full issue after creation")
765
+ returnIssue: z.boolean().optional().describe("When false, skip fetching full issue after creation"),
766
+ format: z.enum(["markdown", "adf", "plain"]).optional().default("markdown").describe(
767
+ 'Description format: "markdown" (converts Markdown to ADF), "adf" (use as-is ADF object), "plain" (converts plain text to ADF with basic formatting). Default: "markdown"'
768
+ )
763
769
  });
764
770
  var UpdateIssueInputSchema = z.object({
765
771
  issueKey: z.string().describe("Issue key to update").refine((v) => isValidIssueKey(v), "Invalid issue key format"),
@@ -769,7 +775,10 @@ var UpdateIssueInputSchema = z.object({
769
775
  assignee: z.string().optional().describe("New assignee account ID"),
770
776
  labels: z.array(z.string()).optional().describe("New labels (replaces existing)"),
771
777
  components: z.array(z.string()).optional().describe("New components (replaces existing)"),
772
- returnIssue: z.boolean().optional().describe("When false, skip fetching full issue after update")
778
+ returnIssue: z.boolean().optional().describe("When false, skip fetching full issue after update"),
779
+ format: z.enum(["markdown", "adf", "plain"]).optional().default("markdown").describe(
780
+ 'Description format: "markdown" (converts Markdown to ADF), "adf" (use as-is ADF object), "plain" (converts plain text to ADF with basic formatting). Default: "markdown"'
781
+ )
773
782
  });
774
783
  var GetMyIssuesInputSchema = z.object({
775
784
  nextPageToken: z.string().optional().describe(
@@ -800,7 +809,10 @@ var AddCommentInputSchema = z.object({
800
809
  visibility: z.object({
801
810
  type: z.enum(["group", "role"]).describe("Visibility type"),
802
811
  value: z.string().describe("Group name or role name")
803
- }).optional().describe("Comment visibility restrictions")
812
+ }).optional().describe("Comment visibility restrictions"),
813
+ format: z.enum(["markdown", "adf", "plain"]).optional().default("markdown").describe(
814
+ 'Comment format: "markdown" (converts Markdown to ADF), "adf" (use as-is ADF object), "plain" (converts plain text to ADF with basic formatting). Default: "markdown"'
815
+ )
804
816
  });
805
817
  var GetProjectInfoInputSchema = z.object({
806
818
  projectKey: z.string().describe("Project key to get detailed information for").refine((v) => isValidProjectKey(v), "Invalid project key format"),
@@ -813,7 +825,10 @@ var CreateSubtaskInputSchema = z.object({
813
825
  priority: z.string().optional().describe("Subtask priority"),
814
826
  assignee: z.string().optional().describe("Assignee account ID"),
815
827
  labels: z.array(z.string()).optional().describe("Subtask labels"),
816
- components: z.array(z.string()).optional().describe("Component names")
828
+ components: z.array(z.string()).optional().describe("Component names"),
829
+ format: z.enum(["markdown", "adf", "plain"]).optional().default("markdown").describe(
830
+ 'Description format: "markdown" (converts Markdown to ADF), "adf" (use as-is ADF object), "plain" (converts plain text to ADF with basic formatting). Default: "markdown"'
831
+ )
817
832
  });
818
833
  var GetCreateMetaInputSchema = z.object({
819
834
  projectKey: z.string().describe("Project key to get create metadata for").refine((v) => isValidProjectKey(v), "Invalid project key format"),
@@ -822,12 +837,28 @@ var GetCreateMetaInputSchema = z.object({
822
837
  var GetCustomFieldsInputSchema = z.object({
823
838
  projectKey: z.string().optional().describe("Project key to filter custom fields (optional)").refine((v) => v ? isValidProjectKey(v) : true, "Invalid project key format")
824
839
  });
825
-
826
- // src/utils/api-helpers.ts
827
- function ensureAdfDescription(desc) {
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
+ });
847
+ function ensureAdfDescription(desc, format = "markdown") {
828
848
  if (!desc) return desc;
829
849
  if (typeof desc === "object") return desc;
830
850
  if (typeof desc !== "string") return desc;
851
+ if (format === "markdown") {
852
+ try {
853
+ return mdToAdf(desc);
854
+ } catch (error) {
855
+ console.warn("Markdown to ADF conversion failed, falling back to plain text:", error);
856
+ format = "plain";
857
+ }
858
+ }
859
+ if (format === "adf") {
860
+ return desc;
861
+ }
831
862
  const urlRegex = /https?:\/\/[^\s)]+/g;
832
863
  const makeTextNodes = (text) => {
833
864
  const nodes = [];
@@ -993,8 +1024,8 @@ async function searchIssues(options) {
993
1024
  if (options.fields) {
994
1025
  data.fields = options.fields;
995
1026
  }
996
- if (options.expand) {
997
- data.expand = options.expand;
1027
+ if (options.expand && options.expand.length > 0) {
1028
+ data.expand = options.expand.join(",");
998
1029
  }
999
1030
  const config = {
1000
1031
  method: "POST",
@@ -1010,7 +1041,10 @@ async function createIssue(issueData, options = { returnIssue: true }) {
1010
1041
  issuetype: { name: issueData.issueType }
1011
1042
  };
1012
1043
  if (issueData.description !== void 0) {
1013
- fields.description = ensureAdfDescription(issueData.description);
1044
+ fields.description = ensureAdfDescription(
1045
+ issueData.description,
1046
+ issueData.format || "markdown"
1047
+ );
1014
1048
  }
1015
1049
  if (issueData.priority) {
1016
1050
  fields.priority = { name: issueData.priority };
@@ -1048,7 +1082,7 @@ async function updateIssue(issueKey, updates) {
1048
1082
  fields.summary = updates.summary;
1049
1083
  }
1050
1084
  if (updates.description !== void 0) {
1051
- fields.description = ensureAdfDescription(updates.description);
1085
+ fields.description = ensureAdfDescription(updates.description, updates.format || "markdown");
1052
1086
  }
1053
1087
  if (updates.priority !== void 0) {
1054
1088
  fields.priority = { name: updates.priority };
@@ -1145,8 +1179,8 @@ async function getStatuses(options = {}) {
1145
1179
  }
1146
1180
  return await makeJiraRequest(config);
1147
1181
  }
1148
- async function addComment(issueKey, body, visibility) {
1149
- const adfBody = ensureAdfDescription(body);
1182
+ async function addComment(issueKey, body, visibility, format) {
1183
+ const adfBody = ensureAdfDescription(body, format || "markdown");
1150
1184
  const data = {
1151
1185
  body: adfBody
1152
1186
  };
@@ -1187,7 +1221,10 @@ async function createSubtask(parentIssueKey, subtaskData) {
1187
1221
  issuetype: { id: subtaskType.id }
1188
1222
  };
1189
1223
  if (subtaskData.description !== void 0) {
1190
- fields.description = ensureAdfDescription(subtaskData.description);
1224
+ fields.description = ensureAdfDescription(
1225
+ subtaskData.description,
1226
+ subtaskData.format || "markdown"
1227
+ );
1191
1228
  }
1192
1229
  if (subtaskData.priority) {
1193
1230
  fields.priority = { name: subtaskData.priority };
@@ -1234,6 +1271,40 @@ async function getFields() {
1234
1271
  };
1235
1272
  return await makeJiraRequest(config);
1236
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
+ }
1237
1308
 
1238
1309
  // src/utils/redaction.ts
1239
1310
  function isRedactionEnabled() {
@@ -1363,14 +1434,15 @@ function formatSearchResultsResponse(result) {
1363
1434
  return `\u2022 **${issue.key}** - ${summary}
1364
1435
  Status: ${status} | Assignee: ${assignee} | Priority: ${priority}`;
1365
1436
  }).join("\n\n");
1437
+ const total = result.total ?? result.issues.length;
1366
1438
  const rangeStart = result.startAt + 1;
1367
- const rangeEnd = Math.min(result.startAt + result.maxResults, result.total);
1368
- 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;
1369
1441
  let paginationInfo = "";
1370
1442
  if (showRangeInline) {
1371
1443
  paginationInfo = `
1372
1444
 
1373
- *Showing ${rangeStart}-${rangeEnd} of ${result.total} results*`;
1445
+ *Showing ${rangeStart}-${rangeEnd} of ${total} results*`;
1374
1446
  }
1375
1447
  if (result.nextPageToken && !result.isLast) {
1376
1448
  paginationInfo += `
@@ -1382,7 +1454,7 @@ function formatSearchResultsResponse(result) {
1382
1454
  content: [
1383
1455
  {
1384
1456
  type: "text",
1385
- text: `Found ${result.total} issue(s)${showRangeInline ? ` (showing ${rangeStart}-${rangeEnd})` : ""}:
1457
+ text: `Found ${total} issue(s)${showRangeInline ? ` (showing ${rangeStart}-${rangeEnd})` : ""}:
1386
1458
 
1387
1459
  ${issuesList}${paginationInfo}`
1388
1460
  }
@@ -1859,7 +1931,7 @@ async function handleSearchIssues(input) {
1859
1931
  if (validated.fields !== void 0) searchParams.fields = validated.fields;
1860
1932
  if (validated.expand !== void 0) searchParams.expand = validated.expand;
1861
1933
  const result = await searchIssues(searchParams);
1862
- log5.info(`Found ${result.total} issue(s), showing ${result.issues.length}`);
1934
+ log5.info(`Found ${result.total ?? 0} issue(s), showing ${result.issues.length}`);
1863
1935
  return formatSearchResultsResponse(result);
1864
1936
  } catch (error) {
1865
1937
  log5.error("Error in handleSearchIssues:", error);
@@ -1911,7 +1983,9 @@ async function handleGetMyIssues(input) {
1911
1983
  if (validated.fields !== void 0) getParams.fields = validated.fields;
1912
1984
  if (validated.expand !== void 0) getParams.expand = validated.expand;
1913
1985
  const result = await getMyIssues(getParams);
1914
- 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
+ );
1915
1989
  return formatSearchResultsResponse(result);
1916
1990
  } catch (error) {
1917
1991
  log6.error("Error in handleGetMyIssues:", error);
@@ -2088,7 +2162,7 @@ async function handleGetStatuses(input) {
2088
2162
  var log11 = createLogger("tool:create-issue");
2089
2163
  var createIssueTool = {
2090
2164
  name: TOOL_NAMES.CREATE_ISSUE,
2091
- description: 'Creates a new Jira issue in the specified project. Supports setting issue type, priority, assignee, labels, components, and custom fields. Description accepts plain text and is auto-formatted to ADF: lines ending with ":" become headings, numbered lines create ordered lists, and URLs are linkified. For required custom fields, supply them via customFields (e.g., { "customfield_12345": { id: "..." } }). Returns the created issue with all details.',
2165
+ description: 'Creates a new Jira issue in the specified project. Supports setting issue type, priority, assignee, labels, components, and custom fields. Description format is controlled by the "format" parameter (default: markdown). For required custom fields, supply them via customFields (e.g., { "customfield_12345": { id: "..." } }). Returns the created issue with all details.',
2092
2166
  inputSchema: {
2093
2167
  type: "object",
2094
2168
  properties: {
@@ -2103,7 +2177,7 @@ var createIssueTool = {
2103
2177
  },
2104
2178
  description: {
2105
2179
  anyOf: [{ type: "string" }, { type: "object" }],
2106
- description: "Detailed issue description (optional). Accepts plain text (auto-formatted to ADF) or an ADF document."
2180
+ description: 'Detailed issue description (optional). Format depends on the "format" parameter.'
2107
2181
  },
2108
2182
  issueType: {
2109
2183
  type: "string",
@@ -2138,6 +2212,12 @@ var createIssueTool = {
2138
2212
  type: "boolean",
2139
2213
  description: "If false, returns only the issue key without fetching full details",
2140
2214
  default: true
2215
+ },
2216
+ format: {
2217
+ type: "string",
2218
+ enum: ["markdown", "adf", "plain"],
2219
+ description: 'Description format: "markdown" (converts Markdown to ADF, default), "adf" (use as-is ADF object), "plain" (converts plain text to ADF with basic formatting)',
2220
+ default: "markdown"
2141
2221
  }
2142
2222
  },
2143
2223
  required: ["projectKey", "summary", "issueType"]
@@ -2158,6 +2238,7 @@ async function handleCreateIssue(input) {
2158
2238
  if (validated.labels !== void 0) createParams.labels = validated.labels;
2159
2239
  if (validated.components !== void 0) createParams.components = validated.components;
2160
2240
  if (validated.customFields !== void 0) createParams.customFields = validated.customFields;
2241
+ if (validated.format !== void 0) createParams.format = validated.format;
2161
2242
  let issueOrKey;
2162
2243
  if (validated.returnIssue === false) {
2163
2244
  issueOrKey = await createIssue(createParams, { returnIssue: false });
@@ -2182,7 +2263,7 @@ async function handleCreateIssue(input) {
2182
2263
  var log12 = createLogger("tool:update-issue");
2183
2264
  var updateIssueTool = {
2184
2265
  name: TOOL_NAMES.UPDATE_ISSUE,
2185
- description: 'Updates an existing Jira issue by its key. Supports updating summary, description, priority, assignee, labels, and components. Description accepts plain text and is auto-formatted to ADF: headings (lines ending with ":"), numbered/bullet lists, and links. Only specified fields will be updated.',
2266
+ description: 'Updates an existing Jira issue by its key. Supports updating summary, description, priority, assignee, labels, and components. Description format is controlled by the "format" parameter (default: markdown). Only specified fields will be updated.',
2186
2267
  inputSchema: {
2187
2268
  type: "object",
2188
2269
  properties: {
@@ -2196,7 +2277,7 @@ var updateIssueTool = {
2196
2277
  },
2197
2278
  description: {
2198
2279
  anyOf: [{ type: "string" }, { type: "object" }],
2199
- description: "New issue description (optional). Accepts plain text (auto-formatted to ADF) or an ADF document."
2280
+ description: 'New issue description (optional). Format depends on the "format" parameter.'
2200
2281
  },
2201
2282
  priority: {
2202
2283
  type: "string",
@@ -2220,6 +2301,12 @@ var updateIssueTool = {
2220
2301
  type: "boolean",
2221
2302
  description: "If false, returns a success message without fetching the updated issue",
2222
2303
  default: true
2304
+ },
2305
+ format: {
2306
+ type: "string",
2307
+ enum: ["markdown", "adf", "plain"],
2308
+ description: 'Description format: "markdown" (converts Markdown to ADF, default), "adf" (use as-is ADF object), "plain" (converts plain text to ADF with basic formatting)',
2309
+ default: "markdown"
2223
2310
  }
2224
2311
  },
2225
2312
  required: ["issueKey"]
@@ -2236,6 +2323,7 @@ async function handleUpdateIssue(input) {
2236
2323
  if (validated.assignee !== void 0) updateParams.assignee = validated.assignee;
2237
2324
  if (validated.labels !== void 0) updateParams.labels = validated.labels;
2238
2325
  if (validated.components !== void 0) updateParams.components = validated.components;
2326
+ if (validated.format !== void 0) updateParams.format = validated.format;
2239
2327
  await updateIssue(validated.issueKey, updateParams);
2240
2328
  if (validated.returnIssue === false) {
2241
2329
  log12.info(`Updated issue ${validated.issueKey}`);
@@ -2254,7 +2342,7 @@ async function handleUpdateIssue(input) {
2254
2342
  var log13 = createLogger("tool:add-comment");
2255
2343
  var addCommentTool = {
2256
2344
  name: TOOL_NAMES.ADD_COMMENT,
2257
- description: "Adds a comment to an issue. Supports visibility restrictions for groups or roles. Returns the created comment with author details and timestamp.",
2345
+ description: 'Adds a comment to an issue. Supports visibility restrictions for groups or roles. Comment format is controlled by the "format" parameter (default: markdown). Returns the created comment with author details and timestamp.',
2258
2346
  inputSchema: {
2259
2347
  type: "object",
2260
2348
  properties: {
@@ -2264,7 +2352,7 @@ var addCommentTool = {
2264
2352
  },
2265
2353
  body: {
2266
2354
  type: "string",
2267
- description: "Comment body text",
2355
+ description: 'Comment body text. Format depends on the "format" parameter.',
2268
2356
  minLength: 1
2269
2357
  },
2270
2358
  visibility: {
@@ -2282,6 +2370,12 @@ var addCommentTool = {
2282
2370
  }
2283
2371
  },
2284
2372
  required: ["type", "value"]
2373
+ },
2374
+ format: {
2375
+ type: "string",
2376
+ enum: ["markdown", "adf", "plain"],
2377
+ description: 'Comment format: "markdown" (converts Markdown to ADF, default), "adf" (use as-is ADF object), "plain" (converts plain text to ADF with basic formatting)',
2378
+ default: "markdown"
2285
2379
  }
2286
2380
  },
2287
2381
  required: ["issueKey", "body"]
@@ -2291,7 +2385,12 @@ async function handleAddComment(input) {
2291
2385
  try {
2292
2386
  const validated = validateInput(AddCommentInputSchema, input);
2293
2387
  log13.info(`Adding comment to issue ${validated.issueKey}...`);
2294
- const comment = await addComment(validated.issueKey, validated.body, validated.visibility);
2388
+ const comment = await addComment(
2389
+ validated.issueKey,
2390
+ validated.body,
2391
+ validated.visibility,
2392
+ validated.format
2393
+ );
2295
2394
  log13.info(`Added comment to ${validated.issueKey}`);
2296
2395
  return formatCommentResponse(comment);
2297
2396
  } catch (error) {
@@ -2339,7 +2438,7 @@ async function handleGetProjectInfo(input) {
2339
2438
  var log15 = createLogger("tool:create-subtask");
2340
2439
  var createSubtaskTool = {
2341
2440
  name: TOOL_NAMES.CREATE_SUBTASK,
2342
- description: "Creates a subtask under an existing parent issue. Automatically determines the correct project and subtask issue type. Supports setting priority, assignee, labels, and components.",
2441
+ description: 'Creates a subtask under an existing parent issue. Automatically determines the correct project and subtask issue type. Supports setting priority, assignee, labels, and components. Description format is controlled by the "format" parameter (default: markdown).',
2343
2442
  inputSchema: {
2344
2443
  type: "object",
2345
2444
  properties: {
@@ -2354,7 +2453,7 @@ var createSubtaskTool = {
2354
2453
  },
2355
2454
  description: {
2356
2455
  type: "string",
2357
- description: "Detailed subtask description (optional)"
2456
+ description: 'Detailed subtask description (optional). Format depends on the "format" parameter.'
2358
2457
  },
2359
2458
  priority: {
2360
2459
  type: "string",
@@ -2375,6 +2474,12 @@ var createSubtaskTool = {
2375
2474
  items: { type: "string" },
2376
2475
  description: "Component names (optional)",
2377
2476
  default: []
2477
+ },
2478
+ format: {
2479
+ type: "string",
2480
+ enum: ["markdown", "adf", "plain"],
2481
+ description: 'Description format: "markdown" (converts Markdown to ADF, default), "adf" (use as-is ADF object), "plain" (converts plain text to ADF with basic formatting)',
2482
+ default: "markdown"
2378
2483
  }
2379
2484
  },
2380
2485
  required: ["parentIssueKey", "summary"]
@@ -2392,6 +2497,7 @@ async function handleCreateSubtask(input) {
2392
2497
  if (validated.assignee !== void 0) subtaskParams.assignee = validated.assignee;
2393
2498
  if (validated.labels !== void 0) subtaskParams.labels = validated.labels;
2394
2499
  if (validated.components !== void 0) subtaskParams.components = validated.components;
2500
+ if (validated.format !== void 0) subtaskParams.format = validated.format;
2395
2501
  const subtask = await createSubtask(validated.parentIssueKey, subtaskParams);
2396
2502
  log15.info(`Created subtask ${subtask.key}`);
2397
2503
  return formatIssueResponse(subtask);
@@ -2471,6 +2577,47 @@ async function handleGetCustomFields(input) {
2471
2577
  }
2472
2578
  }
2473
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
+
2474
2621
  // src/index.ts
2475
2622
  if (process.env.NODE_ENV !== "production") {
2476
2623
  try {
@@ -2479,7 +2626,7 @@ if (process.env.NODE_ENV !== "production") {
2479
2626
  } catch {
2480
2627
  }
2481
2628
  }
2482
- var log18 = createLogger("server");
2629
+ var log19 = createLogger("server");
2483
2630
  var toolHandlers = /* @__PURE__ */ new Map([
2484
2631
  [TOOL_NAMES.GET_VISIBLE_PROJECTS, handleGetVisibleProjects],
2485
2632
  [TOOL_NAMES.GET_ISSUE, handleGetIssue],
@@ -2495,7 +2642,8 @@ var toolHandlers = /* @__PURE__ */ new Map([
2495
2642
  [TOOL_NAMES.GET_PROJECT_INFO, handleGetProjectInfo],
2496
2643
  [TOOL_NAMES.CREATE_SUBTASK, handleCreateSubtask],
2497
2644
  [TOOL_NAMES.GET_CREATE_META, handleGetCreateMeta],
2498
- [TOOL_NAMES.GET_CUSTOM_FIELDS, handleGetCustomFields]
2645
+ [TOOL_NAMES.GET_CUSTOM_FIELDS, handleGetCustomFields],
2646
+ [TOOL_NAMES.CREATE_ISSUE_LINK, handleCreateIssueLink]
2499
2647
  ]);
2500
2648
  var allTools = [
2501
2649
  getVisibleProjectsTool,
@@ -2512,7 +2660,8 @@ var allTools = [
2512
2660
  getProjectInfoTool,
2513
2661
  createSubtaskTool,
2514
2662
  getCreateMetaTool,
2515
- getCustomFieldsTool
2663
+ getCustomFieldsTool,
2664
+ createIssueLinkTool
2516
2665
  ];
2517
2666
  async function main() {
2518
2667
  const isDryRun = (process.env.DRY_RUN || "").toLowerCase() === "1" || (process.env.DRY_RUN || "").toLowerCase() === "true";
@@ -2583,7 +2732,7 @@ async function main() {
2583
2732
  console.error("\u{1F680} Jira MCP server running on stdio");
2584
2733
  }
2585
2734
  main().catch((error) => {
2586
- log18.error("Fatal error:", error);
2735
+ log19.error("Fatal error:", error);
2587
2736
  process.exit(1);
2588
2737
  });
2589
2738
  //# sourceMappingURL=index.js.map