@aaronsb/jira-cloud-mcp 0.2.7 → 0.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 +22 -30
- package/build/client/field-discovery.js +346 -0
- package/build/client/field-type-map.js +106 -0
- package/build/client/jira-client.js +119 -88
- package/build/docs/tool-documentation.js +481 -0
- package/build/handlers/board-handlers.js +10 -153
- package/build/handlers/filter-handlers.js +7 -98
- package/build/handlers/issue-handlers.js +148 -75
- package/build/handlers/project-handlers.js +11 -154
- package/build/handlers/queue-handler.js +252 -0
- package/build/handlers/resource-handlers.js +94 -19
- package/build/handlers/sprint-handlers.js +9 -94
- package/build/handlers/tool-resource-handlers.js +16 -1165
- package/build/index.js +59 -51
- package/build/mcp/markdown-renderer.js +11 -0
- package/build/schemas/tool-schemas.js +98 -197
- package/build/utils/bulk-operation-guard.js +74 -0
- package/build/utils/next-steps.js +124 -0
- package/build/utils/normalize-args.js +12 -0
- package/build/utils/text-processing.js +5 -1
- package/package.json +5 -4
package/build/index.js
CHANGED
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { createRequire } from 'module';
|
|
2
3
|
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
3
4
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
4
5
|
import { CallToolRequestSchema, ErrorCode, ListResourcesRequestSchema, ListResourceTemplatesRequestSchema, ListToolsRequestSchema, McpError, ReadResourceRequestSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
6
|
+
import { fieldDiscovery } from './client/field-discovery.js';
|
|
5
7
|
import { JiraClient } from './client/jira-client.js';
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
8
|
+
import { handleBoardRequest } from './handlers/board-handlers.js';
|
|
9
|
+
import { handleFilterRequest } from './handlers/filter-handlers.js';
|
|
10
|
+
import { handleIssueRequest } from './handlers/issue-handlers.js';
|
|
11
|
+
import { handleProjectRequest } from './handlers/project-handlers.js';
|
|
12
|
+
import { createQueueHandler } from './handlers/queue-handler.js';
|
|
10
13
|
import { setupResourceHandlers } from './handlers/resource-handlers.js';
|
|
11
|
-
import {
|
|
14
|
+
import { handleSprintRequest } from './handlers/sprint-handlers.js';
|
|
12
15
|
import { toolSchemas } from './schemas/tool-schemas.js';
|
|
13
16
|
// Jira credentials from environment variables
|
|
14
17
|
const JIRA_EMAIL = process.env.JIRA_EMAIL;
|
|
@@ -17,16 +20,17 @@ const JIRA_HOST = process.env.JIRA_HOST;
|
|
|
17
20
|
if (!JIRA_EMAIL || !JIRA_API_TOKEN || !JIRA_HOST) {
|
|
18
21
|
throw new Error('Missing required Jira credentials in environment variables');
|
|
19
22
|
}
|
|
23
|
+
const require = createRequire(import.meta.url);
|
|
24
|
+
const { version } = require('../package.json');
|
|
20
25
|
class JiraServer {
|
|
21
26
|
server;
|
|
22
27
|
jiraClient;
|
|
23
28
|
constructor() {
|
|
24
|
-
// Use environment-provided name or default to 'jira-cloud'
|
|
25
29
|
const serverName = process.env.MCP_SERVER_NAME || 'jira-cloud';
|
|
26
30
|
console.error(`Initializing Jira MCP server: ${serverName}`);
|
|
27
31
|
this.server = new Server({
|
|
28
32
|
name: serverName,
|
|
29
|
-
version
|
|
33
|
+
version,
|
|
30
34
|
}, {
|
|
31
35
|
capabilities: {
|
|
32
36
|
tools: {},
|
|
@@ -39,6 +43,8 @@ class JiraServer {
|
|
|
39
43
|
apiToken: JIRA_API_TOKEN,
|
|
40
44
|
});
|
|
41
45
|
this.setupHandlers();
|
|
46
|
+
// Start async field discovery (non-blocking)
|
|
47
|
+
fieldDiscovery.startAsync(this.jiraClient.v3Client);
|
|
42
48
|
this.server.onerror = (error) => console.error('[MCP Error]', error);
|
|
43
49
|
process.on('SIGINT', async () => {
|
|
44
50
|
await this.server.close();
|
|
@@ -49,8 +55,6 @@ class JiraServer {
|
|
|
49
55
|
// Set up required MCP protocol handlers
|
|
50
56
|
this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
51
57
|
tools: Object.entries(toolSchemas)
|
|
52
|
-
// Filter out the deprecated search_jira_issues tool
|
|
53
|
-
.filter(([key]) => key !== 'search_jira_issues')
|
|
54
58
|
.map(([key, schema]) => ({
|
|
55
59
|
name: key,
|
|
56
60
|
description: schema.description,
|
|
@@ -74,49 +78,22 @@ class JiraServer {
|
|
|
74
78
|
const { name } = request.params;
|
|
75
79
|
console.error(`Handling tool request: ${name}`);
|
|
76
80
|
try {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
// Sprint-related tools
|
|
91
|
-
else if (['manage_jira_sprint'].includes(name)) {
|
|
92
|
-
response = await setupSprintHandlers(this.server, this.jiraClient, request);
|
|
93
|
-
}
|
|
94
|
-
// Filter-related tools
|
|
95
|
-
else if (['manage_jira_filter'].includes(name)) {
|
|
96
|
-
response = await setupFilterHandlers(this.server, this.jiraClient, request);
|
|
97
|
-
}
|
|
98
|
-
// Legacy search tool - redirect to filter handler with execute_jql operation
|
|
99
|
-
else if (name === 'search_jira_issues') {
|
|
100
|
-
console.error('Redirecting deprecated search_jira_issues to manage_jira_filter with execute_jql operation');
|
|
101
|
-
// Transform the request to use manage_jira_filter with execute_jql operation
|
|
102
|
-
const transformedRequest = {
|
|
103
|
-
...request,
|
|
104
|
-
params: {
|
|
105
|
-
...request.params,
|
|
106
|
-
name: 'manage_jira_filter',
|
|
107
|
-
arguments: {
|
|
108
|
-
...request.params.arguments,
|
|
109
|
-
operation: 'execute_jql'
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
};
|
|
113
|
-
response = await setupFilterHandlers(this.server, this.jiraClient, transformedRequest);
|
|
114
|
-
}
|
|
115
|
-
else {
|
|
116
|
-
console.error(`Unknown tool requested: ${name}`);
|
|
81
|
+
const toolHandlers = {
|
|
82
|
+
manage_jira_issue: handleIssueRequest,
|
|
83
|
+
manage_jira_project: handleProjectRequest,
|
|
84
|
+
manage_jira_board: handleBoardRequest,
|
|
85
|
+
manage_jira_sprint: handleSprintRequest,
|
|
86
|
+
manage_jira_filter: handleFilterRequest,
|
|
87
|
+
};
|
|
88
|
+
const handlers = {
|
|
89
|
+
...toolHandlers,
|
|
90
|
+
queue_jira_operations: createQueueHandler(toolHandlers, JIRA_HOST),
|
|
91
|
+
};
|
|
92
|
+
const handler = handlers[name];
|
|
93
|
+
if (!handler) {
|
|
117
94
|
throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${name}`);
|
|
118
95
|
}
|
|
119
|
-
|
|
96
|
+
const response = await handler(this.jiraClient, request);
|
|
120
97
|
if (!response) {
|
|
121
98
|
throw new McpError(ErrorCode.InternalError, `No response from handler for tool: ${name}`);
|
|
122
99
|
}
|
|
@@ -127,7 +104,38 @@ class JiraServer {
|
|
|
127
104
|
if (error instanceof McpError) {
|
|
128
105
|
throw error;
|
|
129
106
|
}
|
|
130
|
-
|
|
107
|
+
// Surface Jira permission errors with actionable guidance
|
|
108
|
+
const status = error?.response?.status
|
|
109
|
+
?? error?.status;
|
|
110
|
+
const jiraMessage = error?.response?.data?.errorMessages?.[0]
|
|
111
|
+
?? error?.response?.data?.message
|
|
112
|
+
?? error?.message
|
|
113
|
+
?? '';
|
|
114
|
+
if (status === 403) {
|
|
115
|
+
return {
|
|
116
|
+
content: [{
|
|
117
|
+
type: 'text',
|
|
118
|
+
text: [
|
|
119
|
+
`**Jira denied this operation:** ${jiraMessage || 'Insufficient permissions.'}`,
|
|
120
|
+
'',
|
|
121
|
+
'This is controlled by your Jira project\'s permission scheme.',
|
|
122
|
+
'Contact your Jira admin to request the necessary permission,',
|
|
123
|
+
'or ask them to perform the operation for you.',
|
|
124
|
+
].join('\n'),
|
|
125
|
+
}],
|
|
126
|
+
isError: true,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
if (status === 404) {
|
|
130
|
+
return {
|
|
131
|
+
content: [{
|
|
132
|
+
type: 'text',
|
|
133
|
+
text: `**Not found:** ${jiraMessage || 'The requested resource does not exist or you do not have permission to view it.'}`,
|
|
134
|
+
}],
|
|
135
|
+
isError: true,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
throw new McpError(ErrorCode.InternalError, jiraMessage || 'Internal server error');
|
|
131
139
|
}
|
|
132
140
|
});
|
|
133
141
|
}
|
|
@@ -131,6 +131,17 @@ export function renderIssue(issue, transitions) {
|
|
|
131
131
|
lines.push(` ... and ${issue.comments.length - 3} more comments`);
|
|
132
132
|
}
|
|
133
133
|
}
|
|
134
|
+
// Custom fields (from catalog discovery)
|
|
135
|
+
if (issue.customFieldValues && issue.customFieldValues.length > 0) {
|
|
136
|
+
lines.push('');
|
|
137
|
+
lines.push('## Custom Fields');
|
|
138
|
+
for (const cf of issue.customFieldValues) {
|
|
139
|
+
const displayValue = Array.isArray(cf.value)
|
|
140
|
+
? cf.value.join(', ')
|
|
141
|
+
: String(cf.value);
|
|
142
|
+
lines.push(`- **${cf.name}** (${cf.type}): ${displayValue}`);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
134
145
|
// Available transitions
|
|
135
146
|
if (transitions && transitions.length > 0) {
|
|
136
147
|
lines.push('');
|