@aaronsb/jira-cloud-mcp 0.1.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 +70 -0
- package/build/client/jira-client.js +721 -0
- package/build/handlers/board-handlers.js +326 -0
- package/build/handlers/filter-handlers.js +427 -0
- package/build/handlers/issue-handlers.js +311 -0
- package/build/handlers/project-handlers.js +368 -0
- package/build/handlers/resource-handlers.js +320 -0
- package/build/handlers/search-handlers.js +103 -0
- package/build/handlers/sprint-handlers.js +433 -0
- package/build/handlers/tool-resource-handlers.js +1185 -0
- package/build/health-check.js +67 -0
- package/build/index.js +141 -0
- package/build/schemas/request-schemas.js +187 -0
- package/build/schemas/tool-schemas.js +450 -0
- package/build/types/index.js +1 -0
- package/build/utils/formatters/base-formatter.js +58 -0
- package/build/utils/formatters/board-formatter.js +63 -0
- package/build/utils/formatters/filter-formatter.js +66 -0
- package/build/utils/formatters/index.js +7 -0
- package/build/utils/formatters/issue-formatter.js +84 -0
- package/build/utils/formatters/project-formatter.js +55 -0
- package/build/utils/formatters/search-formatter.js +62 -0
- package/build/utils/formatters/sprint-formatter.js +111 -0
- package/build/utils/text-processing.js +343 -0
- package/package.json +65 -0
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import axios from 'axios';
|
|
3
|
+
/**
|
|
4
|
+
* Health check script for container monitoring
|
|
5
|
+
* Verifies:
|
|
6
|
+
* 1. Environment variables are set
|
|
7
|
+
* 2. Jira API is accessible
|
|
8
|
+
* 3. Required directories exist and are writable
|
|
9
|
+
*/
|
|
10
|
+
export async function checkEnvironmentVariables() {
|
|
11
|
+
const required = ['JIRA_API_TOKEN', 'JIRA_EMAIL', 'JIRA_HOST'];
|
|
12
|
+
const missing = required.filter(v => !process.env[v]);
|
|
13
|
+
if (missing.length > 0) {
|
|
14
|
+
console.error(`Missing required environment variables: ${missing.join(', ')}`);
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
return true;
|
|
18
|
+
}
|
|
19
|
+
export async function checkJiraAccess() {
|
|
20
|
+
const baseURL = `https://${process.env.JIRA_HOST}/rest/api/3`;
|
|
21
|
+
const auth = {
|
|
22
|
+
username: process.env.JIRA_EMAIL,
|
|
23
|
+
password: process.env.JIRA_API_TOKEN
|
|
24
|
+
};
|
|
25
|
+
try {
|
|
26
|
+
await axios.get(`${baseURL}/myself`, { auth });
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
console.error('Failed to access Jira API:', error);
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
export async function checkDirectories() {
|
|
35
|
+
const fs = await import('fs/promises');
|
|
36
|
+
const directories = ['/app/config', '/app/logs'];
|
|
37
|
+
try {
|
|
38
|
+
for (const dir of directories) {
|
|
39
|
+
await fs.access(dir, fs.constants.W_OK);
|
|
40
|
+
}
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
console.error('Directory check failed:', error);
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
export async function main() {
|
|
49
|
+
try {
|
|
50
|
+
const envCheck = await checkEnvironmentVariables();
|
|
51
|
+
const jiraCheck = await checkJiraAccess();
|
|
52
|
+
const dirCheck = await checkDirectories();
|
|
53
|
+
if (envCheck && jiraCheck && dirCheck) {
|
|
54
|
+
console.log('Health check passed');
|
|
55
|
+
process.exit(0);
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
console.error('Health check failed');
|
|
59
|
+
process.exit(1);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
console.error('Health check error:', error);
|
|
64
|
+
process.exit(1);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
main();
|
package/build/index.js
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
3
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
4
|
+
import { CallToolRequestSchema, ErrorCode, ListResourcesRequestSchema, ListResourceTemplatesRequestSchema, ListToolsRequestSchema, McpError, ReadResourceRequestSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
5
|
+
import { JiraClient } from './client/jira-client.js';
|
|
6
|
+
import { setupBoardHandlers } from './handlers/board-handlers.js';
|
|
7
|
+
import { setupFilterHandlers } from './handlers/filter-handlers.js';
|
|
8
|
+
import { setupIssueHandlers } from './handlers/issue-handlers.js';
|
|
9
|
+
import { setupProjectHandlers } from './handlers/project-handlers.js';
|
|
10
|
+
import { setupResourceHandlers } from './handlers/resource-handlers.js';
|
|
11
|
+
import { setupSprintHandlers } from './handlers/sprint-handlers.js';
|
|
12
|
+
import { toolSchemas } from './schemas/tool-schemas.js';
|
|
13
|
+
// Jira credentials from environment variables
|
|
14
|
+
const JIRA_EMAIL = process.env.JIRA_EMAIL;
|
|
15
|
+
const JIRA_API_TOKEN = process.env.JIRA_API_TOKEN;
|
|
16
|
+
const JIRA_HOST = process.env.JIRA_HOST;
|
|
17
|
+
if (!JIRA_EMAIL || !JIRA_API_TOKEN || !JIRA_HOST) {
|
|
18
|
+
throw new Error('Missing required Jira credentials in environment variables');
|
|
19
|
+
}
|
|
20
|
+
class JiraServer {
|
|
21
|
+
server;
|
|
22
|
+
jiraClient;
|
|
23
|
+
constructor() {
|
|
24
|
+
// Use environment-provided name or default to 'jira-cloud'
|
|
25
|
+
const serverName = process.env.MCP_SERVER_NAME || 'jira-cloud';
|
|
26
|
+
console.error(`Initializing Jira MCP server: ${serverName}`);
|
|
27
|
+
this.server = new Server({
|
|
28
|
+
name: serverName,
|
|
29
|
+
version: '0.1.0',
|
|
30
|
+
}, {
|
|
31
|
+
capabilities: {
|
|
32
|
+
tools: {},
|
|
33
|
+
resources: {},
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
this.jiraClient = new JiraClient({
|
|
37
|
+
host: JIRA_HOST,
|
|
38
|
+
email: JIRA_EMAIL,
|
|
39
|
+
apiToken: JIRA_API_TOKEN,
|
|
40
|
+
});
|
|
41
|
+
this.setupHandlers();
|
|
42
|
+
this.server.onerror = (error) => console.error('[MCP Error]', error);
|
|
43
|
+
process.on('SIGINT', async () => {
|
|
44
|
+
await this.server.close();
|
|
45
|
+
process.exit(0);
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
setupHandlers() {
|
|
49
|
+
// Set up required MCP protocol handlers
|
|
50
|
+
this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
51
|
+
tools: Object.entries(toolSchemas)
|
|
52
|
+
// Filter out the deprecated search_jira_issues tool
|
|
53
|
+
.filter(([key]) => key !== 'search_jira_issues')
|
|
54
|
+
.map(([key, schema]) => ({
|
|
55
|
+
name: key,
|
|
56
|
+
description: schema.description,
|
|
57
|
+
inputSchema: {
|
|
58
|
+
type: 'object',
|
|
59
|
+
properties: schema.inputSchema.properties,
|
|
60
|
+
...(('required' in schema.inputSchema) ? { required: schema.inputSchema.required } : {}),
|
|
61
|
+
},
|
|
62
|
+
})),
|
|
63
|
+
}));
|
|
64
|
+
// Set up resource handlers
|
|
65
|
+
const resourceHandlers = setupResourceHandlers(this.jiraClient);
|
|
66
|
+
this.server.setRequestHandler(ListResourcesRequestSchema, resourceHandlers.listResources);
|
|
67
|
+
this.server.setRequestHandler(ListResourceTemplatesRequestSchema, resourceHandlers.listResourceTemplates);
|
|
68
|
+
this.server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
69
|
+
return resourceHandlers.readResource(request.params.uri);
|
|
70
|
+
});
|
|
71
|
+
// Set up tool handlers
|
|
72
|
+
this.server.setRequestHandler(CallToolRequestSchema, async (request, _extra) => {
|
|
73
|
+
console.error('Received request:', JSON.stringify(request, null, 2));
|
|
74
|
+
const { name } = request.params;
|
|
75
|
+
console.error(`Handling tool request: ${name}`);
|
|
76
|
+
try {
|
|
77
|
+
let response;
|
|
78
|
+
// Issue-related tools
|
|
79
|
+
if (['manage_jira_issue'].includes(name)) {
|
|
80
|
+
response = await setupIssueHandlers(this.server, this.jiraClient, request);
|
|
81
|
+
}
|
|
82
|
+
// Project-related tools
|
|
83
|
+
else if (['manage_jira_project'].includes(name)) {
|
|
84
|
+
response = await setupProjectHandlers(this.server, this.jiraClient, request);
|
|
85
|
+
}
|
|
86
|
+
// Board-related tools
|
|
87
|
+
else if (['manage_jira_board'].includes(name)) {
|
|
88
|
+
response = await setupBoardHandlers(this.server, this.jiraClient, request);
|
|
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}`);
|
|
117
|
+
throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${name}`);
|
|
118
|
+
}
|
|
119
|
+
// Ensure we always return a valid response
|
|
120
|
+
if (!response) {
|
|
121
|
+
throw new McpError(ErrorCode.InternalError, `No response from handler for tool: ${name}`);
|
|
122
|
+
}
|
|
123
|
+
return response;
|
|
124
|
+
}
|
|
125
|
+
catch (error) {
|
|
126
|
+
console.error('Error handling request:', error);
|
|
127
|
+
if (error instanceof McpError) {
|
|
128
|
+
throw error;
|
|
129
|
+
}
|
|
130
|
+
throw new McpError(ErrorCode.InternalError, 'Internal server error');
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
async run() {
|
|
135
|
+
const transport = new StdioServerTransport();
|
|
136
|
+
await this.server.connect(transport);
|
|
137
|
+
console.error('Jira MCP server running on stdio');
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
const server = new JiraServer();
|
|
141
|
+
server.run().catch(console.error);
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
// Sprint Management Schemas
|
|
3
|
+
export const CreateJiraSprintSchema = z.object({
|
|
4
|
+
method: z.literal('tools/call'),
|
|
5
|
+
params: z.object({
|
|
6
|
+
name: z.literal('create_jira_sprint'),
|
|
7
|
+
arguments: z.object({
|
|
8
|
+
boardId: z.number(),
|
|
9
|
+
name: z.string(),
|
|
10
|
+
startDate: z.string().optional(),
|
|
11
|
+
endDate: z.string().optional(),
|
|
12
|
+
goal: z.string().optional(),
|
|
13
|
+
}),
|
|
14
|
+
}),
|
|
15
|
+
});
|
|
16
|
+
export const GetJiraSprintSchema = z.object({
|
|
17
|
+
method: z.literal('tools/call'),
|
|
18
|
+
params: z.object({
|
|
19
|
+
name: z.literal('get_jira_sprint'),
|
|
20
|
+
arguments: z.object({
|
|
21
|
+
sprintId: z.number(),
|
|
22
|
+
expand: z.array(z.string()).optional(),
|
|
23
|
+
}),
|
|
24
|
+
}),
|
|
25
|
+
});
|
|
26
|
+
export const ListJiraSprintsSchema = z.object({
|
|
27
|
+
method: z.literal('tools/call'),
|
|
28
|
+
params: z.object({
|
|
29
|
+
name: z.literal('list_jira_sprints'),
|
|
30
|
+
arguments: z.object({
|
|
31
|
+
boardId: z.number(),
|
|
32
|
+
state: z.enum(['future', 'active', 'closed']).optional(),
|
|
33
|
+
startAt: z.number().optional(),
|
|
34
|
+
maxResults: z.number().optional(),
|
|
35
|
+
expand: z.array(z.string()).optional(),
|
|
36
|
+
}),
|
|
37
|
+
}),
|
|
38
|
+
});
|
|
39
|
+
export const UpdateJiraSprintSchema = z.object({
|
|
40
|
+
method: z.literal('tools/call'),
|
|
41
|
+
params: z.object({
|
|
42
|
+
name: z.literal('update_jira_sprint'),
|
|
43
|
+
arguments: z.object({
|
|
44
|
+
sprintId: z.number(),
|
|
45
|
+
name: z.string().optional(),
|
|
46
|
+
goal: z.string().optional(),
|
|
47
|
+
startDate: z.string().optional(),
|
|
48
|
+
endDate: z.string().optional(),
|
|
49
|
+
state: z.enum(['future', 'active', 'closed']).optional(),
|
|
50
|
+
}),
|
|
51
|
+
}),
|
|
52
|
+
});
|
|
53
|
+
export const DeleteJiraSprintSchema = z.object({
|
|
54
|
+
method: z.literal('tools/call'),
|
|
55
|
+
params: z.object({
|
|
56
|
+
name: z.literal('delete_jira_sprint'),
|
|
57
|
+
arguments: z.object({
|
|
58
|
+
sprintId: z.number(),
|
|
59
|
+
}),
|
|
60
|
+
}),
|
|
61
|
+
});
|
|
62
|
+
export const UpdateSprintIssuesSchema = z.object({
|
|
63
|
+
method: z.literal('tools/call'),
|
|
64
|
+
params: z.object({
|
|
65
|
+
name: z.literal('update_sprint_issues'),
|
|
66
|
+
arguments: z.object({
|
|
67
|
+
sprintId: z.number(),
|
|
68
|
+
add: z.array(z.string()).optional(),
|
|
69
|
+
remove: z.array(z.string()).optional(),
|
|
70
|
+
}),
|
|
71
|
+
}),
|
|
72
|
+
});
|
|
73
|
+
// Consolidated API request schemas
|
|
74
|
+
export const GetJiraIssueSchema = z.object({
|
|
75
|
+
method: z.literal('tools/call'),
|
|
76
|
+
params: z.object({
|
|
77
|
+
name: z.literal('get_jira_issue'),
|
|
78
|
+
arguments: z.object({
|
|
79
|
+
issueKey: z.string(),
|
|
80
|
+
expand: z.array(z.string()).optional(),
|
|
81
|
+
}),
|
|
82
|
+
}),
|
|
83
|
+
});
|
|
84
|
+
export const GetJiraProjectSchema = z.object({
|
|
85
|
+
method: z.literal('tools/call'),
|
|
86
|
+
params: z.object({
|
|
87
|
+
name: z.literal('get_jira_project'),
|
|
88
|
+
arguments: z.object({
|
|
89
|
+
projectKey: z.string(),
|
|
90
|
+
expand: z.array(z.string()).optional(),
|
|
91
|
+
include_status_counts: z.boolean().optional(),
|
|
92
|
+
}),
|
|
93
|
+
}),
|
|
94
|
+
});
|
|
95
|
+
export const GetJiraBoardSchema = z.object({
|
|
96
|
+
method: z.literal('tools/call'),
|
|
97
|
+
params: z.object({
|
|
98
|
+
name: z.literal('get_jira_board'),
|
|
99
|
+
arguments: z.object({
|
|
100
|
+
boardId: z.number(),
|
|
101
|
+
expand: z.array(z.string()).optional(),
|
|
102
|
+
}),
|
|
103
|
+
}),
|
|
104
|
+
});
|
|
105
|
+
export const SearchJiraIssuesSchema = z.object({
|
|
106
|
+
method: z.literal('tools/call'),
|
|
107
|
+
params: z.object({
|
|
108
|
+
name: z.literal('search_jira_issues'),
|
|
109
|
+
arguments: z.object({
|
|
110
|
+
jql: z.string(),
|
|
111
|
+
startAt: z.number().optional(),
|
|
112
|
+
maxResults: z.number().optional(),
|
|
113
|
+
expand: z.array(z.string()).optional(),
|
|
114
|
+
}),
|
|
115
|
+
}),
|
|
116
|
+
});
|
|
117
|
+
export const ListJiraProjectsSchema = z.object({
|
|
118
|
+
method: z.literal('tools/call'),
|
|
119
|
+
params: z.object({
|
|
120
|
+
name: z.literal('list_jira_projects'),
|
|
121
|
+
arguments: z.object({
|
|
122
|
+
include_status_counts: z.boolean().optional(),
|
|
123
|
+
}),
|
|
124
|
+
}),
|
|
125
|
+
});
|
|
126
|
+
export const ListJiraBoardsSchema = z.object({
|
|
127
|
+
method: z.literal('tools/call'),
|
|
128
|
+
params: z.object({
|
|
129
|
+
name: z.literal('list_jira_boards'),
|
|
130
|
+
arguments: z.object({
|
|
131
|
+
include_sprints: z.boolean().optional(),
|
|
132
|
+
}),
|
|
133
|
+
}),
|
|
134
|
+
});
|
|
135
|
+
export const CreateJiraIssueSchema = z.object({
|
|
136
|
+
method: z.literal('tools/call'),
|
|
137
|
+
params: z.object({
|
|
138
|
+
name: z.literal('create_jira_issue'),
|
|
139
|
+
arguments: z.object({
|
|
140
|
+
projectKey: z.string(),
|
|
141
|
+
summary: z.string(),
|
|
142
|
+
description: z.string().optional(),
|
|
143
|
+
issueType: z.string(),
|
|
144
|
+
priority: z.string().optional(),
|
|
145
|
+
assignee: z.string().optional(),
|
|
146
|
+
labels: z.array(z.string()).optional(),
|
|
147
|
+
customFields: z.record(z.any()).optional(),
|
|
148
|
+
}),
|
|
149
|
+
}),
|
|
150
|
+
});
|
|
151
|
+
export const UpdateJiraIssueSchema = z.object({
|
|
152
|
+
method: z.literal('tools/call'),
|
|
153
|
+
params: z.object({
|
|
154
|
+
name: z.literal('update_jira_issue'),
|
|
155
|
+
arguments: z.object({
|
|
156
|
+
issueKey: z.string(),
|
|
157
|
+
summary: z.string().optional(),
|
|
158
|
+
description: z.string().optional(),
|
|
159
|
+
parent: z.union([z.string(), z.null()]).optional(),
|
|
160
|
+
assignee: z.string().optional(),
|
|
161
|
+
priority: z.string().optional(),
|
|
162
|
+
labels: z.array(z.string()).optional(),
|
|
163
|
+
customFields: z.record(z.any()).optional(),
|
|
164
|
+
}),
|
|
165
|
+
}),
|
|
166
|
+
});
|
|
167
|
+
export const TransitionJiraIssueSchema = z.object({
|
|
168
|
+
method: z.literal('tools/call'),
|
|
169
|
+
params: z.object({
|
|
170
|
+
name: z.literal('transition_jira_issue'),
|
|
171
|
+
arguments: z.object({
|
|
172
|
+
issueKey: z.string(),
|
|
173
|
+
transitionId: z.string(),
|
|
174
|
+
comment: z.string().optional(),
|
|
175
|
+
}),
|
|
176
|
+
}),
|
|
177
|
+
});
|
|
178
|
+
export const AddJiraCommentSchema = z.object({
|
|
179
|
+
method: z.literal('tools/call'),
|
|
180
|
+
params: z.object({
|
|
181
|
+
name: z.literal('add_jira_comment'),
|
|
182
|
+
arguments: z.object({
|
|
183
|
+
issueKey: z.string(),
|
|
184
|
+
body: z.string(),
|
|
185
|
+
}),
|
|
186
|
+
}),
|
|
187
|
+
});
|