@rui.branco/jira-mcp 1.6.20 → 1.6.22

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 +96 -3
  2. package/package.json +1 -1
package/index.js CHANGED
@@ -1831,7 +1831,15 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
1831
1831
  },
1832
1832
  defaultTeam: {
1833
1833
  type: "string",
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.",
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. This is the instance-wide fallback.",
1835
+ },
1836
+ projectKey: {
1837
+ type: "string",
1838
+ description: "Project key to set a project-specific team for (e.g., 'FRFSD'). Must be used together with projectTeam.",
1839
+ },
1840
+ projectTeam: {
1841
+ type: "string",
1842
+ description: "Team name for the specific project (set via projectKey). Overrides the instance defaultTeam for that project. Pass 'none' to disable team for this project. Pass empty string to remove the override.",
1835
1843
  },
1836
1844
  },
1837
1845
  required: ["name"],
@@ -1862,6 +1870,25 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
1862
1870
  required: [],
1863
1871
  },
1864
1872
  },
1873
+ {
1874
+ name: "jira_get_teams",
1875
+ description:
1876
+ "Search for available Jira teams by name. Returns matching teams with their IDs. Use this to find a team before setting it as default or assigning to a ticket. IMPORTANT: You MUST display the results in your chat response so the user can see them.",
1877
+ inputSchema: {
1878
+ type: "object",
1879
+ properties: {
1880
+ query: {
1881
+ type: "string",
1882
+ description: "Search term to filter teams (e.g., 'site', 'mods'). Leave empty to list all available teams (may be partial).",
1883
+ },
1884
+ instance: {
1885
+ type: "string",
1886
+ description: "Instance name. Uses default instance if omitted.",
1887
+ },
1888
+ },
1889
+ required: [],
1890
+ },
1891
+ },
1865
1892
  {
1866
1893
  name: "jira_create_ticket",
1867
1894
  description:
@@ -2922,7 +2949,29 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
2922
2949
  }
2923
2950
  }
2924
2951
 
2925
- const newInstance = { name: instName, email, token, baseUrl, projects, auth: authStr, defaultTeam };
2952
+ // Resolve projectTeam if provided
2953
+ let projectTeams = existing.projectTeams || {};
2954
+ if (args.projectKey && args.projectTeam !== undefined) {
2955
+ const pk = args.projectKey.toUpperCase();
2956
+ if (args.projectTeam === "") {
2957
+ delete projectTeams[pk];
2958
+ } else if (args.projectTeam.toLowerCase() === "none") {
2959
+ projectTeams[pk] = "none";
2960
+ } else {
2961
+ const tempInst = { baseUrl, auth: authStr };
2962
+ try {
2963
+ const teamId = await resolveTeamId(args.projectTeam, tempInst);
2964
+ projectTeams[pk] = { name: args.projectTeam, id: teamId };
2965
+ } catch (e) {
2966
+ return {
2967
+ content: [{ type: "text", text: e.message }],
2968
+ isError: true,
2969
+ };
2970
+ }
2971
+ }
2972
+ }
2973
+
2974
+ const newInstance = { name: instName, email, token, baseUrl, projects, auth: authStr, defaultTeam, projectTeams };
2926
2975
 
2927
2976
  // Update in-memory instances
