@rui.branco/jira-mcp 1.6.16 → 1.6.18
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 +73 -37
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -208,22 +208,58 @@ async function fetchJiraTeams(endpoint, options = {}, instance = defaultInstance
|
|
|
208
208
|
return text ? JSON.parse(text) : {};
|
|
209
209
|
}
|
|
210
210
|
|
|
211
|
-
async function
|
|
212
|
-
const
|
|
213
|
-
`/
|
|
211
|
+
async function searchTeamsViaJql(query, instance) {
|
|
212
|
+
const data = await fetchJira(
|
|
213
|
+
`/jql/autocompletedata/suggestions?fieldName=cf[10001]&fieldValue=${encodeURIComponent(query)}`,
|
|
214
214
|
{},
|
|
215
215
|
instance,
|
|
216
216
|
);
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
217
|
+
return (data.results || []).map((r) => ({
|
|
218
|
+
title: r.displayName.replace(/<[^>]*>/g, "").replace(/&/g, "&"),
|
|
219
|
+
id: r.value,
|
|
220
|
+
}));
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
async function listTeams(instance) {
|
|
224
|
+
// Try Teams API first, fall back to JQL autocomplete
|
|
225
|
+
try {
|
|
226
|
+
const teams = await fetchJiraTeams(
|
|
227
|
+
`/teams/find?query=&excludeMembers=true`, {}, instance,
|
|
224
228
|
);
|
|
229
|
+
return teams.map((t) => ({ title: t.title, id: `${t.organizationId}-${t.id}` }));
|
|
230
|
+
} catch {
|
|
231
|
+
// Teams API not available, use JQL autocomplete with a broad search
|
|
232
|
+
return searchTeamsViaJql("", instance);
|
|
225
233
|
}
|
|
226
|
-
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
async function resolveTeamId(teamName, instance) {
|
|
237
|
+
// Try Teams API first
|
|
238
|
+
try {
|
|
239
|
+
const teams = await fetchJiraTeams(
|
|
240
|
+
`/teams/find?query=${encodeURIComponent(teamName)}&excludeMembers=true`,
|
|
241
|
+
{},
|
|
242
|
+
instance,
|
|
243
|
+
);
|
|
244
|
+
const match = teams.find(
|
|
245
|
+
(t) => t.title.toLowerCase() === teamName.toLowerCase(),
|
|
246
|
+
);
|
|
247
|
+
if (match) return `${match.organizationId}-${match.id}`;
|
|
248
|
+
} catch {
|
|
249
|
+
// Teams API not available, fall through to JQL
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Fallback: JQL autocomplete
|
|
253
|
+
const jqlTeams = await searchTeamsViaJql(teamName, instance);
|
|
254
|
+
const match = jqlTeams.find(
|
|
255
|
+
(t) => t.title.toLowerCase() === teamName.toLowerCase(),
|
|
256
|
+
);
|
|
257
|
+
if (match) return match.id;
|
|
258
|
+
|
|
259
|
+
const available = jqlTeams.map((t) => t.title).join(", ");
|
|
260
|
+
throw new Error(
|
|
261
|
+
`Team "${teamName}" not found.${available ? ` Similar teams: ${available}` : ""}`,
|
|
262
|
+
);
|
|
227
263
|
}
|
|
228
264
|
|
|
229
265
|
async function downloadAttachment(url, filename, issueKey, instance) {
|
|
@@ -1795,7 +1831,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
1795
1831
|
},
|
|
1796
1832
|
defaultTeam: {
|
|
1797
1833
|
type: "string",
|
|
1798
|
-
description: "Default team name to auto-assign when creating tickets (e.g., 'Site Surveys (MODS)'). Resolved and validated via Jira Teams API. Pass empty string to
|
|
1834
|
+
description: "Default team name to auto-assign when creating tickets (e.g., 'Site Surveys (MODS)'). Resolved and validated via Jira Teams API. Pass 'none' to explicitly disable team prompts. Pass empty string to reset.",
|
|
1799
1835
|
},
|
|
1800
1836
|
},
|
|
1801
1837
|
required: ["name"],
|
|
@@ -2859,6 +2895,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
2859
2895
|
if (args.defaultTeam !== undefined) {
|
|
2860
2896
|
if (args.defaultTeam === "") {
|
|
2861
2897
|
defaultTeam = undefined;
|
|
2898
|
+
} else if (args.defaultTeam.toLowerCase() === "none") {
|
|
2899
|
+
defaultTeam = "none";
|
|
2862
2900
|
} else {
|
|
2863
2901
|
// Validate team exists via Jira Teams API
|
|
2864
2902
|
const tempInst = { baseUrl, auth: authStr };
|
|
@@ -2868,11 +2906,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
2868
2906
|
} catch (e) {
|
|
2869
2907
|
// Try to list available teams for a helpful error
|
|
2870
2908
|
try {
|
|
2871
|
-
const teams = await
|
|
2872
|
-
`/teams/find?query=&excludeMembers=true`,
|
|
2873
|
-
{},
|
|
2874
|
-
tempInst,
|
|
2875
|
-
);
|
|
2909
|
+
const teams = await listTeams(tempInst);
|
|
2876
2910
|
const available = teams.map((t) => t.title).join(", ");
|
|
2877
2911
|
return {
|
|
2878
2912
|
content: [{ type: "text", text: `Team "${args.defaultTeam}" not found. Available teams: ${available}` }],
|
|
@@ -2936,18 +2970,18 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
2936
2970
|
let text = `${action} instance "${instName}" (${baseUrl}).`;
|
|
2937
2971
|
if (projects.length > 0) text += ` Projects: ${projects.join(", ")}.`;
|
|
2938
2972
|
if (args.setDefault) text += " Set as default.";
|
|
2939
|
-
if (defaultTeam) {
|
|
2973
|
+
if (defaultTeam === "none") {
|
|
2974
|
+
text += " Default team: None (disabled).";
|
|
2975
|
+
} else if (defaultTeam) {
|
|
2940
2976
|
text += ` Default team: ${defaultTeam.name}.`;
|
|
2941
2977
|
} else {
|
|
2942
2978
|
// No default team — fetch available teams and prompt
|
|
2943
2979
|
try {
|
|
2944
2980
|
const tempInst = { baseUrl, auth: authStr };
|
|
2945
|
-
const teams = await
|
|
2946
|
-
`/teams/find?query=&excludeMembers=true`, {}, tempInst,
|
|
2947
|
-
);
|
|
2981
|
+
const teams = await listTeams(tempInst);
|
|
2948
2982
|
if (teams && teams.length > 0) {
|
|
2949
2983
|
const list = teams.map((t, i) => `${i + 1}. ${t.title}`).join("\n");
|
|
2950
|
-
text += `\n\n⚠ No default team configured. Available teams:\n${list}\n0. None\n\nAsk the user which team to set as default. If they pick one, call jira_add_instance with name="${instName}" and defaultTeam="<team name>".`;
|
|
2984
|
+
text += `\n\n⚠ No default team configured. Available teams:\n${list}\n0. None\n\nAsk the user which team to set as default. If they pick one, call jira_add_instance with name="${instName}" and defaultTeam="<team name>". If "None" is selected, call jira_add_instance with defaultTeam="none" to stop future prompts.`;
|
|
2951
2985
|
}
|
|
2952
2986
|
} catch {
|
|
2953
2987
|
// Teams API not available, skip
|
|
@@ -2994,11 +3028,17 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
2994
3028
|
}
|
|
2995
3029
|
const currentDefault = rawConfig.defaultInstance || instances[0].name;
|
|
2996
3030
|
let text = `# Configured Jira Instances (${instances.length})\n\n`;
|
|
3031
|
+
const missingTeam = [];
|
|
2997
3032
|
for (const inst of instances) {
|
|
2998
3033
|
const isDefault = inst.name === currentDefault ? " **(default)**" : "";
|
|
2999
3034
|
const projs = inst.projects?.length > 0 ? `\n Projects: ${inst.projects.join(", ")}` : "";
|
|
3000
|
-
const team = inst.defaultTeam ? `\n Default team: ${inst.defaultTeam.name}` : "";
|
|
3035
|
+
const team = inst.defaultTeam === "none" ? "\n Default team: None (disabled)" : inst.defaultTeam ? `\n Default team: ${inst.defaultTeam.name}` : "";
|
|
3001
3036
|
text += `- **${inst.name}**${isDefault}: ${inst.baseUrl} (${inst.email})${projs}${team}\n`;
|
|
3037
|
+
if (!inst.defaultTeam) missingTeam.push(inst);
|
|
3038
|
+
}
|
|
3039
|
+
if (missingTeam.length > 0) {
|
|
3040
|
+
const names = missingTeam.map((i) => `"${i.name}"`).join(", ");
|
|
3041
|
+
text += `\n⚠ Instances without a default team: ${names}.\nAsk the user if they want to configure a default team. To set one, call jira_add_instance with name="<instance>" and defaultTeam="<team name>".`;
|
|
3002
3042
|
}
|
|
3003
3043
|
return { content: [{ type: "text", text }] };
|
|
3004
3044
|
|
|
@@ -3045,17 +3085,15 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
3045
3085
|
let teamPrompt = "";
|
|
3046
3086
|
if (args.team) {
|
|
3047
3087
|
fields.customfield_10001 = await resolveTeamId(args.team, inst);
|
|
3048
|
-
} else if (inst.defaultTeam) {
|
|
3088
|
+
} else if (inst.defaultTeam && inst.defaultTeam !== "none") {
|
|
3049
3089
|
fields.customfield_10001 = inst.defaultTeam.id;
|
|
3050
|
-
} else {
|
|
3090
|
+
} else if (!inst.defaultTeam) {
|
|
3051
3091
|
// No team param and no default — fetch available teams and prompt user
|
|
3052
3092
|
try {
|
|
3053
|
-
const teams = await
|
|
3054
|
-
`/teams/find?query=&excludeMembers=true`, {}, inst,
|
|
3055
|
-
);
|
|
3093
|
+
const teams = await listTeams(inst);
|
|
3056
3094
|
if (teams && teams.length > 0) {
|
|
3057
3095
|
const list = teams.map((t, i) => `${i + 1}. ${t.title}`).join("\n");
|
|
3058
|
-
teamPrompt = `\n\n⚠ No team assigned and no default team configured for instance "${inst.name}". Available teams:\n${list}\n0. None\n\nTo assign a team to this ticket, call jira_update_ticket with issueKey and team parameter.\nIf a team is selected, ask the user if it should also be saved as the default team for instance "${inst.name}" (via jira_add_instance with defaultTeam).`;
|
|
3096
|
+
teamPrompt = `\n\n⚠ No team assigned and no default team configured for instance "${inst.name}". Available teams:\n${list}\n0. None\n\nTo assign a team to this ticket, call jira_update_ticket with issueKey and team parameter.\nIf a team is selected, ask the user if it should also be saved as the default team for instance "${inst.name}" (via jira_add_instance with defaultTeam). If "None" is selected, call jira_add_instance with defaultTeam="none" to stop future prompts.`;
|
|
3059
3097
|
}
|
|
3060
3098
|
} catch {
|
|
3061
3099
|
// Teams API not available, proceed without team
|
|
@@ -3118,7 +3156,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
3118
3156
|
);
|
|
3119
3157
|
if (parentIssue.fields?.customfield_10001) {
|
|
3120
3158
|
fields.customfield_10001 = parentIssue.fields.customfield_10001;
|
|
3121
|
-
} else if (inst.defaultTeam) {
|
|
3159
|
+
} else if (inst.defaultTeam && inst.defaultTeam !== "none") {
|
|
3122
3160
|
teamWarning = `\n\nNote: Parent ${args.parentKey} has no team assigned. Instance default team is "${inst.defaultTeam.name}". Use team parameter to assign it.`;
|
|
3123
3161
|
}
|
|
3124
3162
|
}
|
|
@@ -3469,16 +3507,14 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
3469
3507
|
let teamPrompt = "";
|
|
3470
3508
|
if (of.customfield_10001) {
|
|
3471
3509
|
fields.customfield_10001 = of.customfield_10001;
|
|
3472
|
-
} else if (inst.defaultTeam) {
|
|
3510
|
+
} else if (inst.defaultTeam && inst.defaultTeam !== "none") {
|
|
3473
3511
|
fields.customfield_10001 = inst.defaultTeam.id;
|
|
3474
|
-
} else {
|
|
3512
|
+
} else if (!inst.defaultTeam) {
|
|
3475
3513
|
try {
|
|
3476
|
-
const teams = await
|
|
3477
|
-
`/teams/find?query=&excludeMembers=true`, {}, inst,
|
|
3478
|
-
);
|
|
3514
|
+
const teams = await listTeams(inst);
|
|
3479
3515
|
if (teams && teams.length > 0) {
|
|
3480
3516
|
const list = teams.map((t, i) => `${i + 1}. ${t.title}`).join("\n");
|
|
3481
|
-
teamPrompt = `\n\n⚠ No team assigned (original had none) and no default team configured for instance "${inst.name}". Available teams:\n${list}\n0. None\n\nTo assign a team to this ticket, call jira_update_ticket with issueKey and team parameter.\nIf a team is selected, ask the user if it should also be saved as the default team for instance "${inst.name}" (via jira_add_instance with defaultTeam).`;
|
|
3517
|
+
teamPrompt = `\n\n⚠ No team assigned (original had none) and no default team configured for instance "${inst.name}". Available teams:\n${list}\n0. None\n\nTo assign a team to this ticket, call jira_update_ticket with issueKey and team parameter.\nIf a team is selected, ask the user if it should also be saved as the default team for instance "${inst.name}" (via jira_add_instance with defaultTeam). If "None" is selected, call jira_add_instance with defaultTeam="none" to stop future prompts.`;
|
|
3482
3518
|
}
|
|
3483
3519
|
} catch {
|
|
3484
3520
|
// Teams API not available, proceed without team
|
|
@@ -3694,5 +3730,5 @@ if (require.main === module) {
|
|
|
3694
3730
|
|
|
3695
3731
|
// Export for testing
|
|
3696
3732
|
if (typeof module !== "undefined") {
|
|
3697
|
-
module.exports = { buildCommentADF, parseInlineFormatting, findJiraTicketKeys, resolveTeamId, fetchJiraTeams };
|
|
3733
|
+
module.exports = { buildCommentADF, parseInlineFormatting, findJiraTicketKeys, resolveTeamId, fetchJiraTeams, listTeams, searchTeamsViaJql };
|
|
3698
3734
|
}
|
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.18",
|
|
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": {
|