@aaronsb/jira-cloud-mcp 0.4.0 → 0.4.2
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/build/client/jira-client.js +16 -0
- package/build/docs/tool-documentation.js +1 -1
- package/build/handlers/issue-handlers.js +4 -1
- package/build/handlers/queue-handler.js +28 -9
- package/build/mcp/markdown-renderer.js +42 -8
- package/build/schemas/tool-schemas.js +12 -2
- package/package.json +1 -1
|
@@ -69,11 +69,17 @@ export class JiraClient {
|
|
|
69
69
|
return [
|
|
70
70
|
'summary',
|
|
71
71
|
'description',
|
|
72
|
+
'issuetype',
|
|
73
|
+
'priority',
|
|
72
74
|
'parent',
|
|
73
75
|
'assignee',
|
|
74
76
|
'reporter',
|
|
75
77
|
'status',
|
|
76
78
|
'resolution',
|
|
79
|
+
'labels',
|
|
80
|
+
'created',
|
|
81
|
+
'updated',
|
|
82
|
+
'resolutiondate',
|
|
77
83
|
'duedate',
|
|
78
84
|
this.customFields.startDate,
|
|
79
85
|
this.customFields.storyPoints,
|
|
@@ -88,11 +94,17 @@ export class JiraClient {
|
|
|
88
94
|
key: issue.key,
|
|
89
95
|
summary: fields?.summary,
|
|
90
96
|
description: issue.renderedFields?.description || '',
|
|
97
|
+
issueType: fields?.issuetype?.name || '',
|
|
98
|
+
priority: fields?.priority?.name || null,
|
|
91
99
|
parent: fields?.parent?.key || null,
|
|
92
100
|
assignee: fields?.assignee?.displayName || null,
|
|
93
101
|
reporter: fields?.reporter?.displayName || '',
|
|
94
102
|
status: fields?.status?.name || '',
|
|
95
103
|
resolution: fields?.resolution?.name || null,
|
|
104
|
+
labels: fields?.labels || [],
|
|
105
|
+
created: fields?.created || '',
|
|
106
|
+
updated: fields?.updated || '',
|
|
107
|
+
resolutionDate: fields?.resolutiondate || null,
|
|
96
108
|
dueDate: fields?.duedate || null,
|
|
97
109
|
startDate: fields?.[this.customFields.startDate] || null,
|
|
98
110
|
storyPoints: fields?.[this.customFields.storyPoints] || null,
|
|
@@ -378,6 +390,8 @@ export class JiraClient {
|
|
|
378
390
|
fields.priority = { id: params.priority };
|
|
379
391
|
if (params.labels)
|
|
380
392
|
fields.labels = params.labels;
|
|
393
|
+
if (params.dueDate !== undefined)
|
|
394
|
+
fields.duedate = params.dueDate;
|
|
381
395
|
if (params.customFields) {
|
|
382
396
|
Object.assign(fields, this.convertAdfFields(params.customFields));
|
|
383
397
|
}
|
|
@@ -877,6 +891,8 @@ export class JiraClient {
|
|
|
877
891
|
fields.assignee = { accountId: params.assignee };
|
|
878
892
|
if (params.labels)
|
|
879
893
|
fields.labels = params.labels;
|
|
894
|
+
if (params.dueDate)
|
|
895
|
+
fields.duedate = params.dueDate;
|
|
880
896
|
if (params.customFields) {
|
|
881
897
|
Object.assign(fields, this.convertAdfFields(params.customFields));
|
|
882
898
|
}
|
|
@@ -401,7 +401,7 @@ function generateQueueToolDocumentation(_schema) {
|
|
|
401
401
|
description: "Execute multiple Jira operations in a single call. Operations run sequentially with result references and per-operation error strategies.",
|
|
402
402
|
parameters: {
|
|
403
403
|
operations: {
|
|
404
|
-
type: "array (max
|
|
404
|
+
type: "array (max 16)",
|
|
405
405
|
description: "Ordered list of operations. Each has: tool (string), args (object), onError ('bail' | 'continue', default 'bail').",
|
|
406
406
|
},
|
|
407
407
|
},
|
|
@@ -61,8 +61,9 @@ function validateManageJiraIssueArgs(args) {
|
|
|
61
61
|
normalizedArgs.assignee === undefined &&
|
|
62
62
|
normalizedArgs.priority === undefined &&
|
|
63
63
|
normalizedArgs.labels === undefined &&
|
|
64
|
+
normalizedArgs.dueDate === undefined &&
|
|
64
65
|
normalizedArgs.customFields === undefined) {
|
|
65
|
-
throw new McpError(ErrorCode.InvalidParams, 'At least one update field (summary, description, parent, assignee, priority, labels, or customFields) must be provided for the update operation.');
|
|
66
|
+
throw new McpError(ErrorCode.InvalidParams, 'At least one update field (summary, description, parent, assignee, priority, labels, dueDate, or customFields) must be provided for the update operation.');
|
|
66
67
|
}
|
|
67
68
|
break;
|
|
68
69
|
case 'transition':
|
|
@@ -255,6 +256,7 @@ async function handleCreateIssue(jiraClient, args) {
|
|
|
255
256
|
priority: args.priority,
|
|
256
257
|
assignee: args.assignee,
|
|
257
258
|
labels: args.labels,
|
|
259
|
+
dueDate: args.dueDate ?? undefined,
|
|
258
260
|
customFields,
|
|
259
261
|
});
|
|
260
262
|
// Get the created issue and render to markdown
|
|
@@ -279,6 +281,7 @@ async function handleUpdateIssue(jiraClient, args) {
|
|
|
279
281
|
assignee: args.assignee,
|
|
280
282
|
priority: args.priority,
|
|
281
283
|
labels: args.labels,
|
|
284
|
+
dueDate: args.dueDate,
|
|
282
285
|
customFields,
|
|
283
286
|
});
|
|
284
287
|
// Get the updated issue and render to markdown
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
import { ErrorCode, McpError } from '@modelcontextprotocol/sdk/types.js';
|
|
11
11
|
import { bulkOperationGuard } from '../utils/bulk-operation-guard.js';
|
|
12
12
|
// ── Constants ──────────────────────────────────────────────────────────
|
|
13
|
-
const MAX_OPERATIONS =
|
|
13
|
+
const MAX_OPERATIONS = 16;
|
|
14
14
|
const DESTRUCTIVE_OPERATIONS = new Set(['delete', 'move']);
|
|
15
15
|
// ── Queue Handler ──────────────────────────────────────────────────────
|
|
16
16
|
export function createQueueHandler(handlers, jiraHost) {
|
|
@@ -20,6 +20,7 @@ export function createQueueHandler(handlers, jiraHost) {
|
|
|
20
20
|
throw new McpError(ErrorCode.InvalidParams, 'Missing required parameter: operations (array)');
|
|
21
21
|
}
|
|
22
22
|
const operations = args.operations;
|
|
23
|
+
const detail = args.detail === 'full' ? 'full' : 'summary';
|
|
23
24
|
if (operations.length === 0) {
|
|
24
25
|
throw new McpError(ErrorCode.InvalidParams, 'Operations list is empty.');
|
|
25
26
|
}
|
|
@@ -100,7 +101,7 @@ export function createQueueHandler(handlers, jiraHost) {
|
|
|
100
101
|
}
|
|
101
102
|
}
|
|
102
103
|
return {
|
|
103
|
-
content: [{ type: 'text', text: formatResults(results, operations.length, bailedAt) }],
|
|
104
|
+
content: [{ type: 'text', text: formatResults(results, operations.length, bailedAt, detail) }],
|
|
104
105
|
};
|
|
105
106
|
};
|
|
106
107
|
}
|
|
@@ -222,8 +223,8 @@ function firstLine(text) {
|
|
|
222
223
|
}
|
|
223
224
|
return stripped.slice(0, 100);
|
|
224
225
|
}
|
|
225
|
-
/** Format the queue results
|
|
226
|
-
function formatResults(results, total, bailedAt) {
|
|
226
|
+
/** Format the queue results */
|
|
227
|
+
function formatResults(results, total, bailedAt, detail = 'summary') {
|
|
227
228
|
const success = results.filter(r => r.status === 'success').length;
|
|
228
229
|
const errors = results.filter(r => r.status === 'error').length;
|
|
229
230
|
const skipped = results.filter(r => r.status === 'skipped').length;
|
|
@@ -233,11 +234,29 @@ function formatResults(results, total, bailedAt) {
|
|
|
233
234
|
if (bailedAt >= 0) {
|
|
234
235
|
lines.push(`Stopped at operation ${bailedAt + 1} due to error.`);
|
|
235
236
|
}
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
237
|
+
if (detail === 'full') {
|
|
238
|
+
for (const r of results) {
|
|
239
|
+
const icon = r.status === 'success' ? 'ok' : r.status === 'error' ? 'ERR' : 'SKIP';
|
|
240
|
+
lines.push('');
|
|
241
|
+
lines.push(`---`);
|
|
242
|
+
lines.push(`**[${r.index + 1}] ${icon}**`);
|
|
243
|
+
if (r.status === 'skipped') {
|
|
244
|
+
lines.push(r.text);
|
|
245
|
+
}
|
|
246
|
+
else {
|
|
247
|
+
lines.push(stripNextSteps(r.text));
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
else {
|
|
252
|
+
lines.push('');
|
|
253
|
+
for (const r of results) {
|
|
254
|
+
const icon = r.status === 'success' ? 'ok' : r.status === 'error' ? 'ERR' : 'SKIP';
|
|
255
|
+
const summary = r.status === 'skipped' ? r.text : firstLine(r.text);
|
|
256
|
+
lines.push(` [${r.index + 1}] ${icon}: ${summary}`);
|
|
257
|
+
}
|
|
258
|
+
lines.push('');
|
|
259
|
+
lines.push('_Use `detail: "full"` for complete output from each operation._');
|
|
241
260
|
}
|
|
242
261
|
// Consolidated next-step: use the last successful operation's context
|
|
243
262
|
const lastSuccess = [...results].reverse().find(r => r.status === 'success');
|
|
@@ -78,7 +78,7 @@ export function renderIssue(issue, transitions) {
|
|
|
78
78
|
lines.push(`# ${issue.key}: ${issue.summary}`);
|
|
79
79
|
lines.push('');
|
|
80
80
|
// Core fields
|
|
81
|
-
lines.push(`**Status:** ${formatStatus(issue.status)}`);
|
|
81
|
+
lines.push(`**Type:** ${issue.issueType} | **Status:** ${formatStatus(issue.status)}${issue.priority ? ` | **Priority:** ${issue.priority}` : ''}`);
|
|
82
82
|
if (issue.assignee) {
|
|
83
83
|
lines.push(`**Assignee:** ${issue.assignee}`);
|
|
84
84
|
}
|
|
@@ -89,12 +89,39 @@ export function renderIssue(issue, transitions) {
|
|
|
89
89
|
if (issue.parent) {
|
|
90
90
|
lines.push(`**Parent:** ${issue.parent}`);
|
|
91
91
|
}
|
|
92
|
-
if (issue.
|
|
93
|
-
lines.push(`**
|
|
94
|
-
}
|
|
92
|
+
if (issue.labels && issue.labels.length > 0) {
|
|
93
|
+
lines.push(`**Labels:** ${issue.labels.join(', ')}`);
|
|
94
|
+
}
|
|
95
|
+
// Dates
|
|
96
|
+
const dates = [];
|
|
97
|
+
if (issue.created)
|
|
98
|
+
dates.push(`Created ${formatDate(issue.created)}`);
|
|
99
|
+
if (issue.updated)
|
|
100
|
+
dates.push(`Updated ${formatDate(issue.updated)}`);
|
|
101
|
+
if (dates.length > 0)
|
|
102
|
+
lines.push(`**${dates.join(' | ')}**`);
|
|
103
|
+
const scheduleDates = [];
|
|
104
|
+
if (issue.startDate)
|
|
105
|
+
scheduleDates.push(`Start: ${formatDate(issue.startDate)}`);
|
|
106
|
+
if (issue.dueDate)
|
|
107
|
+
scheduleDates.push(`Due: ${formatDate(issue.dueDate)}`);
|
|
108
|
+
if (issue.resolutionDate)
|
|
109
|
+
scheduleDates.push(`Resolved: ${formatDate(issue.resolutionDate)}`);
|
|
110
|
+
if (scheduleDates.length > 0)
|
|
111
|
+
lines.push(`**${scheduleDates.join(' | ')}**`);
|
|
95
112
|
if (issue.storyPoints) {
|
|
96
113
|
lines.push(`**Points:** ${issue.storyPoints}`);
|
|
97
114
|
}
|
|
115
|
+
if (issue.timeEstimate) {
|
|
116
|
+
const hours = Math.floor(issue.timeEstimate / 3600);
|
|
117
|
+
const minutes = Math.floor((issue.timeEstimate % 3600) / 60);
|
|
118
|
+
const parts = [];
|
|
119
|
+
if (hours > 0)
|
|
120
|
+
parts.push(`${hours}h`);
|
|
121
|
+
if (minutes > 0)
|
|
122
|
+
parts.push(`${minutes}m`);
|
|
123
|
+
lines.push(`**Estimate:** ${parts.length > 0 ? parts.join(' ') : '0m'}`);
|
|
124
|
+
}
|
|
98
125
|
if (issue.resolution) {
|
|
99
126
|
lines.push(`**Resolution:** ${issue.resolution}`);
|
|
100
127
|
}
|
|
@@ -183,10 +210,17 @@ export function renderIssueSearchResults(issues, pagination, jql) {
|
|
|
183
210
|
const issue = issues[i];
|
|
184
211
|
const num = pagination.startAt + i + 1;
|
|
185
212
|
lines.push(`## ${num}. ${issue.key}: ${issue.summary}`);
|
|
186
|
-
|
|
187
|
-
if (issue.
|
|
188
|
-
|
|
189
|
-
|
|
213
|
+
const meta = [`${formatStatus(issue.status)}`, issue.assignee || 'Unassigned'];
|
|
214
|
+
if (issue.priority)
|
|
215
|
+
meta.push(issue.priority);
|
|
216
|
+
lines.push(meta.join(' | '));
|
|
217
|
+
const searchDates = [];
|
|
218
|
+
if (issue.dueDate)
|
|
219
|
+
searchDates.push(`Due: ${formatDate(issue.dueDate)}`);
|
|
220
|
+
if (issue.startDate)
|
|
221
|
+
searchDates.push(`Start: ${formatDate(issue.startDate)}`);
|
|
222
|
+
if (searchDates.length > 0)
|
|
223
|
+
lines.push(searchDates.join(' | '));
|
|
190
224
|
if (issue.description) {
|
|
191
225
|
const desc = stripHtml(issue.description);
|
|
192
226
|
if (desc.length > 0) {
|
|
@@ -188,6 +188,10 @@ export const toolSchemas = {
|
|
|
188
188
|
type: 'object',
|
|
189
189
|
description: 'Custom field values as key-value pairs.',
|
|
190
190
|
},
|
|
191
|
+
dueDate: {
|
|
192
|
+
type: ['string', 'null'],
|
|
193
|
+
description: 'Due date in ISO format (e.g., "2025-06-15") or null to clear. For create and update.',
|
|
194
|
+
},
|
|
191
195
|
parent: {
|
|
192
196
|
type: ['string', 'null'],
|
|
193
197
|
description: 'Parent issue key (e.g., PROJ-100) or null to remove.',
|
|
@@ -349,8 +353,14 @@ export const toolSchemas = {
|
|
|
349
353
|
},
|
|
350
354
|
required: ['tool', 'args'],
|
|
351
355
|
},
|
|
352
|
-
description: 'Ordered list of operations to execute (max
|
|
353
|
-
maxItems:
|
|
356
|
+
description: 'Ordered list of operations to execute (max 16).',
|
|
357
|
+
maxItems: 16,
|
|
358
|
+
},
|
|
359
|
+
detail: {
|
|
360
|
+
type: 'string',
|
|
361
|
+
enum: ['full', 'summary'],
|
|
362
|
+
description: 'Result detail level. summary (default): one-line status per operation. full: complete output matching individual tool calls. Use full when summary lacks needed detail.',
|
|
363
|
+
default: 'summary',
|
|
354
364
|
},
|
|
355
365
|
},
|
|
356
366
|
required: ['operations'],
|
package/package.json
CHANGED