@aaronsb/jira-cloud-mcp 0.7.6 → 0.8.1

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.
@@ -94,6 +94,8 @@ export class JiraClient {
94
94
  this.customFields.startDate,
95
95
  this.customFields.storyPoints,
96
96
  'timeestimate',
97
+ 'timeoriginalestimate',
98
+ 'timespent',
97
99
  ...(this.customFields.sprint ? [this.customFields.sprint] : []),
98
100
  'issuelinks',
99
101
  ];
@@ -147,6 +149,8 @@ export class JiraClient {
147
149
  startDate: fields?.[this.customFields.startDate] || null,
148
150
  storyPoints: fields?.[this.customFields.storyPoints] ?? null,
149
151
  timeEstimate: fields?.timeestimate ?? null,
152
+ originalEstimate: fields?.timeoriginalestimate ?? null,
153
+ timeSpent: fields?.timespent ?? null,
150
154
  sprint: this.customFields.sprint ? this.extractSprintName(fields?.[this.customFields.sprint]) : null,
151
155
  issueLinks: (fields?.issuelinks || []).map((link) => ({
152
156
  type: link.type?.name || '',
@@ -502,6 +506,12 @@ export class JiraClient {
502
506
  fields.labels = params.labels;
503
507
  if (params.dueDate !== undefined)
504
508
  fields.duedate = params.dueDate;
509
+ if (params.originalEstimate || params.remainingEstimate) {
510
+ fields.timetracking = {
511
+ ...(params.originalEstimate ? { originalEstimate: params.originalEstimate } : {}),
512
+ ...(params.remainingEstimate ? { remainingEstimate: params.remainingEstimate } : {}),
513
+ };
514
+ }
505
515
  if (params.customFields) {
506
516
  Object.assign(fields, this.convertAdfFields(params.customFields));
507
517
  }
@@ -510,6 +520,17 @@ export class JiraClient {
510
520
  fields,
511
521
  });
512
522
  }
523
+ async addWorklog(params) {
524
+ await this.client.issueWorklogs.addWorklog({
525
+ issueIdOrKey: params.issueKey,
526
+ timeSpent: params.timeSpent,
527
+ comment: params.comment,
528
+ started: params.started,
529
+ adjustEstimate: params.adjustEstimate ?? 'auto',
530
+ newEstimate: params.newEstimate,
531
+ reduceBy: params.reduceBy,
532
+ });
533
+ }
513
534
  async addComment(issueKey, commentBody) {
514
535
  await this.client.issueComments.addComment({
515
536
  issueIdOrKey: issueKey,
@@ -536,7 +557,8 @@ export class JiraClient {
536
557
  const leanFields = [
537
558
  'summary', 'issuetype', 'priority', 'assignee', 'reporter',
538
559
  'status', 'resolution', 'labels', 'created', 'updated',
539
- 'resolutiondate', 'statuscategorychangedate', 'duedate', 'timeestimate',
560
+ 'resolutiondate', 'statuscategorychangedate', 'duedate',
561
+ 'timeestimate', 'timeoriginalestimate', 'timespent',
540
562
  this.customFields.startDate, this.customFields.storyPoints,
541
563
  ...(this.customFields.sprint ? [this.customFields.sprint] : []),
542
564
  ];
@@ -1057,6 +1079,9 @@ export class JiraClient {
1057
1079
  fields.labels = params.labels;
1058
1080
  if (params.dueDate)
1059
1081
  fields.duedate = params.dueDate;
1082
+ if (params.originalEstimate) {
1083
+ fields.timetracking = { originalEstimate: params.originalEstimate };
1084
+ }
1060
1085
  if (params.customFields) {
1061
1086
  Object.assign(fields, this.convertAdfFields(params.customFields));
1062
1087
  }
@@ -68,18 +68,34 @@ function generateIssueToolDocumentation(schema) {
68
68
  update: {
69
69
  description: "Updates an existing issue",
70
70
  required_parameters: ["issueKey"],
71
- optional_parameters: ["summary", "description", "assignee", "priority", "labels", "customFields"],
71
+ optional_parameters: ["summary", "description", "assignee", "priority", "labels", "originalEstimate", "remainingEstimate", "customFields"],
72
72
  examples: [
73
73
  {
74
74
  description: "Update issue summary",
75
75
  code: { operation: "update", issueKey: "PROJ-123", summary: "Updated feature request" }
76
76
  },
77
77
  {
78
- description: "Add worklog to issue",
79
- code: {
80
- operation: "update", issueKey: "PROJ-123",
81
- customFields: { "worklog": { "timeSpent": "3h 30m", "comment": "Implemented feature X", "started": "2025-04-09T09:00:00.000Z" } }
82
- }
78
+ description: "Set time estimate on an issue",
79
+ code: { operation: "update", issueKey: "PROJ-123", originalEstimate: "3d", remainingEstimate: "2d" }
80
+ }
81
+ ]
82
+ },
83
+ worklog: {
84
+ description: "Logs time spent on an issue",
85
+ required_parameters: ["issueKey", "timeSpent"],
86
+ optional_parameters: ["worklogComment", "started", "adjustEstimate", "newEstimate", "reduceBy"],
87
+ examples: [
88
+ {
89
+ description: "Log 3.5 hours of work",
90
+ code: { operation: "worklog", issueKey: "PROJ-123", timeSpent: "3h 30m", worklogComment: "Implemented feature X" }
91
+ },
92
+ {
93
+ description: "Log work with specific start time",
94
+ code: { operation: "worklog", issueKey: "PROJ-123", timeSpent: "1d", started: "2025-04-09T09:00:00.000+0000", worklogComment: "Design review" }
95
+ },
96
+ {
97
+ description: "Log work without adjusting estimate",
98
+ code: { operation: "worklog", issueKey: "PROJ-123", timeSpent: "2h", adjustEstimate: "leave" }
83
99
  }
84
100
  ]
85
101
  },
@@ -122,11 +138,13 @@ function generateIssueToolDocumentation(schema) {
122
138
  },
123
139
  common_use_cases: [
124
140
  {
125
- title: "Tracking work logs",
126
- description: "To track time spent on issues:",
141
+ title: "Time tracking",
142
+ description: "To estimate and log time on issues:",
127
143
  steps: [
128
- { description: "Add a work log to an issue", code: { operation: "update", issueKey: "PROJ-123", customFields: { "worklog": { "timeSpent": "3h 30m", "comment": "Implemented feature X", "started": "2025-04-09T09:00:00.000Z" } } } },
129
- { description: "View work logs for an issue", code: { operation: "get", issueKey: "PROJ-123", expand: ["worklog"] } }
144
+ { description: "Set an estimate when creating", code: { operation: "create", projectKey: "PROJ", summary: "New task", issueType: "Task", originalEstimate: "3d" } },
145
+ { description: "Update the estimate", code: { operation: "update", issueKey: "PROJ-123", originalEstimate: "5d", remainingEstimate: "3d" } },
146
+ { description: "Log time spent", code: { operation: "worklog", issueKey: "PROJ-123", timeSpent: "3h 30m", worklogComment: "Implemented feature X" } },
147
+ { description: "View time tracking on an issue", code: { operation: "get", issueKey: "PROJ-123" } }
130
148
  ]
131
149
  },
132
150
  {
@@ -1,6 +1,7 @@
1
1
  import { ErrorCode, McpError } from '@modelcontextprotocol/sdk/types.js';
2
2
  import { GraphQLHierarchyWalker, walkTree } from '../client/graphql-hierarchy.js';
3
3
  import { renderRollupTree } from '../handlers/plan-handler.js';
4
+ import { formatDuration } from '../mcp/markdown-renderer.js';
4
5
  import { evaluateRow, extractColumnRefs, parseComputeList } from '../utils/cube-dsl.js';
5
6
  import { analysisNextSteps } from '../utils/next-steps.js';
6
7
  import { normalizeArgs } from '../utils/normalize-args.js';
@@ -39,18 +40,6 @@ function formatDateShort(date) {
39
40
  function daysBetween(a, b) {
40
41
  return Math.round((b.getTime() - a.getTime()) / (1000 * 60 * 60 * 24));
41
42
  }
42
- function formatDuration(seconds) {
43
- const hours = Math.floor(seconds / 3600);
44
- const days = Math.floor(hours / 8); // 8-hour work day
45
- if (days > 0) {
46
- const remainingHours = hours % 8;
47
- return remainingHours > 0 ? `${days}d ${remainingHours}h` : `${days}d`;
48
- }
49
- const minutes = Math.floor((seconds % 3600) / 60);
50
- if (hours > 0)
51
- return minutes > 0 ? `${hours}h ${minutes}m` : `${hours}h`;
52
- return `${minutes}m`;
53
- }
54
43
  function countBy(items, keyFn) {
55
44
  const counts = new Map();
56
45
  for (const item of items) {
@@ -114,22 +103,26 @@ export function renderPoints(issues) {
114
103
  return lines.join('\n');
115
104
  }
116
105
  export function renderTime(issues) {
117
- const estimated = issues.filter(i => i.timeEstimate != null);
118
- const unestimated = issues.length - estimated.length;
119
- const total = sumBy(issues, i => i.timeEstimate);
120
- const byBucket = new Map();
121
- for (const issue of issues) {
122
- const bucket = bucketStatus(issue.statusCategory);
123
- byBucket.set(bucket, (byBucket.get(bucket) ?? 0) + (issue.timeEstimate ?? 0));
124
- }
125
- const done = byBucket.get('Done') ?? 0;
126
- const remaining = total - done;
106
+ const hasOriginal = issues.some(i => i.originalEstimate != null);
107
+ const hasLogged = issues.some(i => i.timeSpent != null);
108
+ const hasRemaining = issues.some(i => i.timeEstimate != null);
109
+ const originalTotal = sumBy(issues, i => i.originalEstimate);
110
+ const loggedTotal = sumBy(issues, i => i.timeSpent);
111
+ const remainingTotal = sumBy(issues, i => i.timeEstimate);
112
+ const unestimated = issues.filter(i => i.originalEstimate == null && i.timeEstimate == null).length;
127
113
  const lines = ['## Time (Effort)', ''];
128
114
  lines.push('| Metric | Value |');
129
115
  lines.push('|--------|-------|');
130
- lines.push(`| Original Estimate | ${formatDuration(total)} |`);
131
- lines.push(`| Completed | ${formatDuration(done)} |`);
132
- lines.push(`| Remaining | ${formatDuration(remaining)} |`);
116
+ if (hasOriginal)
117
+ lines.push(`| Original Estimate | ${formatDuration(originalTotal)} |`);
118
+ if (hasLogged)
119
+ lines.push(`| Logged | ${formatDuration(loggedTotal)} |`);
120
+ if (hasRemaining)
121
+ lines.push(`| Remaining | ${formatDuration(remainingTotal)} |`);
122
+ if (hasOriginal && hasLogged && originalTotal > 0) {
123
+ const pct = Math.round((loggedTotal / originalTotal) * 100);
124
+ lines.push(`| Effort Used | ${pct}% |`);
125
+ }
133
126
  if (unestimated > 0) {
134
127
  lines.push(`| Unestimated | ${unestimated} issue${unestimated !== 1 ? 's' : ''} |`);
135
128
  }
@@ -405,6 +398,10 @@ async function batchParallel(tasks, batchSize) {
405
398
  return results;
406
399
  }
407
400
  const ROW_BATCH_SIZE = 3; // ~18-33 concurrent count queries per batch
401
+ /** Strip ORDER BY clause from JQL — ordering is meaningless for counts and analysis */
402
+ export function stripOrderBy(jql) {
403
+ return jql.replace(/\s+ORDER\s+BY\s+.+$/i, '');
404
+ }
408
405
  /** Build a scoped JQL by adding a condition to the base query */
409
406
  function scopeJql(baseJql, condition) {
410
407
  return `(${baseJql}) AND ${condition}`;
@@ -779,6 +776,8 @@ function graphIssueToDetails(issue) {
779
776
  storyPoints: issue.storyPoints,
780
777
  sprint: null,
781
778
  timeEstimate: null,
779
+ originalEstimate: null,
780
+ timeSpent: null,
782
781
  issueLinks: [],
783
782
  };
784
783
  }
@@ -899,6 +898,9 @@ export async function handleAnalysisRequest(jiraClient, request, graphqlClient,
899
898
  throw new McpError(ErrorCode.InvalidParams, 'Either jql, filterId, or dataRef parameter is required.');
900
899
  }
901
900
  }
901
+ // Strip ORDER BY — analysis uses counts and full-page fetches, ordering is meaningless
902
+ // and breaks scopeJql() which wraps JQL in parens (ORDER BY inside parens is invalid)
903
+ jql = stripOrderBy(jql);
902
904
  // Parse requested metrics
903
905
  const requested = (args.metrics && Array.isArray(args.metrics))
904
906
  ? args.metrics
@@ -13,8 +13,8 @@ function validateManageJiraIssueArgs(args) {
13
13
  const normalizedArgs = normalizeArgs(args);
14
14
  // Validate operation parameter
15
15
  if (typeof normalizedArgs.operation !== 'string' ||
16
- !['create', 'get', 'update', 'delete', 'move', 'transition', 'comment', 'link', 'hierarchy'].includes(normalizedArgs.operation)) {
17
- throw new McpError(ErrorCode.InvalidParams, 'Invalid operation parameter. Valid values are: create, get, update, delete, move, transition, comment, link, hierarchy');
16
+ !['create', 'get', 'update', 'delete', 'move', 'transition', 'comment', 'link', 'hierarchy', 'worklog'].includes(normalizedArgs.operation)) {
17
+ throw new McpError(ErrorCode.InvalidParams, 'Invalid operation parameter. Valid values are: create, get, update, delete, move, transition, comment, link, hierarchy, worklog');
18
18
  }
19
19
  // Validate parameters based on operation
20
20
  switch (normalizedArgs.operation) {
@@ -62,8 +62,10 @@ function validateManageJiraIssueArgs(args) {
62
62
  normalizedArgs.priority === undefined &&
63
63
  normalizedArgs.labels === undefined &&
64
64
  normalizedArgs.dueDate === undefined &&
65
+ normalizedArgs.originalEstimate === undefined &&
66
+ normalizedArgs.remainingEstimate === undefined &&
65
67
  normalizedArgs.customFields === undefined) {
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.');
68
+ throw new McpError(ErrorCode.InvalidParams, 'At least one update field (summary, description, parent, assignee, priority, labels, dueDate, originalEstimate, remainingEstimate, or customFields) must be provided for the update operation.');
67
69
  }
68
70
  break;
69
71
  case 'transition':
@@ -93,6 +95,20 @@ function validateManageJiraIssueArgs(args) {
93
95
  throw new McpError(ErrorCode.InvalidParams, 'Missing or invalid linkType parameter. Please provide a valid link type for the link operation.');
94
96
  }
95
97
  break;
98
+ case 'worklog':
99
+ if (typeof normalizedArgs.issueKey !== 'string' || normalizedArgs.issueKey.trim() === '') {
100
+ throw new McpError(ErrorCode.InvalidParams, 'Missing or invalid issueKey parameter. Please provide a valid issue key for the worklog operation.');
101
+ }
102
+ if (typeof normalizedArgs.timeSpent !== 'string' || normalizedArgs.timeSpent.trim() === '') {
103
+ throw new McpError(ErrorCode.InvalidParams, 'Missing or invalid timeSpent parameter. Provide time in Jira format (e.g., "3h 30m", "1d", "2w").');
104
+ }
105
+ if (normalizedArgs.adjustEstimate === 'new' && !normalizedArgs.newEstimate) {
106
+ throw new McpError(ErrorCode.InvalidParams, 'newEstimate is required when adjustEstimate is "new" (e.g., "2d").');
107
+ }
108
+ if (normalizedArgs.adjustEstimate === 'manual' && !normalizedArgs.reduceBy) {
109
+ throw new McpError(ErrorCode.InvalidParams, 'reduceBy is required when adjustEstimate is "manual" (e.g., "1h").');
110
+ }
111
+ break;
96
112
  case 'hierarchy':
97
113
  if (typeof normalizedArgs.issueKey !== 'string' || normalizedArgs.issueKey.trim() === '') {
98
114
  throw new McpError(ErrorCode.InvalidParams, 'Missing or invalid issueKey parameter. Please provide a valid issue key for the hierarchy operation.');
@@ -258,6 +274,7 @@ async function handleCreateIssue(jiraClient, args) {
258
274
  assignee: args.assignee,
259
275
  labels: args.labels,
260
276
  dueDate: args.dueDate ?? undefined,
277
+ originalEstimate: args.originalEstimate,
261
278
  customFields,
262
279
  });
263
280
  // Get the created issue and render to markdown
@@ -283,6 +300,8 @@ async function handleUpdateIssue(jiraClient, args) {
283
300
  priority: args.priority,
284
301
  labels: args.labels,
285
302
  dueDate: args.dueDate,
303
+ originalEstimate: args.originalEstimate,
304
+ remainingEstimate: args.remainingEstimate,
286
305
  customFields,
287
306
  });
288
307
  // Get the updated issue and render to markdown
@@ -341,6 +360,28 @@ async function handleLinkIssue(jiraClient, args) {
341
360
  ],
342
361
  };
343
362
  }
363
+ async function handleWorklogIssue(jiraClient, args) {
364
+ await jiraClient.addWorklog({
365
+ issueKey: args.issueKey,
366
+ timeSpent: args.timeSpent,
367
+ comment: args.worklogComment,
368
+ started: args.started,
369
+ adjustEstimate: args.adjustEstimate,
370
+ newEstimate: args.newEstimate,
371
+ reduceBy: args.reduceBy,
372
+ });
373
+ // Get the updated issue and render to markdown
374
+ const updatedIssue = await jiraClient.getIssue(args.issueKey, false, false);
375
+ const markdown = MarkdownRenderer.renderIssue(updatedIssue);
376
+ return {
377
+ content: [
378
+ {
379
+ type: 'text',
380
+ text: `# Worklog Added\n\nLogged ${args.timeSpent} on ${args.issueKey}\n\n${markdown}${issueGuidance('worklog', args.issueKey)}`,
381
+ },
382
+ ],
383
+ };
384
+ }
344
385
  function renderHierarchyTree(node, focusKey, prefix = '', isLast = true, isRoot = true) {
345
386
  const connector = isRoot ? '' : (isLast ? '└─ ' : '├─ ');
346
387
  const marker = node.key === focusKey ? ' ← you are here' : '';
@@ -419,6 +460,10 @@ export async function handleIssueRequest(jiraClient, request) {
419
460
  console.error('Processing link issue operation');
420
461
  return await handleLinkIssue(jiraClient, normalizedArgs);
421
462
  }
463
+ case 'worklog': {
464
+ console.error('Processing worklog operation');
465
+ return await handleWorklogIssue(jiraClient, normalizedArgs);
466
+ }
422
467
  case 'hierarchy': {
423
468
  console.error('Processing hierarchy operation');
424
469
  return await handleHierarchy(jiraClient, normalizedArgs);
@@ -32,6 +32,22 @@ function formatDate(dateStr) {
32
32
  const date = new Date(dateStr);
33
33
  return date.toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric' });
34
34
  }
35
+ /** Format seconds into human-readable duration (e.g. 1d 2h, 3h 30m) */
36
+ export function formatDuration(seconds) {
37
+ if (seconds === 0)
38
+ return '0m';
39
+ const days = Math.floor(seconds / 28800); // 8h workday
40
+ const hours = Math.floor((seconds % 28800) / 3600);
41
+ const minutes = Math.floor((seconds % 3600) / 60);
42
+ const parts = [];
43
+ if (days > 0)
44
+ parts.push(`${days}d`);
45
+ if (hours > 0)
46
+ parts.push(`${hours}h`);
47
+ if (minutes > 0)
48
+ parts.push(`${minutes}m`);
49
+ return parts.length > 0 ? parts.join(' ') : '0m';
50
+ }
35
51
  /**
36
52
  * Format status with visual indicator
37
53
  */
@@ -110,16 +126,16 @@ export function renderIssue(issue, transitions) {
110
126
  lines.push(dates.join(' | '));
111
127
  if (issue.storyPoints)
112
128
  lines.push(`Points: ${issue.storyPoints}`);
113
- if (issue.timeEstimate) {
114
- const hours = Math.floor(issue.timeEstimate / 3600);
115
- const minutes = Math.floor((issue.timeEstimate % 3600) / 60);
116
- const parts = [];
117
- if (hours > 0)
118
- parts.push(`${hours}h`);
119
- if (minutes > 0)
120
- parts.push(`${minutes}m`);
121
- lines.push(`Estimate: ${parts.length > 0 ? parts.join(' ') : '0m'}`);
122
- }
129
+ // Time tracking — consolidated line
130
+ const timeParts = [];
131
+ if (issue.originalEstimate != null)
132
+ timeParts.push(`Estimate: ${formatDuration(issue.originalEstimate)}`);
133
+ if (issue.timeEstimate != null)
134
+ timeParts.push(`Remaining: ${formatDuration(issue.timeEstimate)}`);
135
+ if (issue.timeSpent != null)
136
+ timeParts.push(`Logged: ${formatDuration(issue.timeSpent)}`);
137
+ if (timeParts.length > 0)
138
+ lines.push(timeParts.join(' | '));
123
139
  if (issue.resolution)
124
140
  lines.push(`Resolution: ${issue.resolution}`);
125
141
  // Description — already markdown from ADF conversion
@@ -142,13 +142,13 @@ export const toolSchemas = {
142
142
  },
143
143
  manage_jira_issue: {
144
144
  name: 'manage_jira_issue',
145
- description: 'Get, create, update, delete, move, transition, comment on, link, or explore hierarchy of Jira issues',
145
+ description: 'Get, create, update, delete, move, transition, comment on, link, log work on, or explore hierarchy of Jira issues',
146
146
  inputSchema: {
147
147
  type: 'object',
148
148
  properties: {
149
149
  operation: {
150
150
  type: 'string',
151
- enum: ['create', 'get', 'update', 'delete', 'move', 'transition', 'comment', 'link', 'hierarchy'],
151
+ enum: ['create', 'get', 'update', 'delete', 'move', 'transition', 'comment', 'link', 'hierarchy', 'worklog'],
152
152
  description: 'Operation to perform',
153
153
  },
154
154
  issueKey: {
@@ -192,6 +192,39 @@ export const toolSchemas = {
192
192
  type: ['string', 'null'],
193
193
  description: 'Due date in ISO format (e.g., "2025-06-15") or null to clear. For create and update.',
194
194
  },
195
+ originalEstimate: {
196
+ type: 'string',
197
+ description: 'Original time estimate in Jira format (e.g., "3d", "2h 30m", "1w"). For create and update.',
198
+ },
199
+ remainingEstimate: {
200
+ type: 'string',
201
+ description: 'Remaining time estimate in Jira format (e.g., "1d", "4h"). For update only.',
202
+ },
203
+ timeSpent: {
204
+ type: 'string',
205
+ description: 'Time spent in Jira format (e.g., "3h 30m", "1d", "2w"). Required for worklog.',
206
+ },
207
+ worklogComment: {
208
+ type: 'string',
209
+ description: 'Description of work performed. For worklog.',
210
+ },
211
+ started: {
212
+ type: 'string',
213
+ description: 'When the work started, ISO datetime (e.g., "2025-04-09T09:00:00.000+0000"). Defaults to now. For worklog.',
214
+ },
215
+ adjustEstimate: {
216
+ type: 'string',
217
+ enum: ['auto', 'leave', 'new', 'manual'],
218
+ description: 'How to adjust the remaining estimate: auto (reduce by timeSpent), leave (unchanged), new (set to newEstimate), manual (reduce by reduceBy). Default: auto. For worklog.',
219
+ },
220
+ newEstimate: {
221
+ type: 'string',
222
+ description: 'New remaining estimate when adjustEstimate is "new" (e.g., "2d"). For worklog.',
223
+ },
224
+ reduceBy: {
225
+ type: 'string',
226
+ description: 'Amount to reduce remaining estimate when adjustEstimate is "manual" (e.g., "1h"). For worklog.',
227
+ },
195
228
  parent: {
196
229
  type: ['string', 'null'],
197
230
  description: 'Parent issue key (e.g., PROJ-100) or null to remove.',
@@ -38,6 +38,9 @@ export function issueNextSteps(operation, issueKey) {
38
38
  case 'link':
39
39
  steps.push({ description: 'View the linked issue', tool: 'manage_jira_issue', example: { operation: 'get', issueKey } }, { description: 'Read available link types from jira://issue-link-types resource' });
40
40
  break;
41
+ case 'worklog':
42
+ steps.push({ description: 'View the updated issue', tool: 'manage_jira_issue', example: { operation: 'get', issueKey } }, { description: 'Log more time', tool: 'manage_jira_issue', example: { operation: 'worklog', issueKey, timeSpent: '<duration>' } }, { description: 'Adjust the remaining estimate', tool: 'manage_jira_issue', example: { operation: 'update', issueKey, remainingEstimate: '<duration>' } });
43
+ break;
41
44
  case 'hierarchy':
42
45
  steps.push({ description: 'View a specific issue from the tree', tool: 'manage_jira_issue', example: { operation: 'get', issueKey } }, { description: 'Analyze plan rollups (requires Jira Plans)', tool: 'analyze_jira_plan', example: { issueKey } }, { description: 'Search for issues in this project', tool: 'manage_jira_filter', example: { operation: 'execute_jql', jql: `project = "${issueKey?.split('-')[0]}"` } });
43
46
  break;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aaronsb/jira-cloud-mcp",
3
- "version": "0.7.6",
3
+ "version": "0.8.1",
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",