mcp-jira-stdio 1.8.0 → 1.9.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
@@ -441,7 +441,7 @@ var require_package2 = __commonJS({
441
441
  "package.json"(exports$1, module) {
442
442
  module.exports = {
443
443
  name: "mcp-jira-stdio",
444
- version: "1.8.0",
444
+ version: "1.9.0",
445
445
  description: "Model Context Protocol (MCP) server for Jira API integration",
446
446
  author: "graslt",
447
447
  license: "MIT",
@@ -497,7 +497,7 @@ var require_package2 = __commonJS({
497
497
  zod: "^3.22.4"
498
498
  },
499
499
  devDependencies: {
500
- "@types/node": "^22.5.0",
500
+ "@types/node": "^25.3.3",
501
501
  "@typescript-eslint/eslint-plugin": "^8.0.0",
502
502
  "@typescript-eslint/parser": "^8.0.0",
503
503
  "@vitest/coverage-v8": "^4.0.10",
@@ -577,7 +577,9 @@ var TOOL_NAMES = {
577
577
  GET_COMMENTS: "jira_get_comments",
578
578
  ADD_ATTACHMENT: "jira_add_attachment",
579
579
  GET_ATTACHMENTS: "jira_get_attachments",
580
- DELETE_ATTACHMENT: "jira_delete_attachment"
580
+ DELETE_ATTACHMENT: "jira_delete_attachment",
581
+ GET_TRANSITIONS: "jira_get_transitions",
582
+ TRANSITION_ISSUE: "jira_transition_issue"
581
583
  };
582
584
 
583
585
  // src/utils/logger.ts
@@ -883,6 +885,9 @@ var UpdateIssueInputSchema = z.object({
883
885
  assignee: z.string().optional().describe("New assignee account ID"),
884
886
  labels: z.array(z.string()).optional().describe("New labels (replaces existing)"),
885
887
  components: z.array(z.string()).optional().describe("New components (replaces existing)"),
888
+ parent: z.string().optional().describe(
889
+ "New parent issue key (e.g., PROJECT-100). Set to empty string to remove the parent."
890
+ ),
886
891
  returnIssue: z.boolean().optional().describe("When false, skip fetching full issue after update"),
887
892
  format: z.enum(["markdown", "adf", "plain"]).optional().default("markdown").describe(
888
893
  'Description format: "markdown" (converts Markdown to ADF), "adf" (use as-is ADF object), "plain" (converts plain text to ADF with basic formatting). Default: "markdown"'
@@ -985,6 +990,27 @@ var GetAttachmentsInputSchema = z.object({
985
990
  var DeleteAttachmentInputSchema = z.object({
986
991
  attachmentId: z.string().min(1).describe("ID of the attachment to delete")
987
992
  });
993
+ var GetTransitionsInputSchema = z.object({
994
+ issueKey: z.string().describe("Issue key to get available transitions for (e.g., PROJECT-123)").refine((v) => isValidIssueKey(v), "Invalid issue key format")
995
+ });
996
+ var TransitionIssueInputSchema = z.object({
997
+ issueKey: z.string().describe("Issue key to transition (e.g., PROJECT-123)").refine((v) => isValidIssueKey(v), "Invalid issue key format"),
998
+ transitionId: z.string().optional().describe(
999
+ "ID of the transition to perform. Use jira_get_transitions to find available transition IDs."
1000
+ ),
1001
+ transitionName: z.string().optional().describe(
1002
+ 'Name of the transition to perform (e.g., "In Progress", "Done"). Case-insensitive. Alternative to transitionId.'
1003
+ ),
1004
+ comment: z.string().optional().describe("Optional comment to add when transitioning the issue"),
1005
+ resolution: z.string().optional().describe(
1006
+ 'Resolution name when transitioning to a resolved/done status (e.g., "Done", "Fixed")'
1007
+ ),
1008
+ format: z.enum(["markdown", "adf", "plain"]).optional().default("markdown").describe(
1009
+ 'Comment format: "markdown" (converts Markdown to ADF), "adf" (use as-is ADF object), "plain" (converts plain text to ADF with basic formatting). Default: "markdown"'
1010
+ )
1011
+ }).refine((data) => data.transitionId || data.transitionName, {
1012
+ message: "Either transitionId or transitionName must be provided"
1013
+ });
988
1014
  function ensureAdfDescription(desc, format = "markdown") {
989
1015
  if (!desc) return desc;
990
1016
  if (typeof desc === "object") return desc;
@@ -1237,6 +1263,9 @@ async function updateIssue(issueKey, updates) {
1237
1263
  if (updates.components !== void 0) {
1238
1264
  fields.components = updates.components.map((name) => ({ name }));
1239
1265
  }
1266
+ if (updates.parent !== void 0) {
1267
+ fields.parent = updates.parent ? { key: updates.parent } : null;
1268
+ }
1240
1269
  const config = {
1241
1270
  method: "PUT",
1242
1271
  url: `/issue/${issueKey}`,
@@ -1521,6 +1550,42 @@ async function deleteAttachment(attachmentId) {
1521
1550
  };
1522
1551
  await makeJiraRequest(config);
1523
1552
  }
1553
+ async function getTransitions(issueKey) {
1554
+ const config = {
1555
+ method: "GET",
1556
+ url: `/issue/${issueKey}/transitions`
1557
+ };
1558
+ const response = await makeJiraRequest(config);
1559
+ return response.transitions;
1560
+ }
1561
+ async function transitionIssue(issueKey, transitionId, options = {}) {
1562
+ const data = {
1563
+ transition: { id: transitionId }
1564
+ };
1565
+ if (options.resolution) {
1566
+ data.fields = {
1567
+ resolution: { name: options.resolution }
1568
+ };
1569
+ }
1570
+ if (options.comment) {
1571
+ const adfBody = ensureAdfDescription(options.comment, options.format || "markdown");
1572
+ data.update = {
1573
+ comment: [
1574
+ {
1575
+ add: {
1576
+ body: adfBody
1577
+ }
1578
+ }
1579
+ ]
1580
+ };
1581
+ }
1582
+ const config = {
1583
+ method: "POST",
1584
+ url: `/issue/${issueKey}/transitions`,
1585
+ data
1586
+ };
1587
+ await makeJiraRequest(config);
1588
+ }
1524
1589
 
1525
1590
  // src/utils/redaction.ts
1526
1591
  function isRedactionEnabled() {
@@ -1608,6 +1673,8 @@ function formatIssueResponse(issue) {
1608
1673
  }
1609
1674
  const createdText = fields?.created ? new Date(fields.created).toISOString() : "N/A";
1610
1675
  const updatedText = fields?.updated ? new Date(fields.updated).toISOString() : "N/A";
1676
+ const parentText = fields?.parent ? `
1677
+ **Parent:** ${fields.parent.key}${fields.parent.fields?.summary ? ` - ${fields.parent.fields.summary}` : ""}${fields.parent.fields?.status ? ` (${fields.parent.fields.status.name})` : ""}` : "";
1611
1678
  return {
1612
1679
  content: [
1613
1680
  {
@@ -1621,7 +1688,7 @@ function formatIssueResponse(issue) {
1621
1688
  **Project:** ${fields?.project?.name || "Unknown"} (${fields?.project?.key || "N/A"})
1622
1689
  **Issue Type:** ${fields?.issuetype?.name || "Unknown"}
1623
1690
  **Labels:** ${labelsText}
1624
- **Components:** ${componentsText}
1691
+ **Components:** ${componentsText}${parentText}
1625
1692
  **Created:** ${createdText}
1626
1693
  **Updated:** ${updatedText}
1627
1694
 
@@ -2062,6 +2129,34 @@ ${attachmentsList}
2062
2129
  ]
2063
2130
  };
2064
2131
  }
2132
+ function formatTransitionsResponse(transitions, issueKey) {
2133
+ if (transitions.length === 0) {
2134
+ return {
2135
+ content: [
2136
+ {
2137
+ type: "text",
2138
+ text: `No transitions available for issue ${issueKey}. The issue may be in a final state or you may lack permission.`
2139
+ }
2140
+ ]
2141
+ };
2142
+ }
2143
+ const transitionsList = transitions.map(
2144
+ (t) => `\u2022 **${t.name}** (ID: ${t.id})
2145
+ Target status: ${t.to.name} (Category: ${t.to.statusCategory?.name || "Unknown"})`
2146
+ ).join("\n\n");
2147
+ return {
2148
+ content: [
2149
+ {
2150
+ type: "text",
2151
+ text: `**Available transitions for ${issueKey}** (${transitions.length}):
2152
+
2153
+ ${transitionsList}
2154
+
2155
+ \u{1F4A1} **Tip:** Use \`jira_transition_issue\` with the transition ID or name to move this issue.`
2156
+ }
2157
+ ]
2158
+ };
2159
+ }
2065
2160
 
2066
2161
  // src/utils/error-handler.ts
2067
2162
  var log2 = createLogger("error-handler");
@@ -2612,6 +2707,10 @@ var updateIssueTool = {
2612
2707
  items: { type: "string" },
2613
2708
  description: "New components array (replaces existing components) - optional"
2614
2709
  },
2710
+ parent: {
2711
+ type: "string",
2712
+ description: "New parent issue key (e.g., PROJECT-100). Set to empty string to remove the parent."
2713
+ },
2615
2714
  returnIssue: {
2616
2715
  type: "boolean",
2617
2716
  description: "If false, returns a success message without fetching the updated issue",
@@ -2638,6 +2737,7 @@ async function handleUpdateIssue(input) {
2638
2737
  if (validated.assignee !== void 0) updateParams.assignee = validated.assignee;
2639
2738
  if (validated.labels !== void 0) updateParams.labels = validated.labels;
2640
2739
  if (validated.components !== void 0) updateParams.components = validated.components;
2740
+ if (validated.parent !== void 0) updateParams.parent = validated.parent;
2641
2741
  if (validated.format !== void 0) updateParams.format = validated.format;
2642
2742
  await updateIssue(validated.issueKey, updateParams);
2643
2743
  if (validated.returnIssue === false) {
@@ -3130,6 +3230,115 @@ async function handleDeleteAttachment(input) {
3130
3230
  }
3131
3231
  }
3132
3232
 
3233
+ // src/tools/get-transitions.ts
3234
+ var log23 = createLogger("tool:get-transitions");
3235
+ var getTransitionsTool = {
3236
+ name: TOOL_NAMES.GET_TRANSITIONS,
3237
+ description: "Retrieves all available workflow transitions for a Jira issue. Use this to discover which status changes are possible for an issue before calling jira_transition_issue.",
3238
+ inputSchema: {
3239
+ type: "object",
3240
+ properties: {
3241
+ issueKey: {
3242
+ type: "string",
3243
+ description: "Issue key to get available transitions for (e.g., PROJECT-123)"
3244
+ }
3245
+ },
3246
+ required: ["issueKey"]
3247
+ }
3248
+ };
3249
+ async function handleGetTransitions(input) {
3250
+ try {
3251
+ const validated = validateInput(GetTransitionsInputSchema, input);
3252
+ log23.info(`Getting transitions for issue ${validated.issueKey}...`);
3253
+ const transitions = await getTransitions(validated.issueKey);
3254
+ log23.info(`Found ${transitions.length} transition(s) for ${validated.issueKey}`);
3255
+ return formatTransitionsResponse(transitions, validated.issueKey);
3256
+ } catch (error) {
3257
+ log23.error("Error in handleGetTransitions:", error);
3258
+ return handleError(error);
3259
+ }
3260
+ }
3261
+
3262
+ // src/tools/transition-issue.ts
3263
+ var log24 = createLogger("tool:transition-issue");
3264
+ var transitionIssueTool = {
3265
+ name: TOOL_NAMES.TRANSITION_ISSUE,
3266
+ description: 'Transitions a Jira issue to a new workflow status (e.g., "To Do" -> "In Progress" -> "Done"). Use jira_get_transitions first to discover available transitions. Supports adding a comment and setting resolution during the transition.',
3267
+ inputSchema: {
3268
+ type: "object",
3269
+ properties: {
3270
+ issueKey: {
3271
+ type: "string",
3272
+ description: "Issue key to transition (e.g., PROJECT-123)"
3273
+ },
3274
+ transitionId: {
3275
+ type: "string",
3276
+ description: "ID of the transition to perform. Use jira_get_transitions to find available transition IDs."
3277
+ },
3278
+ transitionName: {
3279
+ type: "string",
3280
+ description: 'Name of the transition to perform (e.g., "In Progress", "Done"). Case-insensitive. Alternative to transitionId.'
3281
+ },
3282
+ comment: {
3283
+ type: "string",
3284
+ description: "Optional comment to add when transitioning the issue"
3285
+ },
3286
+ resolution: {
3287
+ type: "string",
3288
+ description: 'Resolution name when transitioning to a resolved/done status (e.g., "Done", "Fixed")'
3289
+ },
3290
+ format: {
3291
+ type: "string",
3292
+ enum: ["markdown", "adf", "plain"],
3293
+ description: 'Comment format: "markdown" (converts Markdown to ADF), "adf" (use as-is ADF object), "plain" (converts plain text to ADF with basic formatting). Default: "markdown"',
3294
+ default: "markdown"
3295
+ }
3296
+ },
3297
+ required: ["issueKey"]
3298
+ }
3299
+ };
3300
+ async function handleTransitionIssue(input) {
3301
+ try {
3302
+ const validated = validateInput(TransitionIssueInputSchema, input);
3303
+ let transitionId = validated.transitionId;
3304
+ if (!transitionId && validated.transitionName) {
3305
+ log24.info(
3306
+ `Looking up transition "${validated.transitionName}" for issue ${validated.issueKey}...`
3307
+ );
3308
+ const transitions = await getTransitions(validated.issueKey);
3309
+ const match = transitions.find(
3310
+ (t) => t.name.toLowerCase() === validated.transitionName.toLowerCase()
3311
+ );
3312
+ if (!match) {
3313
+ const available = transitions.map((t) => `"${t.name}" (ID: ${t.id})`).join(", ");
3314
+ return {
3315
+ content: [
3316
+ {
3317
+ type: "text",
3318
+ text: `Transition "${validated.transitionName}" not found for issue ${validated.issueKey}.
3319
+
3320
+ Available transitions: ${available || "none"}`
3321
+ }
3322
+ ]
3323
+ };
3324
+ }
3325
+ transitionId = match.id;
3326
+ }
3327
+ log24.info(`Transitioning issue ${validated.issueKey} with transition ${transitionId}...`);
3328
+ const transitionOptions = {};
3329
+ if (validated.comment) transitionOptions.comment = validated.comment;
3330
+ if (validated.resolution) transitionOptions.resolution = validated.resolution;
3331
+ if (validated.format) transitionOptions.format = validated.format;
3332
+ await transitionIssue(validated.issueKey, transitionId, transitionOptions);
3333
+ const updatedIssue = await getIssue(validated.issueKey);
3334
+ log24.info(`Transitioned issue ${validated.issueKey} to ${updatedIssue.fields.status.name}`);
3335
+ return formatIssueResponse(updatedIssue);
3336
+ } catch (error) {
3337
+ log24.error("Error in handleTransitionIssue:", error);
3338
+ return handleError(error);
3339
+ }
3340
+ }
3341
+
3133
3342
  // src/index.ts
3134
3343
  if (process.env.NODE_ENV !== "production") {
3135
3344
  try {
@@ -3138,7 +3347,7 @@ if (process.env.NODE_ENV !== "production") {
3138
3347
  } catch {
3139
3348
  }
3140
3349
  }
3141
- var log23 = createLogger("server");
3350
+ var log25 = createLogger("server");
3142
3351
  var toolHandlers = /* @__PURE__ */ new Map([
3143
3352
  [TOOL_NAMES.GET_VISIBLE_PROJECTS, handleGetVisibleProjects],
3144
3353
  [TOOL_NAMES.GET_ISSUE, handleGetIssue],
@@ -3159,7 +3368,9 @@ var toolHandlers = /* @__PURE__ */ new Map([
3159
3368
  [TOOL_NAMES.GET_COMMENTS, handleGetComments],
3160
3369
  [TOOL_NAMES.ADD_ATTACHMENT, handleAddAttachment],
3161
3370
  [TOOL_NAMES.GET_ATTACHMENTS, handleGetAttachments],
3162
- [TOOL_NAMES.DELETE_ATTACHMENT, handleDeleteAttachment]
3371
+ [TOOL_NAMES.DELETE_ATTACHMENT, handleDeleteAttachment],
3372
+ [TOOL_NAMES.GET_TRANSITIONS, handleGetTransitions],
3373
+ [TOOL_NAMES.TRANSITION_ISSUE, handleTransitionIssue]
3163
3374
  ]);
3164
3375
  var allTools = [
3165
3376
  getVisibleProjectsTool,
@@ -3181,7 +3392,9 @@ var allTools = [
3181
3392
  getCommentsTool,
3182
3393
  addAttachmentTool,
3183
3394
  getAttachmentsTool,
3184
- deleteAttachmentTool
3395
+ deleteAttachmentTool,
3396
+ getTransitionsTool,
3397
+ transitionIssueTool
3185
3398
  ];
3186
3399
  async function main() {
3187
3400
  const isDryRun = (process.env.DRY_RUN || "").toLowerCase() === "1" || (process.env.DRY_RUN || "").toLowerCase() === "true";
@@ -3252,7 +3465,7 @@ async function main() {
3252
3465
  console.error("\u{1F680} Jira MCP server running on stdio");
3253
3466
  }
3254
3467
  main().catch((error) => {
3255
- log23.error("Fatal error:", error);
3468
+ log25.error("Fatal error:", error);
3256
3469
  process.exit(1);
3257
3470
  });
3258
3471
  //# sourceMappingURL=index.js.map