@aaronsb/jira-cloud-mcp 0.4.1 → 0.4.3

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.
@@ -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 10)",
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 = 10;
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) {
@@ -19,12 +19,18 @@
19
19
  function formatDate(dateStr) {
20
20
  if (!dateStr)
21
21
  return 'Not set';
22
+ // Date-only strings (YYYY-MM-DD) are parsed as UTC midnight by Date constructor,
23
+ // then toLocaleDateString shifts them by local TZ offset — causing off-by-one.
24
+ // Parse date-only values directly to avoid timezone shifting.
25
+ const dateOnly = dateStr.match(/^(\d{4})-(\d{2})-(\d{2})$/);
26
+ if (dateOnly) {
27
+ const [, y, m, d] = dateOnly;
28
+ const date = new Date(Number(y), Number(m) - 1, Number(d));
29
+ return date.toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric' });
30
+ }
31
+ // Full datetime strings include timezone info, so they render correctly.
22
32
  const date = new Date(dateStr);
23
- return date.toLocaleDateString('en-US', {
24
- year: 'numeric',
25
- month: 'short',
26
- day: 'numeric'
27
- });
33
+ return date.toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric' });
28
34
  }
29
35
  /**
30
36
  * Format status with visual indicator
@@ -78,7 +84,7 @@ export function renderIssue(issue, transitions) {
78
84
  lines.push(`# ${issue.key}: ${issue.summary}`);
79
85
  lines.push('');
80
86
  // Core fields
81
- lines.push(`**Status:** ${formatStatus(issue.status)}`);
87
+ lines.push(`**Type:** ${issue.issueType} | **Status:** ${formatStatus(issue.status)}${issue.priority ? ` | **Priority:** ${issue.priority}` : ''}`);
82
88
  if (issue.assignee) {
83
89
  lines.push(`**Assignee:** ${issue.assignee}`);
84
90
  }
@@ -89,12 +95,39 @@ export function renderIssue(issue, transitions) {
89
95
  if (issue.parent) {
90
96
  lines.push(`**Parent:** ${issue.parent}`);
91
97
  }
92
- if (issue.dueDate) {
93
- lines.push(`**Due:** ${formatDate(issue.dueDate)}`);
94
- }
98
+ if (issue.labels && issue.labels.length > 0) {
99
+ lines.push(`**Labels:** ${issue.labels.join(', ')}`);
100
+ }
101
+ // Dates
102
+ const dates = [];
103
+ if (issue.created)
104
+ dates.push(`Created ${formatDate(issue.created)}`);
105
+ if (issue.updated)
106
+ dates.push(`Updated ${formatDate(issue.updated)}`);
107
+ if (dates.length > 0)
108
+ lines.push(`**${dates.join(' | ')}**`);
109
+ const scheduleDates = [];
110
+ if (issue.startDate)
111
+ scheduleDates.push(`Start: ${formatDate(issue.startDate)}`);
112
+ if (issue.dueDate)
113
+ scheduleDates.push(`Due: ${formatDate(issue.dueDate)}`);
114
+ if (issue.resolutionDate)
115
+ scheduleDates.push(`Resolved: ${formatDate(issue.resolutionDate)}`);
116
+ if (scheduleDates.length > 0)
117
+ lines.push(`**${scheduleDates.join(' | ')}**`);
95
118
  if (issue.storyPoints) {
96
119
  lines.push(`**Points:** ${issue.storyPoints}`);
97
120
  }
121
+ if (issue.timeEstimate) {
122
+ const hours = Math.floor(issue.timeEstimate / 3600);
123
+ const minutes = Math.floor((issue.timeEstimate % 3600) / 60);
124
+ const parts = [];
125
+ if (hours > 0)
126
+ parts.push(`${hours}h`);
127
+ if (minutes > 0)
128
+ parts.push(`${minutes}m`);
129
+ lines.push(`**Estimate:** ${parts.length > 0 ? parts.join(' ') : '0m'}`);
130
+ }
98
131
  if (issue.resolution) {
99
132
  lines.push(`**Resolution:** ${issue.resolution}`);
100
133
  }
@@ -183,10 +216,17 @@ export function renderIssueSearchResults(issues, pagination, jql) {
183
216
  const issue = issues[i];
184
217
  const num = pagination.startAt + i + 1;
185
218
  lines.push(`## ${num}. ${issue.key}: ${issue.summary}`);
186
- lines.push(`${formatStatus(issue.status)} | ${issue.assignee || 'Unassigned'}`);
187
- if (issue.dueDate) {
188
- lines.push(`Due: ${formatDate(issue.dueDate)}`);
189
- }
219
+ const meta = [`${formatStatus(issue.status)}`, issue.assignee || 'Unassigned'];
220
+ if (issue.priority)
221
+ meta.push(issue.priority);
222
+ lines.push(meta.join(' | '));
223
+ const searchDates = [];
224
+ if (issue.dueDate)
225
+ searchDates.push(`Due: ${formatDate(issue.dueDate)}`);
226
+ if (issue.startDate)
227
+ searchDates.push(`Start: ${formatDate(issue.startDate)}`);
228
+ if (searchDates.length > 0)
229
+ lines.push(searchDates.join(' | '));
190
230
  if (issue.description) {
191
231
  const desc = stripHtml(issue.description);
192
232
  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,8 @@ export const toolSchemas = {
349
353
  },
350
354
  required: ['tool', 'args'],
351
355
  },
352
- description: 'Ordered list of operations to execute (max 10).',
353
- maxItems: 10,
356
+ description: 'Ordered list of operations to execute (max 16).',
357
+ maxItems: 16,
354
358
  },
355
359
  detail: {
356
360
  type: 'string',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aaronsb/jira-cloud-mcp",
3
- "version": "0.4.1",
3
+ "version": "0.4.3",
4
4
  "mcpName": "io.github.aaronsb/jira-cloud",
5
5
  "description": "Model Context Protocol (MCP) server for Jira Cloud - enables AI assistants to interact with Jira",
6
6
  "type": "module",