mcp-jira-stdio 1.4.0 → 1.5.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
@@ -439,7 +439,7 @@ var require_package2 = __commonJS({
439
439
  "package.json"(exports, module) {
440
440
  module.exports = {
441
441
  name: "mcp-jira-stdio",
442
- version: "1.4.0",
442
+ version: "1.5.1",
443
443
  description: "Model Context Protocol (MCP) server for Jira API integration",
444
444
  author: "graslt",
445
445
  license: "MIT",
@@ -555,7 +555,9 @@ var TOOL_NAMES = {
555
555
  UPDATE_ISSUE: "jira_update_issue",
556
556
  ADD_COMMENT: "jira_add_comment",
557
557
  GET_PROJECT_INFO: "jira_get_project_info",
558
- CREATE_SUBTASK: "jira_create_subtask"
558
+ CREATE_SUBTASK: "jira_create_subtask",
559
+ GET_CREATE_META: "jira_get_create_meta",
560
+ GET_CUSTOM_FIELDS: "jira_get_custom_fields"
559
561
  };
560
562
 
561
563
  // src/utils/logger.ts
@@ -813,6 +815,13 @@ var CreateSubtaskInputSchema = z.object({
813
815
  labels: z.array(z.string()).optional().describe("Subtask labels"),
814
816
  components: z.array(z.string()).optional().describe("Component names")
815
817
  });
818
+ var GetCreateMetaInputSchema = z.object({
819
+ projectKey: z.string().describe("Project key to get create metadata for").refine((v) => isValidProjectKey(v), "Invalid project key format"),
820
+ issueTypeName: z.string().optional().describe("Specific issue type name to get metadata for (optional)")
821
+ });
822
+ var GetCustomFieldsInputSchema = z.object({
823
+ projectKey: z.string().optional().describe("Project key to filter custom fields (optional)").refine((v) => v ? isValidProjectKey(v) : true, "Invalid project key format")
824
+ });
816
825
 
817
826
  // src/utils/api-helpers.ts
818
827
  function ensureAdfDescription(desc) {
@@ -1125,7 +1134,10 @@ async function getStatuses(options = {}) {
1125
1134
  return await makeJiraRequest(config);
1126
1135
  }
1127
1136
  async function addComment(issueKey, body, visibility) {
1128
- const data = { body };
1137
+ const adfBody = ensureAdfDescription(body);
1138
+ const data = {
1139
+ body: adfBody
1140
+ };
1129
1141
  if (visibility) {
1130
1142
  data.visibility = visibility;
1131
1143
  }
@@ -1185,6 +1197,31 @@ async function createSubtask(parentIssueKey, subtaskData) {
1185
1197
  const response = await makeJiraRequest(config);
1186
1198
  return await getIssue(response.key);
1187
1199
  }
1200
+ async function getCreateMeta(options = {}) {
1201
+ const params = {};
1202
+ if (options.projectKeys && options.projectKeys.length > 0) {
1203
+ params.projectKeys = options.projectKeys.join(",");
1204
+ }
1205
+ if (options.issueTypeNames && options.issueTypeNames.length > 0) {
1206
+ params.issuetypeNames = options.issueTypeNames.join(",");
1207
+ }
1208
+ if (options.expand) {
1209
+ params.expand = options.expand;
1210
+ }
1211
+ const config = {
1212
+ method: "GET",
1213
+ url: "/issue/createmeta",
1214
+ params
1215
+ };
1216
+ return await makeJiraRequest(config);
1217
+ }
1218
+ async function getFields() {
1219
+ const config = {
1220
+ method: "GET",
1221
+ url: "/field"
1222
+ };
1223
+ return await makeJiraRequest(config);
1224
+ }
1188
1225
 
1189
1226
  // src/utils/redaction.ts
1190
1227
  function isRedactionEnabled() {
@@ -1307,9 +1344,12 @@ function formatSearchResultsResponse(result) {
1307
1344
  };
1308
1345
  }
1309
1346
  const issuesList = result.issues.map((issue) => {
1310
- const assignee = issue.fields.assignee?.displayName || "Unassigned";
1311
- return `\u2022 **${issue.key}** - ${issue.fields.summary}
1312
- Status: ${issue.fields.status.name} | Assignee: ${assignee} | Priority: ${issue.fields.priority?.name || "None"}`;
1347
+ const assignee = issue.fields?.assignee?.displayName || "Unassigned";
1348
+ const summary = issue.fields?.summary || "No summary";
1349
+ const status = issue.fields?.status?.name || "Unknown";
1350
+ const priority = issue.fields?.priority?.name || "None";
1351
+ return `\u2022 **${issue.key}** - ${summary}
1352
+ Status: ${status} | Assignee: ${assignee} | Priority: ${priority}`;
1313
1353
  }).join("\n\n");
1314
1354
  const rangeStart = result.startAt + 1;
1315
1355
  const rangeEnd = Math.min(result.startAt + result.maxResults, result.total);
@@ -1339,23 +1379,34 @@ ${issuesList}${paginationInfo}`
1339
1379
  }
1340
1380
  function formatErrorResponse(error) {
1341
1381
  let errorMessage = "An unexpected error occurred.";
1382
+ let hasCustomFieldError = false;
1342
1383
  if (typeof error === "string") {
1343
1384
  errorMessage = error;
1385
+ hasCustomFieldError = error.includes("customfield_");
1344
1386
  } else if (error instanceof Error) {
1345
1387
  errorMessage = error.message;
1388
+ hasCustomFieldError = error.message.includes("customfield_");
1346
1389
  } else if (error?.response?.data) {
1347
1390
  const jiraError = error.response.data;
1348
1391
  if (jiraError.errorMessages && jiraError.errorMessages.length > 0) {
1349
1392
  errorMessage = jiraError.errorMessages.join(", ");
1350
1393
  } else if (jiraError.errors) {
1351
- errorMessage = Object.entries(jiraError.errors).map(([field, message]) => `${field}: ${message}`).join(", ");
1394
+ const errors = Object.entries(jiraError.errors);
1395
+ errorMessage = errors.map(([field, message]) => `${field}: ${message}`).join(", ");
1396
+ hasCustomFieldError = errors.some(([field]) => field.startsWith("customfield_"));
1352
1397
  }
1353
1398
  }
1399
+ let hint = "";
1400
+ if (hasCustomFieldError) {
1401
+ hint = `
1402
+
1403
+ \u{1F4A1} **Hint:** This error involves custom fields. Use 'jira_get_create_meta' tool to discover required fields and their allowed values before creating an issue.`;
1404
+ }
1354
1405
  return {
1355
1406
  content: [
1356
1407
  {
1357
1408
  type: "text",
1358
- text: `\u274C Error: ${errorMessage}`
1409
+ text: `\u274C Error: ${errorMessage}${hint}`
1359
1410
  }
1360
1411
  ]
1361
1412
  };
@@ -1522,6 +1573,96 @@ Last Updated: ${new Date(project.insight.lastIssueUpdateTime).toISOString()}` :
1522
1573
  ]
1523
1574
  };
1524
1575
  }
1576
+ function formatCreateMetaResponse(meta) {
1577
+ if (!meta.projects || meta.projects.length === 0) {
1578
+ return {
1579
+ content: [
1580
+ {
1581
+ type: "text",
1582
+ text: "No create metadata found for the specified project."
1583
+ }
1584
+ ]
1585
+ };
1586
+ }
1587
+ const project = meta.projects[0];
1588
+ if (!project) {
1589
+ return {
1590
+ content: [
1591
+ {
1592
+ type: "text",
1593
+ text: "No create metadata found for the specified project."
1594
+ }
1595
+ ]
1596
+ };
1597
+ }
1598
+ const issueTypesList = project.issuetypes.map((issueType) => {
1599
+ const requiredFields = [];
1600
+ const optionalFields = [];
1601
+ Object.entries(issueType.fields).forEach(([fieldKey, field]) => {
1602
+ const allowedValuesText = field.allowedValues ? `
1603
+ Allowed values: ${field.allowedValues.map((v) => v.name || v.value || v.id).join(", ")}` : "";
1604
+ const defaultValueText = field.hasDefaultValue ? `
1605
+ Default: ${JSON.stringify(field.defaultValue)}` : "";
1606
+ const fieldInfo = ` \u2022 **${field.name}** (${fieldKey})
1607
+ Type: ${field.schema.type}${allowedValuesText}${defaultValueText}`;
1608
+ if (field.required) {
1609
+ requiredFields.push(fieldInfo);
1610
+ } else {
1611
+ optionalFields.push(fieldInfo);
1612
+ }
1613
+ });
1614
+ const requiredSection = requiredFields.length > 0 ? `**Required Fields:**
1615
+ ${requiredFields.join("\n\n")}` : "No required fields";
1616
+ const optionalSection = optionalFields.length > 0 ? `
1617
+
1618
+ **Optional Fields:**
1619
+ ${optionalFields.join("\n\n")}` : "";
1620
+ return `### ${issueType.name} (${issueType.subtask ? "Subtask" : "Issue"})
1621
+ ${issueType.description}
1622
+
1623
+ ${requiredSection}${optionalSection}`;
1624
+ }).join("\n\n---\n\n");
1625
+ return {
1626
+ content: [
1627
+ {
1628
+ type: "text",
1629
+ text: `**Create Metadata for Project: ${project.name} (${project.key})**
1630
+
1631
+ ${issueTypesList}`
1632
+ }
1633
+ ]
1634
+ };
1635
+ }
1636
+ function formatCustomFieldsResponse(fields, projectKey) {
1637
+ const customFields = fields.filter((field) => field.custom);
1638
+ if (customFields.length === 0) {
1639
+ return {
1640
+ content: [
1641
+ {
1642
+ type: "text",
1643
+ text: projectKey ? `No custom fields found for project ${projectKey}.` : "No custom fields found."
1644
+ }
1645
+ ]
1646
+ };
1647
+ }
1648
+ const fieldsList = customFields.map((field) => {
1649
+ const schemaInfo = field.schema.custom ? `Custom Type: ${field.schema.custom}` : `Type: ${field.schema.type}`;
1650
+ return `\u2022 **${field.name}** (${field.key})
1651
+ ${schemaInfo}
1652
+ ID: ${field.id}`;
1653
+ }).join("\n\n");
1654
+ const header = projectKey ? `Found ${customFields.length} custom field(s) for project ${projectKey}:` : `Found ${customFields.length} custom field(s):`;
1655
+ return {
1656
+ content: [
1657
+ {
1658
+ type: "text",
1659
+ text: `${header}
1660
+
1661
+ ${fieldsList}`
1662
+ }
1663
+ ]
1664
+ };
1665
+ }
1525
1666
 
1526
1667
  // src/utils/error-handler.ts
1527
1668
  var log2 = createLogger("error-handler");
@@ -2248,6 +2389,76 @@ async function handleCreateSubtask(input) {
2248
2389
  }
2249
2390
  }
2250
2391
 
2392
+ // src/tools/get-create-meta.ts
2393
+ var log16 = createLogger("tool:get-create-meta");
2394
+ var getCreateMetaTool = {
2395
+ name: TOOL_NAMES.GET_CREATE_META,
2396
+ description: "Retrieves create metadata for a project, showing all available fields (including custom fields) for creating issues. Shows required vs optional fields, field types, and allowed values. Use this before creating issues to discover what fields are needed.",
2397
+ inputSchema: {
2398
+ type: "object",
2399
+ properties: {
2400
+ projectKey: {
2401
+ type: "string",
2402
+ description: "Project key to get create metadata for (e.g., PROJECT)"
2403
+ },
2404
+ issueTypeName: {
2405
+ type: "string",
2406
+ description: "Specific issue type name to get metadata for (optional)"
2407
+ }
2408
+ },
2409
+ required: ["projectKey"]
2410
+ }
2411
+ };
2412
+ async function handleGetCreateMeta(input) {
2413
+ try {
2414
+ const validated = validateInput(GetCreateMetaInputSchema, input);
2415
+ log16.info(`Getting create metadata for project ${validated.projectKey}...`);
2416
+ const options = {
2417
+ projectKeys: [validated.projectKey],
2418
+ expand: "projects.issuetypes.fields"
2419
+ };
2420
+ if (validated.issueTypeName) {
2421
+ options.issueTypeNames = [validated.issueTypeName];
2422
+ }
2423
+ const meta = await getCreateMeta(options);
2424
+ log16.info(`Retrieved create metadata for ${validated.projectKey}`);
2425
+ return formatCreateMetaResponse(meta);
2426
+ } catch (error) {
2427
+ log16.error("Error in handleGetCreateMeta:", error);
2428
+ return handleError(error);
2429
+ }
2430
+ }
2431
+
2432
+ // src/tools/get-custom-fields.ts
2433
+ var log17 = createLogger("tool:get-custom-fields");
2434
+ var getCustomFieldsTool = {
2435
+ name: TOOL_NAMES.GET_CUSTOM_FIELDS,
2436
+ description: "Retrieves all custom fields available in Jira. Shows custom field names, IDs (e.g., customfield_10071), and types. Useful for discovering what custom fields exist and their identifiers.",
2437
+ inputSchema: {
2438
+ type: "object",
2439
+ properties: {
2440
+ projectKey: {
2441
+ type: "string",
2442
+ description: "Project key to filter custom fields (optional)"
2443
+ }
2444
+ },
2445
+ required: []
2446
+ }
2447
+ };
2448
+ async function handleGetCustomFields(input) {
2449
+ try {
2450
+ const validated = validateInput(GetCustomFieldsInputSchema, input);
2451
+ const projectMsg = validated.projectKey ? ` for project ${validated.projectKey}` : "";
2452
+ log17.info(`Getting custom fields${projectMsg}...`);
2453
+ const fields = await getFields();
2454
+ log17.info(`Retrieved ${fields.filter((f) => f.custom).length} custom fields`);
2455
+ return formatCustomFieldsResponse(fields, validated.projectKey);
2456
+ } catch (error) {
2457
+ log17.error("Error in handleGetCustomFields:", error);
2458
+ return handleError(error);
2459
+ }
2460
+ }
2461
+
2251
2462
  // src/index.ts
2252
2463
  if (process.env.NODE_ENV !== "production") {
2253
2464
  try {
@@ -2256,7 +2467,7 @@ if (process.env.NODE_ENV !== "production") {
2256
2467
  } catch {
2257
2468
  }
2258
2469
  }
2259
- var log16 = createLogger("server");
2470
+ var log18 = createLogger("server");
2260
2471
  var toolHandlers = /* @__PURE__ */ new Map([
2261
2472
  [TOOL_NAMES.GET_VISIBLE_PROJECTS, handleGetVisibleProjects],
2262
2473
  [TOOL_NAMES.GET_ISSUE, handleGetIssue],
@@ -2270,7 +2481,9 @@ var toolHandlers = /* @__PURE__ */ new Map([
2270
2481
  [TOOL_NAMES.UPDATE_ISSUE, handleUpdateIssue],
2271
2482
  [TOOL_NAMES.ADD_COMMENT, handleAddComment],
2272
2483
  [TOOL_NAMES.GET_PROJECT_INFO, handleGetProjectInfo],
2273
- [TOOL_NAMES.CREATE_SUBTASK, handleCreateSubtask]
2484
+ [TOOL_NAMES.CREATE_SUBTASK, handleCreateSubtask],
2485
+ [TOOL_NAMES.GET_CREATE_META, handleGetCreateMeta],
2486
+ [TOOL_NAMES.GET_CUSTOM_FIELDS, handleGetCustomFields]
2274
2487
  ]);
2275
2488
  var allTools = [
2276
2489
  getVisibleProjectsTool,
@@ -2285,7 +2498,9 @@ var allTools = [
2285
2498
  updateIssueTool,
2286
2499
  addCommentTool,
2287
2500
  getProjectInfoTool,
2288
- createSubtaskTool
2501
+ createSubtaskTool,
2502
+ getCreateMetaTool,
2503
+ getCustomFieldsTool
2289
2504
  ];
2290
2505
  async function main() {
2291
2506
  const isDryRun = (process.env.DRY_RUN || "").toLowerCase() === "1" || (process.env.DRY_RUN || "").toLowerCase() === "true";
@@ -2356,7 +2571,7 @@ async function main() {
2356
2571
  console.error("\u{1F680} Jira MCP server running on stdio");
2357
2572
  }
2358
2573
  main().catch((error) => {
2359
- log16.error("Fatal error:", error);
2574
+ log18.error("Fatal error:", error);
2360
2575
  process.exit(1);
2361
2576
  });
2362
2577
  //# sourceMappingURL=index.js.map