@rui.branco/jira-mcp 1.6.9 → 1.6.11

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.
Files changed (2) hide show
  1. package/index.js +66 -34
  2. package/package.json +1 -1
package/index.js CHANGED
@@ -1256,6 +1256,10 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
1256
1256
  description:
1257
1257
  "Fetch linked Figma designs and export images (default: true)",
1258
1258
  },
1259
+ instance: {
1260
+ type: "string",
1261
+ description: "Instance name override. Auto-detected from issue key prefix if omitted.",
1262
+ },
1259
1263
  },
1260
1264
  required: ["issueKey"],
1261
1265
  },
@@ -1298,6 +1302,10 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
1298
1302
  description: "The Jira issue key (e.g., MODS-123)",
1299
1303
  },
1300
1304
  comment: { type: "string", description: "The comment text to add" },
1305
+ instance: {
1306
+ type: "string",
1307
+ description: "Instance name override. Auto-detected from issue key prefix if omitted.",
1308
+ },
1301
1309
  },
1302
1310
  required: ["issueKey", "comment"],
1303
1311
  },
@@ -1319,6 +1327,10 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
1319
1327
  "The ID of the comment to reply to. Use jira_get_ticket to see comments and their IDs.",
1320
1328
  },
1321
1329
  reply: { type: "string", description: "The reply text" },
1330
+ instance: {
1331
+ type: "string",
1332
+ description: "Instance name override. Auto-detected from issue key prefix if omitted.",
1333
+ },
1322
1334
  },
1323
1335
  required: ["issueKey", "commentId", "reply"],
1324
1336
  },
@@ -1340,6 +1352,10 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
1340
1352
  "The ID of the comment to edit. Use jira_get_ticket to see comments and their IDs.",
1341
1353
  },
1342
1354
  comment: { type: "string", description: "The new comment text" },
1355
+ instance: {
1356
+ type: "string",
1357
+ description: "Instance name override. Auto-detected from issue key prefix if omitted.",
1358
+ },
1343
1359
  },
1344
1360
  required: ["issueKey", "commentId", "comment"],
1345
1361
  },
@@ -1360,6 +1376,10 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
1360
1376
  description:
1361
1377
  "The ID of the comment to delete. Use jira_get_ticket to see comments and their IDs.",
1362
1378
  },
1379
+ instance: {
1380
+ type: "string",
1381
+ description: "Instance name override. Auto-detected from issue key prefix if omitted.",
1382
+ },
1363
1383
  },
1364
1384
  required: ["issueKey", "commentId"],
1365
1385
  },
@@ -1385,6 +1405,10 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
1385
1405
  description:
1386
1406
  "Target status name (e.g., 'Review', 'Done'). Will auto-transition through intermediate states if needed.",
1387
1407
  },
1408
+ instance: {
1409
+ type: "string",
1410
+ description: "Instance name override. Auto-detected from issue key prefix if omitted.",
1411
+ },
1388
1412
  },
1389
1413
  required: ["issueKey"],
1390
1414
  },
@@ -1438,6 +1462,10 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
1438
1462
  items: { type: "string" },
1439
1463
  description: "Labels to set on the ticket",
1440
1464
  },
1465
+ instance: {
1466
+ type: "string",
1467
+ description: "Instance name override. Auto-detected from issue key prefix if omitted.",
1468
+ },
1441
1469
  },
1442
1470
  required: ["issueKey"],
1443
1471
  },
@@ -1499,7 +1527,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
1499
1527
  {
1500
1528
  name: "jira_add_instance",
1501
1529
  description:
1502
- "Add or update a Jira instance configuration. Saves to config and makes it available immediately without restart. Use this to connect to a new Jira instance during a session.",
1530
+ "Add or update a Jira instance configuration. Saves to config and makes it available immediately without restart. To add a new instance, provide name + email + token + baseUrl. To update an existing instance (e.g. change projects or set as default), just provide name and the fields to change.",
1503
1531
  inputSchema: {
1504
1532
  type: "object",
1505
1533
  properties: {
@@ -1509,27 +1537,27 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
1509
1537
  },
1510
1538
  email: {
1511
1539
  type: "string",
1512
- description: "Jira account email",
1540
+ description: "Jira account email (required for new instances)",
1513
1541
  },
1514
1542
  token: {
1515
1543
  type: "string",
1516
- description: "Jira API token (from https://id.atlassian.com/manage-profile/security/api-tokens)",
1544
+ description: "Jira API token (required for new instances)",
1517
1545
  },
1518
1546
  baseUrl: {
1519
1547
  type: "string",
1520
- description: "Jira base URL (e.g., https://company.atlassian.net)",
1548
+ description: "Jira base URL (required for new instances, e.g., https://company.atlassian.net)",
1521
1549
  },
1522
1550
  projects: {
1523
1551
  type: "array",
1524
1552
  items: { type: "string" },
1525
- description: "Project key prefixes to auto-route to this instance (e.g., ['PROJ', 'ENG'])",
1553
+ description: "Project key prefixes to auto-route to this instance (e.g., ['PROJ', 'ENG']). Replaces existing projects.",
1526
1554
  },
1527
1555
  setDefault: {
1528
1556
  type: "boolean",
1529
1557
  description: "Set this instance as the default (default: false)",
1530
1558
  },
1531
1559
  },
1532
- required: ["name", "email", "token", "baseUrl"],
1560
+ required: ["name"],
1533
1561
  },
