@rui.branco/jira-mcp 1.6.7 → 1.6.8
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/index.js +285 -23
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -19,21 +19,30 @@ const path = require("path");
|
|
|
19
19
|
const fetch = require("node-fetch");
|
|
20
20
|
const { spawn, execSync } = require("child_process");
|
|
21
21
|
|
|
22
|
-
// Auto-update:
|
|
23
|
-
const
|
|
24
|
-
const
|
|
22
|
+
// Auto-update: check GitHub for new commits, install in background
|
|
23
|
+
const GITHUB_REPO = "rui-branco/jira-mcp";
|
|
24
|
+
const INSTALLED_SHA_FILE = path.join(__dirname, ".installed-sha");
|
|
25
25
|
try {
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
26
|
+
const localSha = fs.existsSync(INSTALLED_SHA_FILE)
|
|
27
|
+
? fs.readFileSync(INSTALLED_SHA_FILE, "utf-8").trim()
|
|
28
|
+
: "";
|
|
29
|
+
const remoteSha = execSync(
|
|
30
|
+
`git ls-remote https://github.com/${GITHUB_REPO}.git HEAD`,
|
|
31
|
+
{ stdio: "pipe", timeout: 5000 },
|
|
32
|
+
)
|
|
30
33
|
.toString()
|
|
34
|
+
.split("\t")[0]
|
|
31
35
|
.trim();
|
|
32
|
-
if (
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
36
|
+
if (remoteSha && remoteSha !== localSha) {
|
|
37
|
+
const child = spawn(
|
|
38
|
+
"sh",
|
|
39
|
+
[
|
|
40
|
+
"-c",
|
|
41
|
+
`npm install -g git+ssh://git@github.com/${GITHUB_REPO}.git && echo "${remoteSha}" > "${INSTALLED_SHA_FILE}"`,
|
|
42
|
+
],
|
|
43
|
+
{ stdio: "ignore", detached: true },
|
|
44
|
+
);
|
|
45
|
+
child.unref();
|
|
37
46
|
}
|
|
38
47
|
} catch {}
|
|
39
48
|
|
|
@@ -270,6 +279,8 @@ async function parseInlineFormatting(text) {
|
|
|
270
279
|
|
|
271
280
|
// Parse text with markdown formatting and @mentions, build ADF content
|
|
272
281
|
async function buildCommentADF(text) {
|
|
282
|
+
// Sanitize: replace em dashes and en dashes with hyphen
|
|
283
|
+
text = text.replace(/[—–]/g, "-");
|
|
273
284
|
// Split into blocks by double newlines (paragraphs)
|
|
274
285
|
const blocks = text.split(/\n\n+/);
|
|
275
286
|
const content = [];
|
|
@@ -584,6 +595,7 @@ async function fetchFigmaDesign(url) {
|
|
|
584
595
|
async function getTicket(issueKey, downloadImages = true, fetchFigma = true) {
|
|
585
596
|
const issue = await fetchJira(`/issue/${issueKey}?expand=renderedFields`);
|
|
586
597
|
const fields = issue.fields;
|
|
598
|
+
const storyPoints = fields.customfield_10016 ?? fields.story_points ?? null;
|
|
587
599
|
|
|
588
600
|
let output = `# ${issueKey}: ${fields.summary}\n\n`;
|
|
589
601
|
output += `**Status:** ${fields.status?.name || "Unknown"}\n`;
|
|
@@ -592,6 +604,15 @@ async function getTicket(issueKey, downloadImages = true, fetchFigma = true) {
|
|
|
592
604
|
output += `**Assignee:** ${fields.assignee?.displayName || "Unassigned"}\n`;
|
|
593
605
|
output += `**Reporter:** ${fields.reporter?.displayName || "Unknown"}\n`;
|
|
594
606
|
|
|
607
|
+
// Date fields
|
|
608
|
+
if (fields.created) output += `**Created:** ${fields.created}\n`;
|
|
609
|
+
if (fields.updated) output += `**Updated:** ${fields.updated}\n`;
|
|
610
|
+
if (fields.resolutiondate) output += `**Resolved:** ${fields.resolutiondate}\n`;
|
|
611
|
+
if (fields.resolution) output += `**Resolution:** ${fields.resolution.name}\n`;
|
|
612
|
+
|
|
613
|
+
// Story points
|
|
614
|
+
if (storyPoints != null) output += `**Story Points:** ${storyPoints}\n`;
|
|
615
|
+
|
|
595
616
|
if (fields.sprint) {
|
|
596
617
|
output += `**Sprint:** ${fields.sprint.name}\n`;
|
|
597
618
|
}
|
|
@@ -600,6 +621,16 @@ async function getTicket(issueKey, downloadImages = true, fetchFigma = true) {
|
|
|
600
621
|
output += `**Parent:** ${fields.parent.key} - ${fields.parent.fields?.summary || ""}\n`;
|
|
601
622
|
}
|
|
602
623
|
|
|
624
|
+
// Labels, Components, Fix Versions
|
|
625
|
+
if (fields.labels?.length > 0) output += `**Labels:** ${fields.labels.join(", ")}\n`;
|
|
626
|
+
if (fields.components?.length > 0) output += `**Components:** ${fields.components.map(c => c.name).join(", ")}\n`;
|
|
627
|
+
if (fields.fixVersions?.length > 0) output += `**Fix Versions:** ${fields.fixVersions.map(v => v.name).join(", ")}\n`;
|
|
628
|
+
|
|
629
|
+
// Time tracking
|
|
630
|
+
if (fields.timetracking && (fields.timetracking.originalEstimate || fields.timetracking.timeSpent)) {
|
|
631
|
+
output += `**Time Tracking:** estimate=${fields.timetracking.originalEstimate || "none"}, spent=${fields.timetracking.timeSpent || "none"}, remaining=${fields.timetracking.remainingEstimate || "none"}\n`;
|
|
632
|
+
}
|
|
633
|
+
|
|
603
634
|
// Subtasks
|
|
604
635
|
if (fields.subtasks?.length > 0) {
|
|
605
636
|
output += `**Subtasks:** ${fields.subtasks.length}\n`;
|
|
@@ -919,17 +950,202 @@ async function getTicket(issueKey, downloadImages = true, fetchFigma = true) {
|
|
|
919
950
|
return { text: output, jiraImages: downloadedImages, figmaDesigns };
|
|
920
951
|
}
|
|
921
952
|
|
|
922
|
-
async function searchTickets(jql, maxResults = 10) {
|
|
953
|
+
async function searchTickets(jql, maxResults = 10, fields = null) {
|
|
954
|
+
const defaultFields = [
|
|
955
|
+
"summary", "status", "assignee", "reporter", "issuetype", "priority",
|
|
956
|
+
"created", "resolutiondate", "updated", "statuscategorychangedate",
|
|
957
|
+
"resolution", "timetracking", "aggregatetimeoriginalestimate",
|
|
958
|
+
"aggregatetimespent", "parent", "labels", "components", "fixVersions",
|
|
959
|
+
"customfield_10016", // story points (Jira Software)
|
|
960
|
+
];
|
|
961
|
+
const requestFields = fields || defaultFields;
|
|
923
962
|
const data = await fetchJira(
|
|
924
|
-
`/search/jql?jql=${encodeURIComponent(jql)}&maxResults=${maxResults}`,
|
|
963
|
+
`/search/jql?jql=${encodeURIComponent(jql)}&maxResults=${maxResults}&fields=${requestFields.join(",")}`,
|
|
925
964
|
);
|
|
926
965
|
|
|
927
|
-
|
|
966
|
+
const issues = data.issues || [];
|
|
967
|
+
let output = `# Search Results (${data.total || 0} total, showing ${issues.length})\n\n`;
|
|
968
|
+
|
|
969
|
+
for (const issue of issues) {
|
|
970
|
+
const f = issue.fields || {};
|
|
971
|
+
const storyPoints = f.customfield_10016 ?? f.story_points ?? null;
|
|
972
|
+
|
|
973
|
+
output += `- **${issue.key}**: ${f.summary || "No summary"}\n`;
|
|
974
|
+
output += ` Status: ${f.status?.name || "Unknown"} | Type: ${f.issuetype?.name || "Unknown"} | Priority: ${f.priority?.name || "None"}\n`;
|
|
975
|
+
output += ` Assignee: ${f.assignee?.displayName || "Unassigned"} | Reporter: ${f.reporter?.displayName || "Unknown"}\n`;
|
|
976
|
+
|
|
977
|
+
if (f.created) output += ` Created: ${f.created}\n`;
|
|
978
|
+
if (f.updated) output += ` Updated: ${f.updated}\n`;
|
|
979
|
+
if (f.resolutiondate) output += ` Resolved: ${f.resolutiondate}\n`;
|
|
980
|
+
if (f.resolution) output += ` Resolution: ${f.resolution.name}\n`;
|
|
981
|
+
if (storyPoints != null) output += ` Story Points: ${storyPoints}\n`;
|
|
982
|
+
if (f.parent) output += ` Parent: ${f.parent.key}${f.parent.fields?.summary ? ` - ${f.parent.fields.summary}` : ""}\n`;
|
|
983
|
+
if (f.labels?.length > 0) output += ` Labels: ${f.labels.join(", ")}\n`;
|
|
984
|
+
if (f.components?.length > 0) output += ` Components: ${f.components.map(c => c.name).join(", ")}\n`;
|
|
985
|
+
if (f.fixVersions?.length > 0) output += ` Fix Versions: ${f.fixVersions.map(v => v.name).join(", ")}\n`;
|
|
986
|
+
if (f.timetracking && (f.timetracking.originalEstimate || f.timetracking.timeSpent)) {
|
|
987
|
+
output += ` Time Tracking: estimate=${f.timetracking.originalEstimate || "none"}, spent=${f.timetracking.timeSpent || "none"}, remaining=${f.timetracking.remainingEstimate || "none"}\n`;
|
|
988
|
+
}
|
|
928
989
|
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
990
|
+
output += "\n";
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
return output;
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
// ============ CHANGELOG FUNCTIONS ============
|
|
997
|
+
|
|
998
|
+
function parseStatusHistory(changelog) {
|
|
999
|
+
const statusChanges = [];
|
|
1000
|
+
if (!changelog?.histories) return statusChanges;
|
|
1001
|
+
|
|
1002
|
+
for (const history of changelog.histories) {
|
|
1003
|
+
for (const item of history.items || []) {
|
|
1004
|
+
if (item.field === "status") {
|
|
1005
|
+
statusChanges.push({
|
|
1006
|
+
from: item.fromString,
|
|
1007
|
+
to: item.toString,
|
|
1008
|
+
date: history.created,
|
|
1009
|
+
author: history.author?.displayName || "Unknown",
|
|
1010
|
+
});
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
// Sort chronologically
|
|
1016
|
+
statusChanges.sort((a, b) => new Date(a.date) - new Date(b.date));
|
|
1017
|
+
return statusChanges;
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
function computeMetrics(statusHistory, created) {
|
|
1021
|
+
if (!statusHistory.length) return null;
|
|
1022
|
+
|
|
1023
|
+
const metrics = {
|
|
1024
|
+
cycleTime: null,
|
|
1025
|
+
leadTime: null,
|
|
1026
|
+
timesInProgress: 0,
|
|
1027
|
+
timeInStatus: {},
|
|
1028
|
+
};
|
|
1029
|
+
|
|
1030
|
+
// Build timeline: start with created date in the initial status
|
|
1031
|
+
const timeline = [];
|
|
1032
|
+
if (created && statusHistory.length > 0) {
|
|
1033
|
+
timeline.push({ status: statusHistory[0].from, date: new Date(created) });
|
|
1034
|
+
}
|
|
1035
|
+
for (const change of statusHistory) {
|
|
1036
|
+
timeline.push({ status: change.to, date: new Date(change.date) });
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
// Calculate time in each status
|
|
1040
|
+
for (let i = 0; i < timeline.length - 1; i++) {
|
|
1041
|
+
const status = timeline[i].status;
|
|
1042
|
+
const duration = timeline[i + 1].date - timeline[i].date;
|
|
1043
|
+
metrics.timeInStatus[status] = (metrics.timeInStatus[status] || 0) + duration;
|
|
1044
|
+
}
|
|
1045
|
+
// Add current status (time since last transition)
|
|
1046
|
+
const last = timeline[timeline.length - 1];
|
|
1047
|
+
const sinceLastTransition = Date.now() - last.date;
|
|
1048
|
+
metrics.timeInStatus[last.status] = (metrics.timeInStatus[last.status] || 0) + sinceLastTransition;
|
|
1049
|
+
|
|
1050
|
+
// Count times in progress-like statuses
|
|
1051
|
+
metrics.timesInProgress = statusHistory.filter(
|
|
1052
|
+
(c) => c.to.toLowerCase().includes("progress"),
|
|
1053
|
+
).length;
|
|
1054
|
+
|
|
1055
|
+
// Cycle time: first "In Progress" to last "Done"
|
|
1056
|
+
const firstInProgress = statusHistory.find(
|
|
1057
|
+
(c) => c.to.toLowerCase().includes("progress"),
|
|
1058
|
+
);
|
|
1059
|
+
const lastDone = [...statusHistory].reverse().find(
|
|
1060
|
+
(c) => c.to.toLowerCase() === "done" || c.to.toLowerCase() === "closed",
|
|
1061
|
+
);
|
|
1062
|
+
if (firstInProgress && lastDone) {
|
|
1063
|
+
metrics.cycleTime = new Date(lastDone.date) - new Date(firstInProgress.date);
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
// Lead time: created to done
|
|
1067
|
+
if (created && lastDone) {
|
|
1068
|
+
metrics.leadTime = new Date(lastDone.date) - new Date(created);
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
// Format durations
|
|
1072
|
+
const fmt = (ms) => {
|
|
1073
|
+
if (ms == null) return null;
|
|
1074
|
+
const totalMinutes = Math.floor(ms / 60000);
|
|
1075
|
+
const days = Math.floor(totalMinutes / (60 * 24));
|
|
1076
|
+
const hours = Math.floor((totalMinutes % (60 * 24)) / 60);
|
|
1077
|
+
const minutes = totalMinutes % 60;
|
|
1078
|
+
const parts = [];
|
|
1079
|
+
if (days > 0) parts.push(`${days}d`);
|
|
1080
|
+
if (hours > 0) parts.push(`${hours}h`);
|
|
1081
|
+
parts.push(`${minutes}m`);
|
|
1082
|
+
return parts.join(" ");
|
|
1083
|
+
};
|
|
1084
|
+
|
|
1085
|
+
return {
|
|
1086
|
+
cycleTime: fmt(metrics.cycleTime),
|
|
1087
|
+
leadTime: fmt(metrics.leadTime),
|
|
1088
|
+
timesInProgress: metrics.timesInProgress,
|
|
1089
|
+
timeInStatus: Object.fromEntries(
|
|
1090
|
+
Object.entries(metrics.timeInStatus).map(([k, v]) => [k, fmt(v)]),
|
|
1091
|
+
),
|
|
1092
|
+
};
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
function formatChangelog(issueKey, statusHistory, created) {
|
|
1096
|
+
let output = `## Status History for ${issueKey}\n\n`;
|
|
1097
|
+
|
|
1098
|
+
if (statusHistory.length === 0) {
|
|
1099
|
+
output += "_No status transitions found._\n";
|
|
1100
|
+
return output;
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
for (const change of statusHistory) {
|
|
1104
|
+
const date = new Date(change.date).toLocaleString();
|
|
1105
|
+
output += `- **${change.from}** → **${change.to}** (${date}, by ${change.author})\n`;
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
const computed = computeMetrics(statusHistory, created);
|
|
1109
|
+
if (computed) {
|
|
1110
|
+
output += `\n### Computed Metrics\n\n`;
|
|
1111
|
+
if (computed.cycleTime) output += `- **Cycle Time:** ${computed.cycleTime}\n`;
|
|
1112
|
+
if (computed.leadTime) output += `- **Lead Time:** ${computed.leadTime}\n`;
|
|
1113
|
+
output += `- **Times In Progress:** ${computed.timesInProgress}\n`;
|
|
1114
|
+
if (Object.keys(computed.timeInStatus).length > 0) {
|
|
1115
|
+
output += `- **Time in Status:**\n`;
|
|
1116
|
+
for (const [status, time] of Object.entries(computed.timeInStatus)) {
|
|
1117
|
+
output += ` - ${status}: ${time}\n`;
|
|
1118
|
+
}
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1122
|
+
return output;
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
async function getChangelog(issueKey) {
|
|
1126
|
+
const issue = await fetchJira(`/issue/${issueKey}?expand=changelog&fields=created`);
|
|
1127
|
+
const statusHistory = parseStatusHistory(issue.changelog);
|
|
1128
|
+
const created = issue.fields?.created;
|
|
1129
|
+
return { statusHistory, created, formatted: formatChangelog(issueKey, statusHistory, created) };
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1132
|
+
async function getChangelogsBulk(jql, maxResults = 50) {
|
|
1133
|
+
// First get the issue keys matching the JQL
|
|
1134
|
+
const data = await fetchJira(
|
|
1135
|
+
`/search/jql?jql=${encodeURIComponent(jql)}&maxResults=${maxResults}&fields=key,created`,
|
|
1136
|
+
);
|
|
1137
|
+
const issues = data.issues || [];
|
|
1138
|
+
|
|
1139
|
+
let output = `# Changelogs (${issues.length} of ${data.total || 0} issues)\n\n`;
|
|
1140
|
+
|
|
1141
|
+
// Fetch changelog for each issue (JIRA search API doesn't support expand=changelog on /search/jql)
|
|
1142
|
+
for (const issue of issues) {
|
|
1143
|
+
try {
|
|
1144
|
+
const result = await getChangelog(issue.key);
|
|
1145
|
+
output += result.formatted + "\n";
|
|
1146
|
+
} catch (e) {
|
|
1147
|
+
output += `## ${issue.key}\n\n_Error fetching changelog: ${e.message}_\n\n`;
|
|
1148
|
+
}
|
|
933
1149
|
}
|
|
934
1150
|
|
|
935
1151
|
return output;
|
|
@@ -982,7 +1198,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
982
1198
|
{
|
|
983
1199
|
name: "jira_search",
|
|
984
1200
|
description:
|
|
985
|
-
"Search Jira tickets using JQL. Examples: 'project = MODS AND status = Open'",
|
|
1201
|
+
"Search Jira tickets using JQL. Returns detailed fields including dates, story points, labels, components, fix versions, time tracking, and parent. Examples: 'project = MODS AND status = Open'",
|
|
986
1202
|
inputSchema: {
|
|
987
1203
|
type: "object",
|
|
988
1204
|
properties: {
|
|
@@ -991,6 +1207,12 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
991
1207
|
type: "number",
|
|
992
1208
|
description: "Max results (default 10)",
|
|
993
1209
|
},
|
|
1210
|
+
fields: {
|
|
1211
|
+
type: "array",
|
|
1212
|
+
items: { type: "string" },
|
|
1213
|
+
description:
|
|
1214
|
+
"Custom list of fields to return. Default includes: summary, status, assignee, reporter, issuetype, priority, created, resolutiondate, updated, resolution, timetracking, parent, labels, components, fixVersions, customfield_10016 (story points).",
|
|
1215
|
+
},
|
|
994
1216
|
},
|
|
995
1217
|
required: ["jql"],
|
|
996
1218
|
},
|
|
@@ -998,7 +1220,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
998
1220
|
{
|
|
999
1221
|
name: "jira_add_comment",
|
|
1000
1222
|
description:
|
|
1001
|
-
"Add a comment to a Jira ticket. IMPORTANT: Use @DisplayName (e.g. @Julia Pereszta) for mentions — NOT [~accountId:...] syntax. Keep comments non-technical and user-facing. Never mention git details like 'pushed to main', branch names, or technical implementation details — stakeholders don't care about that.",
|
|
1223
|
+
"Add a comment to a Jira ticket. IMPORTANT: Use @DisplayName (e.g. @Julia Pereszta) for mentions — NOT [~accountId:...] syntax. Keep comments non-technical and user-facing. Never mention git details like 'pushed to main', branch names, or technical implementation details — stakeholders don't care about that. NEVER use em dashes (—) or en dashes (–) in comments — use commas, periods, or rewrite the sentence instead.",
|
|
1002
1224
|
inputSchema: {
|
|
1003
1225
|
type: "object",
|
|
1004
1226
|
properties: {
|
|
@@ -1101,7 +1323,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
1101
1323
|
{
|
|
1102
1324
|
name: "jira_update_ticket",
|
|
1103
1325
|
description:
|
|
1104
|
-
"Update fields on a Jira ticket. IMPORTANT: Only pass the fields you want to change. Omitted fields are left untouched.",
|
|
1326
|
+
"Update fields on a Jira ticket. IMPORTANT: Only pass the fields you want to change. Omitted fields are left untouched. NEVER use em dashes (—) or en dashes (–) in text — use commas, periods, or rewrite the sentence instead.",
|
|
1105
1327
|
inputSchema: {
|
|
1106
1328
|
type: "object",
|
|
1107
1329
|
properties: {
|
|
@@ -1171,6 +1393,32 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
1171
1393
|
required: ["query"],
|
|
1172
1394
|
},
|
|
1173
1395
|
},
|
|
1396
|
+
{
|
|
1397
|
+
name: "jira_get_changelog",
|
|
1398
|
+
description:
|
|
1399
|
+
"Get status change history for a Jira ticket or multiple tickets via JQL. Returns all status transitions with timestamps and authors, plus computed metrics (cycle time, lead time, time in each status). Use issueKey for a single ticket, or jql for bulk retrieval.",
|
|
1400
|
+
inputSchema: {
|
|
1401
|
+
type: "object",
|
|
1402
|
+
properties: {
|
|
1403
|
+
issueKey: {
|
|
1404
|
+
type: "string",
|
|
1405
|
+
description:
|
|
1406
|
+
"Single issue key (e.g., MODS-13996). Use this OR jql, not both.",
|
|
1407
|
+
},
|
|
1408
|
+
jql: {
|
|
1409
|
+
type: "string",
|
|
1410
|
+
description:
|
|
1411
|
+
"JQL query to get changelogs for multiple tickets (e.g., 'project = MODS AND sprint = 123'). Use this OR issueKey, not both.",
|
|
1412
|
+
},
|
|
1413
|
+
maxResults: {
|
|
1414
|
+
type: "number",
|
|
1415
|
+
description:
|
|
1416
|
+
"Max results when using jql (default 50). Each ticket requires a separate API call, so keep this reasonable.",
|
|
1417
|
+
},
|
|
1418
|
+
},
|
|
1419
|
+
required: [],
|
|
1420
|
+
},
|
|
1421
|
+
},
|
|
1174
1422
|
],
|
|
1175
1423
|
};
|
|
1176
1424
|
});
|
|
@@ -1263,7 +1511,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1263
1511
|
|
|
1264
1512
|
return { content };
|
|
1265
1513
|
} else if (name === "jira_search") {
|
|
1266
|
-
const result = await searchTickets(args.jql, args.maxResults || 10);
|
|
1514
|
+
const result = await searchTickets(args.jql, args.maxResults || 10, args.fields || null);
|
|
1267
1515
|
return { content: [{ type: "text", text: result }] };
|
|
1268
1516
|
} else if (name === "jira_add_comment") {
|
|
1269
1517
|
// Build ADF content with mention support
|
|
@@ -1589,6 +1837,20 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1589
1837
|
{ type: "text", text: `Updated ${args.issueKey}: ${updated}.` },
|
|
1590
1838
|
],
|
|
1591
1839
|
};
|
|
1840
|
+
} else if (name === "jira_get_changelog") {
|
|
1841
|
+
if (!args.issueKey && !args.jql) {
|
|
1842
|
+
return {
|
|
1843
|
+
content: [{ type: "text", text: "Error: Provide either issueKey or jql parameter." }],
|
|
1844
|
+
isError: true,
|
|
1845
|
+
};
|
|
1846
|
+
}
|
|
1847
|
+
if (args.issueKey) {
|
|
1848
|
+
const result = await getChangelog(args.issueKey);
|
|
1849
|
+
return { content: [{ type: "text", text: result.formatted }] };
|
|
1850
|
+
} else {
|
|
1851
|
+
const result = await getChangelogsBulk(args.jql, args.maxResults || 50);
|
|
1852
|
+
return { content: [{ type: "text", text: result }] };
|
|
1853
|
+
}
|
|
1592
1854
|
} else {
|
|
1593
1855
|
throw new Error(`Unknown tool: ${name}`);
|
|
1594
1856
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rui.branco/jira-mcp",
|
|
3
|
-
"version": "1.6.
|
|
3
|
+
"version": "1.6.8",
|
|
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": {
|