2928
2977
  if (isUpdate) {
@@ -2955,6 +3004,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
2955
3004
  // Save without the computed auth field
2956
3005
  const toSave = { name: instName, email, token, baseUrl, projects };
2957
3006
  if (defaultTeam) toSave.defaultTeam = defaultTeam;
3007
+ if (Object.keys(projectTeams).length > 0) toSave.projectTeams = projectTeams;
2958
3008
  const savedIdx = savedConfig.instances.findIndex((i) => i.name === instName);
2959
3009
  if (savedIdx >= 0) {
2960
3010
  savedConfig.instances[savedIdx] = toSave;
@@ -2970,6 +3020,17 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
2970
3020
  let text = `${action} instance "${instName}" (${baseUrl}).`;
2971
3021
  if (projects.length > 0) text += ` Projects: ${projects.join(", ")}.`;
2972
3022
  if (args.setDefault) text += " Set as default.";
3023
+ if (args.projectKey && args.projectTeam !== undefined) {
3024
+ const pk = args.projectKey.toUpperCase();
3025
+ const pt = projectTeams[pk];
3026
+ if (pt === "none") {
3027
+ text += ` Project ${pk} team: None (disabled).`;
3028
+ } else if (pt) {
3029
+ text += ` Project ${pk} team: ${pt.name}.`;
3030
+ } else {
3031
+ text += ` Project ${pk} team override removed.`;
3032
+ }
3033
+ }
2973
3034
  if (defaultTeam === "none") {
2974
3035
  text += " Default team: None (disabled).";
2975
3036
  } else if (defaultTeam) {
@@ -3033,7 +3094,14 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
3033
3094
  const isDefault = inst.name === currentDefault ? " **(default)**" : "";
3034
3095
  const projs = inst.projects?.length > 0 ? `\n Projects: ${inst.projects.join(", ")}` : "";
3035
3096
  const team = inst.defaultTeam === "none" ? "\n Default team: None (disabled)" : inst.defaultTeam ? `\n Default team: ${inst.defaultTeam.name}` : "";
3036
- text += `- **${inst.name}**${isDefault}: ${inst.baseUrl} (${inst.email})${projs}${team}\n`;
3097
+ let projTeams = "";
3098
+ if (inst.projectTeams && Object.keys(inst.projectTeams).length > 0) {
3099
+ for (const [pk, pt] of Object.entries(inst.projectTeams)) {
3100
+ const ptName = pt === "none" ? "None (disabled)" : pt.name;
3101
+ projTeams += `\n ${pk} team: ${ptName}`;
3102
+ }
3103
+ }
3104
+ text += `- **${inst.name}**${isDefault}: ${inst.baseUrl} (${inst.email})${projs}${team}${projTeams}\n`;
3037
3105
  if (!inst.defaultTeam) missingTeam.push(inst);
3038
3106
  }
3039
3107
  if (missingTeam.length > 0) {
@@ -3042,6 +3110,19 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
3042
3110
  }
3043
3111
  return { content: [{ type: "text", text }] };
3044
3112
 
3113
+ } else if (name === "jira_get_teams") {
3114
+ const inst = args.instance ? getInstanceByName(args.instance) : defaultInstance;
3115
+ const query = args.query || "";
3116
+ const teams = await searchTeamsViaJql(query, inst);
3117
+ if (teams.length === 0) {
3118
+ return { content: [{ type: "text", text: `No teams found${query ? ` matching "${query}"` : ""}.` }] };
3119
+ }
3120
+ teams.sort((a, b) => a.title.localeCompare(b.title));
3121
+ const list = teams.map((t, i) => `${i + 1}. ${t.title}`).join("\n");
3122
+ return {
3123
+ content: [{ type: "text", text: `Found ${teams.length} team(s)${query ? ` matching "${query}"` : ""}:\n\n0. None\n${list}\n\nIMPORTANT: Display this list in chat so the user can see it. To set as default team, call jira_add_instance with defaultTeam="<team name>". To assign to a ticket, use team parameter on jira_create_ticket or jira_update_ticket.` }],
3124
+ };
3125
+
3045
3126
  } else if (name === "jira_create_ticket") {
3046
3127
  const inst = args.instance
3047
3128
  ? getInstanceByName(args.instance)
@@ -3083,8 +3164,15 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
3083
3164
  fields.parent = { key: args.parentKey };
3084
3165
  }
3085
3166
  let teamPrompt = "";
3167
+ const projKey = args.projectKey.toUpperCase();
3168
+ const projTeam = inst.projectTeams?.[projKey];
3086
3169
  if (args.team) {
3087
3170
  fields.customfield_10001 = await resolveTeamId(args.team, inst);
3171
+ } else if (projTeam && projTeam !== "none") {
3172
+ // Project-specific team override
3173
+ fields.customfield_10001 = projTeam.id;
3174
+ } else if (projTeam === "none") {
3175
+ // Project explicitly set to no team — skip silently
3088
3176
  } else if (inst.defaultTeam && inst.defaultTeam !== "none") {
3089
3177
  fields.customfield_10001 = inst.defaultTeam.id;
3090
3178
  } else if (!inst.defaultTeam) {
@@ -3505,8 +3593,13 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
3505
3593
  fields.components = of.components.map((c) => ({ name: c.name }));
3506
3594
  }
3507
3595
  let teamPrompt = "";
3596
+ const cloneProjTeam = inst.projectTeams?.[targetProject?.toUpperCase()];
3508
3597
  if (of.customfield_10001) {
3509
3598
  fields.customfield_10001 = of.customfield_10001;
3599
+ } else if (cloneProjTeam && cloneProjTeam !== "none") {
3600
+ fields.customfield_10001 = cloneProjTeam.id;
3601
+ } else if (cloneProjTeam === "none") {
3602
+ // Project explicitly set to no team
3510
3603
  } else if (inst.defaultTeam && inst.defaultTeam !== "none") {
3511
3604
  fields.customfield_10001 = inst.defaultTeam.id;
3512
3605
  } else if (!inst.defaultTeam) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rui.branco/jira-mcp",
3
- "version": "1.6.20",
3
+ "version": "1.6.22",
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": {