1534
1562
  },
1535
1563
  {
@@ -1609,7 +1637,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1609
1637
  } else if (name === "jira_get_ticket") {
1610
1638
  const downloadImages = args.downloadImages !== false;
1611
1639
  const fetchFigma = args.fetchFigma !== false;
1612
- const result = await getTicket(args.issueKey, downloadImages, fetchFigma);
1640
+ const inst = args.instance ? getInstanceByName(args.instance) : null;
1641
+ const result = await getTicket(args.issueKey, downloadImages, fetchFigma, inst);
1613
1642
 
1614
1643
  const content = [{ type: "text", text: result.text }];
1615
1644
 
@@ -1657,7 +1686,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1657
1686
  const result = await searchTickets(args.jql, args.maxResults || 10, args.fields || null, inst);
1658
1687
  return { content: [{ type: "text", text: result }] };
1659
1688
  } else if (name === "jira_add_comment") {
1660
- const inst = getInstanceForKey(args.issueKey);
1689
+ const inst = args.instance ? getInstanceByName(args.instance) : getInstanceForKey(args.issueKey);
1661
1690
  // Build ADF content with mention support
1662
1691
  const adfContent = await buildCommentADF(args.comment, inst);
1663
1692
  const body = {
@@ -1682,7 +1711,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1682
1711
  ],
1683
1712
  };
1684
1713
  } else if (name === "jira_reply_comment") {
1685
- const inst = getInstanceForKey(args.issueKey);
1714
+ const inst = args.instance ? getInstanceByName(args.instance) : getInstanceForKey(args.issueKey);
1686
1715
  // Fetch the original comment
1687
1716
  const original = await fetchJira(
1688
1717
  `/issue/${args.issueKey}/comment/${args.commentId}`,
@@ -1750,7 +1779,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1750
1779
  ],
1751
1780
  };
1752
1781
  } else if (name === "jira_edit_comment") {
1753
- const inst = getInstanceForKey(args.issueKey);
1782
+ const inst = args.instance ? getInstanceByName(args.instance) : getInstanceForKey(args.issueKey);
1754
1783
  // Build ADF content with mention support
1755
1784
  const adfContent = await buildCommentADF(args.comment, inst);
1756
1785
  const body = {
@@ -1774,7 +1803,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1774
1803
  ],
1775
1804
  };
1776
1805
  } else if (name === "jira_delete_comment") {
1777
- const inst = getInstanceForKey(args.issueKey);
1806
+ const inst = args.instance ? getInstanceByName(args.instance) : getInstanceForKey(args.issueKey);
1778
1807
  await fetchJira(`/issue/${args.issueKey}/comment/${args.commentId}`, {
1779
1808
  method: "DELETE",
1780
1809
  }, inst);
@@ -1787,7 +1816,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1787
1816
  ],
1788
1817
  };
1789
1818
  } else if (name === "jira_transition") {
1790
- const inst = getInstanceForKey(args.issueKey);
1819
+ const inst = args.instance ? getInstanceByName(args.instance) : getInstanceForKey(args.issueKey);
1791
1820
  if (!args.transitionId && !args.targetStatus) {
1792
1821
  // List available transitions
1793
1822
  const result = await fetchJira(`/issue/${args.issueKey}/transitions`, {}, inst);
@@ -1883,7 +1912,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1883
1912
  ],
1884
1913
  };
