@abdelrahmanhsn/jira-mcp 1.2.0 → 1.3.0
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/README.md +2 -0
- package/package.json +1 -1
- package/server.js +78 -0
package/README.md
CHANGED
|
@@ -13,6 +13,7 @@ A [Model Context Protocol (MCP)](https://modelcontextprotocol.io) server that co
|
|
|
13
13
|
| `get_my_standup` | Get a standup summary of tickets you updated since yesterday |
|
|
14
14
|
| `get_sprint_summary` | Get all sprint tickets grouped by status (Todo / In Progress / Done) |
|
|
15
15
|
| `search_tickets` | Search tickets with plain English or raw JQL |
|
|
16
|
+
| `get_context_for_pr` | Extract Jira ticket from a branch name and return a ready-to-use PR description block |
|
|
16
17
|
|
|
17
18
|
## Prerequisites
|
|
18
19
|
|
|
@@ -124,6 +125,7 @@ Once configured, you can ask your AI assistant:
|
|
|
124
125
|
- *"Give me my standup for today"*
|
|
125
126
|
- *"Summarize the active sprint — how many tickets are done vs in progress?"*
|
|
126
127
|
- *"Search for open bugs related to login"*
|
|
128
|
+
- *"Get PR context for branch STUD-17891-add-email-icon"*
|
|
127
129
|
|
|
128
130
|
## Security
|
|
129
131
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@abdelrahmanhsn/jira-mcp",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.3.0",
|
|
5
5
|
"description": "MCP server for Jira — query your tickets, active sprints, and issue details from any AI IDE (GitHub Copilot, Cursor, Claude Desktop)",
|
|
6
6
|
"main": "server.js",
|
|
7
7
|
"bin": {
|
package/server.js
CHANGED
|
@@ -191,6 +191,84 @@ server.tool(
|
|
|
191
191
|
}
|
|
192
192
|
);
|
|
193
193
|
|
|
194
|
+
// Tool: get_context_for_pr
|
|
195
|
+
server.tool(
|
|
196
|
+
"get_context_for_pr",
|
|
197
|
+
"Extract the Jira ticket key from a branch name, fetch its details and comments, and return a structured PR context block ready for AI to write a pull request description",
|
|
198
|
+
{
|
|
199
|
+
branch: z.string().describe("Git branch name, e.g. STUD-17891-add-email-icon or feature/STUD-17891"),
|
|
200
|
+
},
|
|
201
|
+
async ({ branch }) => {
|
|
202
|
+
// Extract issue key (e.g. STUD-17891) from anywhere in the branch name
|
|
203
|
+
const match = branch.match(/([A-Z][A-Z0-9]+-\d+)/i);
|
|
204
|
+
if (!match) {
|
|
205
|
+
return {
|
|
206
|
+
content: [{
|
|
207
|
+
type: "text",
|
|
208
|
+
text: `Could not find a Jira issue key in branch name: "${branch}". Expected format: PROJ-123 anywhere in the branch.`,
|
|
209
|
+
}],
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const issueKey = match[1].toUpperCase();
|
|
214
|
+
|
|
215
|
+
const [issueRes, commentsRes] = await Promise.all([
|
|
216
|
+
jiraClient.get(`/issue/${issueKey}`, {
|
|
217
|
+
params: { fields: "summary,description,issuetype,priority,status,assignee,reporter,labels,components" },
|
|
218
|
+
}),
|
|
219
|
+
jiraClient.get(`/issue/${issueKey}/comment`, {
|
|
220
|
+
params: { maxResults: 10, orderBy: "-created" },
|
|
221
|
+
}),
|
|
222
|
+
]);
|
|
223
|
+
|
|
224
|
+
const f = issueRes.data.fields;
|
|
225
|
+
|
|
226
|
+
// Extract plain text from Atlassian Document Format description
|
|
227
|
+
function extractText(node) {
|
|
228
|
+
if (!node) return "";
|
|
229
|
+
if (node.type === "text") return node.text || "";
|
|
230
|
+
if (node.content) return node.content.map(extractText).join(" ");
|
|
231
|
+
return "";
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const description = extractText(f.description).trim() || "No description provided.";
|
|
235
|
+
|
|
236
|
+
const comments = (commentsRes.data.comments || []).map(c => ({
|
|
237
|
+
author: c.author?.displayName,
|
|
238
|
+
body: extractText(c.body).trim(),
|
|
239
|
+
created: c.created?.split("T")[0],
|
|
240
|
+
})).filter(c => c.body);
|
|
241
|
+
|
|
242
|
+
const context = {
|
|
243
|
+
issue_key: issueKey,
|
|
244
|
+
summary: f.summary,
|
|
245
|
+
type: f.issuetype?.name,
|
|
246
|
+
priority: f.priority?.name,
|
|
247
|
+
status: f.status?.name,
|
|
248
|
+
assignee: f.assignee?.displayName ?? "Unassigned",
|
|
249
|
+
reporter: f.reporter?.displayName,
|
|
250
|
+
labels: f.labels ?? [],
|
|
251
|
+
components: (f.components ?? []).map(c => c.name),
|
|
252
|
+
description,
|
|
253
|
+
recent_comments: comments,
|
|
254
|
+
pr_context: [
|
|
255
|
+
`## ${issueKey}: ${f.summary}`,
|
|
256
|
+
``,
|
|
257
|
+
`**Type:** ${f.issuetype?.name} | **Priority:** ${f.priority?.name} | **Status:** ${f.status?.name}`,
|
|
258
|
+
``,
|
|
259
|
+
`### What this PR does`,
|
|
260
|
+
description,
|
|
261
|
+
comments.length > 0 ? `\n### Discussion context\n${comments.map(c => `- **${c.author}** (${c.created}): ${c.body}`).join("\n")}` : "",
|
|
262
|
+
``,
|
|
263
|
+
`### Jira ticket`,
|
|
264
|
+
`https://${JIRA_DOMAIN}/browse/${issueKey}`,
|
|
265
|
+
].filter(Boolean).join("\n"),
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
return { content: [{ type: "text", text: JSON.stringify(context, null, 2) }] };
|
|
269
|
+
}
|
|
270
|
+
);
|
|
271
|
+
|
|
194
272
|
// ── Start ────────────────────────────────────────────────────────────────────
|
|
195
273
|
const transport = new StdioServerTransport();
|
|
196
274
|
await server.connect(transport);
|