@aaronsb/jira-cloud-mcp 0.3.1 → 0.4.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.
- package/README.md +12 -8
- package/build/client/jira-client.js +147 -0
- package/build/handlers/issue-handlers.js +39 -2
- package/build/handlers/queue-handler.js +27 -8
- package/build/index.js +8 -1
- package/build/schemas/tool-schemas.js +16 -2
- package/build/utils/next-steps.js +3 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -2,9 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
A Model Context Protocol server for interacting with Jira Cloud instances.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Install
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
### Claude Desktop (one-click)
|
|
8
|
+
|
|
9
|
+
Download [`jira-cloud-mcp.mcpb`](https://github.com/aaronsb/jira-cloud/releases/latest) and open it — Claude Desktop will prompt for your Jira credentials.
|
|
10
|
+
|
|
11
|
+
### Claude Code
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
claude mcp add jira-cloud -e JIRA_API_TOKEN=your-token -e JIRA_EMAIL=your-email -e JIRA_HOST=https://your-team.atlassian.net -- npx -y @aaronsb/jira-cloud-mcp
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
### Manual (any MCP client)
|
|
8
18
|
|
|
9
19
|
```json
|
|
10
20
|
{
|
|
@@ -22,12 +32,6 @@ Add to your MCP settings:
|
|
|
22
32
|
}
|
|
23
33
|
```
|
|
24
34
|
|
|
25
|
-
Or install globally:
|
|
26
|
-
|
|
27
|
-
```bash
|
|
28
|
-
npm install -g @aaronsb/jira-cloud-mcp
|
|
29
|
-
```
|
|
30
|
-
|
|
31
35
|
### Credentials
|
|
32
36
|
|
|
33
37
|
Generate an API token at [Atlassian Account Settings](https://id.atlassian.com/manage/api-tokens).
|
|
@@ -175,6 +175,153 @@ export class JiraClient {
|
|
|
175
175
|
}
|
|
176
176
|
return issueDetails;
|
|
177
177
|
}
|
|
178
|
+
/** Lightweight fetch: key, summary, issuetype, status, parent only */
|
|
179
|
+
async fetchNodeFields(issueKey) {
|
|
180
|
+
const issue = await this.client.issues.getIssue({
|
|
181
|
+
issueIdOrKey: issueKey,
|
|
182
|
+
fields: ['summary', 'issuetype', 'status', 'parent'],
|
|
183
|
+
});
|
|
184
|
+
const f = issue.fields;
|
|
185
|
+
return {
|
|
186
|
+
key: issue.key,
|
|
187
|
+
summary: f?.summary || '',
|
|
188
|
+
issueType: f?.issuetype?.name || '',
|
|
189
|
+
status: f?.status?.name || '',
|
|
190
|
+
parent: f?.parent?.key || null,
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
/** Fetch children of given keys in one JQL query. Returns truncated flag if results hit the limit. */
|
|
194
|
+
async fetchChildren(parentKeys) {
|
|
195
|
+
if (parentKeys.length === 0)
|
|
196
|
+
return { children: [], truncated: false };
|
|
197
|
+
const maxResults = 100;
|
|
198
|
+
const jql = `parent in (${parentKeys.join(', ')})`;
|
|
199
|
+
const results = await this.client.issueSearch.searchForIssuesUsingJqlEnhancedSearch({
|
|
200
|
+
jql,
|
|
201
|
+
maxResults,
|
|
202
|
+
fields: ['summary', 'issuetype', 'status', 'parent'],
|
|
203
|
+
});
|
|
204
|
+
const issues = results.issues || [];
|
|
205
|
+
return {
|
|
206
|
+
children: issues.map((issue) => ({
|
|
207
|
+
key: issue.key,
|
|
208
|
+
summary: issue.fields?.summary || '',
|
|
209
|
+
issueType: issue.fields?.issuetype?.name || '',
|
|
210
|
+
status: issue.fields?.status?.name || '',
|
|
211
|
+
parent: issue.fields?.parent?.key || '',
|
|
212
|
+
})),
|
|
213
|
+
truncated: issues.length >= maxResults,
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Traverse the issue hierarchy: walk up `upHops` levels and down `downHops` levels
|
|
218
|
+
* from the focus issue, returning a tree with a "you are here" marker.
|
|
219
|
+
*/
|
|
220
|
+
async getHierarchy(issueKey, upHops = 4, downHops = 4) {
|
|
221
|
+
// 1. Fetch focus node
|
|
222
|
+
const focus = await this.fetchNodeFields(issueKey);
|
|
223
|
+
// 2. Walk UP the parent chain (with circular reference guard)
|
|
224
|
+
const ancestors = [];
|
|
225
|
+
const visited = new Set([focus.key]);
|
|
226
|
+
let current = focus;
|
|
227
|
+
for (let i = 0; i < upHops; i++) {
|
|
228
|
+
if (!current.parent || visited.has(current.parent))
|
|
229
|
+
break;
|
|
230
|
+
visited.add(current.parent);
|
|
231
|
+
const parentNode = await this.fetchNodeFields(current.parent);
|
|
232
|
+
ancestors.unshift(parentNode); // prepend — root first
|
|
233
|
+
current = parentNode;
|
|
234
|
+
}
|
|
235
|
+
// Build the focus node
|
|
236
|
+
const focusNode = {
|
|
237
|
+
key: focus.key,
|
|
238
|
+
summary: focus.summary,
|
|
239
|
+
issueType: focus.issueType,
|
|
240
|
+
status: focus.status,
|
|
241
|
+
children: [],
|
|
242
|
+
};
|
|
243
|
+
// BFS: expand children level by level
|
|
244
|
+
let truncated = false;
|
|
245
|
+
let actualDownDepth = 0;
|
|
246
|
+
let frontier = [focusNode];
|
|
247
|
+
for (let level = 0; level < downHops; level++) {
|
|
248
|
+
const parentKeys = frontier.map(n => n.key);
|
|
249
|
+
const result = await this.fetchChildren(parentKeys);
|
|
250
|
+
if (result.children.length === 0)
|
|
251
|
+
break;
|
|
252
|
+
if (result.truncated)
|
|
253
|
+
truncated = true;
|
|
254
|
+
actualDownDepth = level + 1;
|
|
255
|
+
// Group children by parent
|
|
256
|
+
const byParent = new Map();
|
|
257
|
+
for (const child of result.children) {
|
|
258
|
+
const group = byParent.get(child.parent) || [];
|
|
259
|
+
group.push(child);
|
|
260
|
+
byParent.set(child.parent, group);
|
|
261
|
+
}
|
|
262
|
+
const nextFrontier = [];
|
|
263
|
+
for (const parent of frontier) {
|
|
264
|
+
const childNodes = (byParent.get(parent.key) || []).map(c => ({
|
|
265
|
+
key: c.key,
|
|
266
|
+
summary: c.summary,
|
|
267
|
+
issueType: c.issueType,
|
|
268
|
+
status: c.status,
|
|
269
|
+
children: [],
|
|
270
|
+
}));
|
|
271
|
+
parent.children = childNodes;
|
|
272
|
+
nextFrontier.push(...childNodes);
|
|
273
|
+
}
|
|
274
|
+
frontier = nextFrontier;
|
|
275
|
+
}
|
|
276
|
+
// 4. Build the full tree: ancestors → focus → descendants
|
|
277
|
+
let root = focusNode;
|
|
278
|
+
for (const ancestor of [...ancestors].reverse()) {
|
|
279
|
+
root = {
|
|
280
|
+
key: ancestor.key,
|
|
281
|
+
summary: ancestor.summary,
|
|
282
|
+
issueType: ancestor.issueType,
|
|
283
|
+
status: ancestor.status,
|
|
284
|
+
children: [root],
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
// 5. Expand sibling children for ancestor nodes (batched single call)
|
|
288
|
+
if (ancestors.length > 0) {
|
|
289
|
+
// Collect all ancestor keys for a single batched fetch
|
|
290
|
+
const ancestorKeys = [];
|
|
291
|
+
let walkNode = root;
|
|
292
|
+
for (let i = 0; i < ancestors.length; i++) {
|
|
293
|
+
ancestorKeys.push(walkNode.key);
|
|
294
|
+
walkNode = walkNode.children[0];
|
|
295
|
+
}
|
|
296
|
+
const siblingResult = await this.fetchChildren(ancestorKeys);
|
|
297
|
+
if (siblingResult.truncated)
|
|
298
|
+
truncated = true;
|
|
299
|
+
// Group siblings by parent
|
|
300
|
+
const siblingsByParent = new Map();
|
|
301
|
+
for (const child of siblingResult.children) {
|
|
302
|
+
const group = siblingsByParent.get(child.parent) || [];
|
|
303
|
+
group.push(child);
|
|
304
|
+
siblingsByParent.set(child.parent, group);
|
|
305
|
+
}
|
|
306
|
+
// Distribute siblings to each ancestor node
|
|
307
|
+
let node = root;
|
|
308
|
+
for (let i = 0; i < ancestors.length; i++) {
|
|
309
|
+
const existingChildKey = node.children[0]?.key;
|
|
310
|
+
const siblings = (siblingsByParent.get(node.key) || [])
|
|
311
|
+
.filter(c => c.key !== existingChildKey)
|
|
312
|
+
.map(c => ({ key: c.key, summary: c.summary, issueType: c.issueType, status: c.status, children: [] }));
|
|
313
|
+
node.children = [...node.children, ...siblings];
|
|
314
|
+
node = node.children.find(c => c.key === existingChildKey) || node.children[0];
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
return {
|
|
318
|
+
root,
|
|
319
|
+
focusKey: focus.key,
|
|
320
|
+
upDepth: ancestors.length,
|
|
321
|
+
downDepth: actualDownDepth,
|
|
322
|
+
truncated,
|
|
323
|
+
};
|
|
324
|
+
}
|
|
178
325
|
async getIssueAttachments(issueKey) {
|
|
179
326
|
const issue = await this.client.issues.getIssue({
|
|
180
327
|
issueIdOrKey: issueKey,
|
|
@@ -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'].includes(normalizedArgs.operation)) {
|
|
17
|
-
throw new McpError(ErrorCode.InvalidParams, 'Invalid operation parameter. Valid values are: create, get, update, delete, move, transition, comment, link');
|
|
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');
|
|
18
18
|
}
|
|
19
19
|
// Validate parameters based on operation
|
|
20
20
|
switch (normalizedArgs.operation) {
|
|
@@ -92,6 +92,11 @@ function validateManageJiraIssueArgs(args) {
|
|
|
92
92
|
throw new McpError(ErrorCode.InvalidParams, 'Missing or invalid linkType parameter. Please provide a valid link type for the link operation.');
|
|
93
93
|
}
|
|
94
94
|
break;
|
|
95
|
+
case 'hierarchy':
|
|
96
|
+
if (typeof normalizedArgs.issueKey !== 'string' || normalizedArgs.issueKey.trim() === '') {
|
|
97
|
+
throw new McpError(ErrorCode.InvalidParams, 'Missing or invalid issueKey parameter. Please provide a valid issue key for the hierarchy operation.');
|
|
98
|
+
}
|
|
99
|
+
break;
|
|
95
100
|
}
|
|
96
101
|
// Validate expand parameter
|
|
97
102
|
if (normalizedArgs.expand !== undefined) {
|
|
@@ -332,6 +337,34 @@ async function handleLinkIssue(jiraClient, args) {
|
|
|
332
337
|
],
|
|
333
338
|
};
|
|
334
339
|
}
|
|
340
|
+
function renderHierarchyTree(node, focusKey, prefix = '', isLast = true, isRoot = true) {
|
|
341
|
+
const connector = isRoot ? '' : (isLast ? '└─ ' : '├─ ');
|
|
342
|
+
const marker = node.key === focusKey ? ' ← you are here' : '';
|
|
343
|
+
const line = `${prefix}${connector}**${node.key}** ${node.issueType}: ${node.summary} [${node.status}]${marker}`;
|
|
344
|
+
const childPrefix = isRoot ? '' : (prefix + (isLast ? ' ' : '│ '));
|
|
345
|
+
const childLines = node.children.map((child, i) => renderHierarchyTree(child, focusKey, childPrefix, i === node.children.length - 1, false));
|
|
346
|
+
return [line, ...childLines].join('\n');
|
|
347
|
+
}
|
|
348
|
+
async function handleHierarchy(jiraClient, args) {
|
|
349
|
+
const up = Math.min(Math.max(args.up ?? 4, 0), 8);
|
|
350
|
+
const down = Math.min(Math.max(args.down ?? 4, 0), 8);
|
|
351
|
+
console.error(`Fetching hierarchy for ${args.issueKey}: up=${up}, down=${down}`);
|
|
352
|
+
const result = await jiraClient.getHierarchy(args.issueKey, up, down);
|
|
353
|
+
const tree = renderHierarchyTree(result.root, result.focusKey);
|
|
354
|
+
const lines = [
|
|
355
|
+
`# Issue Hierarchy: ${result.focusKey}`,
|
|
356
|
+
'',
|
|
357
|
+
`Traversed ${result.upDepth} level${result.upDepth !== 1 ? 's' : ''} up, ${result.downDepth} level${result.downDepth !== 1 ? 's' : ''} down`,
|
|
358
|
+
];
|
|
359
|
+
if (result.truncated) {
|
|
360
|
+
lines.push('', '⚠️ Results were truncated — some children may not be shown. Narrow the scope with smaller `up`/`down` values or focus on a specific subtree.');
|
|
361
|
+
}
|
|
362
|
+
lines.push('', tree);
|
|
363
|
+
const summary = lines.join('\n');
|
|
364
|
+
return {
|
|
365
|
+
content: [{ type: 'text', text: summary + issueGuidance('hierarchy', args.issueKey) }],
|
|
366
|
+
};
|
|
367
|
+
}
|
|
335
368
|
// Main handler function
|
|
336
369
|
export async function handleIssueRequest(jiraClient, request) {
|
|
337
370
|
console.error('Handling issue request...');
|
|
@@ -382,6 +415,10 @@ export async function handleIssueRequest(jiraClient, request) {
|
|
|
382
415
|
console.error('Processing link issue operation');
|
|
383
416
|
return await handleLinkIssue(jiraClient, normalizedArgs);
|
|
384
417
|
}
|
|
418
|
+
case 'hierarchy': {
|
|
419
|
+
console.error('Processing hierarchy operation');
|
|
420
|
+
return await handleHierarchy(jiraClient, normalizedArgs);
|
|
421
|
+
}
|
|
385
422
|
default: {
|
|
386
423
|
console.error(`Unknown operation: ${normalizedArgs.operation}`);
|
|
387
424
|
throw new McpError(ErrorCode.MethodNotFound, `Unknown operation: ${normalizedArgs.operation}`);
|
|
@@ -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');
|
package/build/index.js
CHANGED
|
@@ -18,7 +18,14 @@ const JIRA_EMAIL = process.env.JIRA_EMAIL;
|
|
|
18
18
|
const JIRA_API_TOKEN = process.env.JIRA_API_TOKEN;
|
|
19
19
|
const JIRA_HOST = process.env.JIRA_HOST;
|
|
20
20
|
if (!JIRA_EMAIL || !JIRA_API_TOKEN || !JIRA_HOST) {
|
|
21
|
-
|
|
21
|
+
const missing = [
|
|
22
|
+
!JIRA_API_TOKEN && 'JIRA_API_TOKEN',
|
|
23
|
+
!JIRA_EMAIL && 'JIRA_EMAIL',
|
|
24
|
+
!JIRA_HOST && 'JIRA_HOST',
|
|
25
|
+
].filter(Boolean).join(', ');
|
|
26
|
+
console.error(`[jira-cloud] Missing required environment variables: ${missing}`);
|
|
27
|
+
console.error('[jira-cloud] Set these in your MCP configuration or MCPB extension settings.');
|
|
28
|
+
process.exit(1);
|
|
22
29
|
}
|
|
23
30
|
const require = createRequire(import.meta.url);
|
|
24
31
|
const { version } = require('../package.json');
|
|
@@ -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, or
|
|
145
|
+
description: 'Get, create, update, delete, move, transition, comment on, link, 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'],
|
|
151
|
+
enum: ['create', 'get', 'update', 'delete', 'move', 'transition', 'comment', 'link', 'hierarchy'],
|
|
152
152
|
description: 'Operation to perform',
|
|
153
153
|
},
|
|
154
154
|
issueKey: {
|
|
@@ -216,6 +216,14 @@ export const toolSchemas = {
|
|
|
216
216
|
type: 'string',
|
|
217
217
|
description: 'Target issue type for move (e.g., Story, Bug). Required for move.',
|
|
218
218
|
},
|
|
219
|
+
up: {
|
|
220
|
+
type: 'number',
|
|
221
|
+
description: 'Hierarchy: how many levels up to traverse (default 4, max 8).',
|
|
222
|
+
},
|
|
223
|
+
down: {
|
|
224
|
+
type: 'number',
|
|
225
|
+
description: 'Hierarchy: how many levels down to traverse (default 4, max 8).',
|
|
226
|
+
},
|
|
219
227
|
expand: {
|
|
220
228
|
type: 'array',
|
|
221
229
|
items: {
|
|
@@ -344,6 +352,12 @@ export const toolSchemas = {
|
|
|
344
352
|
description: 'Ordered list of operations to execute (max 10).',
|
|
345
353
|
maxItems: 10,
|
|
346
354
|
},
|
|
355
|
+
detail: {
|
|
356
|
+
type: 'string',
|
|
357
|
+
enum: ['full', 'summary'],
|
|
358
|
+
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.',
|
|
359
|
+
default: 'summary',
|
|
360
|
+
},
|
|
347
361
|
},
|
|
348
362
|
required: ['operations'],
|
|
349
363
|
},
|
|
@@ -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 'hierarchy':
|
|
42
|
+
steps.push({ description: 'View a specific issue from the tree', tool: 'manage_jira_issue', example: { operation: 'get', issueKey } }, { description: 'Search for issues in this project', tool: 'manage_jira_filter', example: { operation: 'execute_jql', jql: `project = "${issueKey?.split('-')[0]}"` } });
|
|
43
|
+
break;
|
|
41
44
|
}
|
|
42
45
|
return steps.length > 0 ? formatSteps(steps) : '';
|
|
43
46
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aaronsb/jira-cloud-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.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",
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
"update-doc-timestamps": "node scripts/update-doc-timestamps.js"
|
|
47
47
|
},
|
|
48
48
|
"dependencies": {
|
|
49
|
-
"@modelcontextprotocol/sdk": "^1.
|
|
49
|
+
"@modelcontextprotocol/sdk": "^1.27.1",
|
|
50
50
|
"jira.js": "5.3.1",
|
|
51
51
|
"jsdom": "^27.3.0",
|
|
52
52
|
"markdown-it": "^14.1.0"
|