1885
1914
  } else if (name === "jira_update_ticket") {
1886
- const inst = getInstanceForKey(args.issueKey);
1915
+ const inst = args.instance ? getInstanceByName(args.instance) : getInstanceForKey(args.issueKey);
1887
1916
  const fields = {};
1888
1917
  if (args.summary) {
1889
1918
  if (args.replaceSummary) {
@@ -2006,31 +2035,34 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
2006
2035
  }
2007
2036
  } else if (name === "jira_add_instance") {
2008
2037
  const instName = args.name.trim();
2009
- const projects = (args.projects || []).map((p) => p.toUpperCase());
2010
- const authStr = Buffer.from(`${args.email}:${args.token}`).toString("base64");
2011
- const newInstance = {
2012
- name: instName,
2013
- email: args.email,
2014
- token: args.token,
2015
- baseUrl: args.baseUrl.replace(/\/$/, ""),
2016
- projects,
2017
- auth: authStr,
2018
- };
2038
+ const existingIdx = instances.findIndex((i) => i.name === instName);
2039
+ const isUpdate = existingIdx >= 0;
2040
+
2041
+ // For new instances, email/token/baseUrl are required
2042
+ if (!isUpdate && (!args.email || !args.token || !args.baseUrl)) {
2043
+ return {
2044
+ content: [{ type: "text", text: "New instance requires email, token, and baseUrl." }],
2045
+ isError: true,
2046
+ };
2047
+ }
2048
+
2049
+ // Merge with existing or create new
2050
+ const existing = isUpdate ? instances[existingIdx] : {};
2051
+ const email = args.email || existing.email;
2052
+ const token = args.token || existing.token;
2053
+ const baseUrl = args.baseUrl ? args.baseUrl.replace(/\/$/, "") : existing.baseUrl;
2054
+ const projects = args.projects ? args.projects.map((p) => p.toUpperCase()) : (existing.projects || []);
2055
+ const authStr = Buffer.from(`${email}:${token}`).toString("base64");
2056
+
2057
+ const newInstance = { name: instName, email, token, baseUrl, projects, auth: authStr };
2019
2058
 
2020
2059
  // Update in-memory instances
2021
- const existingIdx = instances.findIndex((i) => i.name === instName);
2022
- if (existingIdx >= 0) {
2060
+ if (isUpdate) {
2023
2061
  instances[existingIdx] = newInstance;
2024
2062
  } else {
2025
2063
  instances.push(newInstance);
2026
2064
  }
2027
2065
 
2028
- // Update default if requested or if it's the first instance
2029
- if (args.setDefault || instances.length === 1) {
2030
- // Can't reassign const, but defaultInstance is used via getInstanceByName/getInstanceForKey
2031
- // which search the instances array, so this is handled by rawConfig.defaultInstance below
2032
- }
2033
-
2034
2066
  // Persist to config file
2035
2067
  const savedConfig = loadConfigFile();
2036
2068
  if (!savedConfig.instances) {
@@ -2053,7 +2085,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
2053
2085
  }
2054
2086
 
2055
2087
  // Save without the computed auth field
2056
- const toSave = { name: instName, email: args.email, token: args.token, baseUrl: newInstance.baseUrl, projects };
2088
+ const toSave = { name: instName, email, token, baseUrl, projects };
2057
2089
  const savedIdx = savedConfig.instances.findIndex((i) => i.name === instName);
2058
2090
  if (savedIdx >= 0) {
2059
2091
  savedConfig.instances[savedIdx] = toSave;
@@ -2065,8 +2097,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
2065
2097
  }
2066
2098
  fs.writeFileSync(jiraConfigPath, JSON.stringify(savedConfig, null, 2));
2067
2099
 
2068
- const action = existingIdx >= 0 ? "Updated" : "Added";
2069
- let text = `${action} instance "${instName}" (${newInstance.baseUrl}).`;
2100
+ const action = isUpdate ? "Updated" : "Added";
2101
+ let text = `${action} instance "${instName}" (${baseUrl}).`;
2070
2102
  if (projects.length > 0) text += ` Projects: ${projects.join(", ")}.`;
2071
2103
  if (args.setDefault) text += " Set as default.";
2072
2104
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rui.branco/jira-mcp",
3
- "version": "1.6.9",
3
+ "version": "1.6.11",
4
4
  "description": "Jira MCP server for Claude Code - fetch tickets, search with JQL, update tickets, manage comments, change status, and get Figma designs",
5
5
  "main": "index.js",
6
6
  "bin": {