@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,326 @@
|
|
|
1
|
+
import { ErrorCode, McpError } from '@modelcontextprotocol/sdk/types.js';
|
|
2
|
+
import { BoardFormatter } from '../utils/formatters/index.js';
|
|
3
|
+
// Helper function to normalize parameter names (support both snake_case and camelCase)
|
|
4
|
+
function normalizeArgs(args) {
|
|
5
|
+
const normalized = {};
|
|
6
|
+
for (const [key, value] of Object.entries(args)) {
|
|
7
|
+
// Convert snake_case to camelCase
|
|
8
|
+
if (key === 'board_id') {
|
|
9
|
+
normalized['boardId'] = value;
|
|
10
|
+
}
|
|
11
|
+
else if (key === 'include_sprints') {
|
|
12
|
+
normalized['includeSprints'] = value;
|
|
13
|
+
}
|
|
14
|
+
else if (key === 'project_key') {
|
|
15
|
+
normalized['projectKey'] = value;
|
|
16
|
+
}
|
|
17
|
+
else if (key === 'start_at') {
|
|
18
|
+
normalized['startAt'] = value;
|
|
19
|
+
}
|
|
20
|
+
else if (key === 'max_results') {
|
|
21
|
+
normalized['maxResults'] = value;
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
normalized[key] = value;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return normalized;
|
|
28
|
+
}
|
|
29
|
+
// Validate the consolidated board management arguments
|
|
30
|
+
function validateManageJiraBoardArgs(args) {
|
|
31
|
+
if (typeof args !== 'object' || args === null) {
|
|
32
|
+
throw new McpError(ErrorCode.InvalidParams, 'Invalid manage_jira_board arguments: Expected an object with an operation parameter');
|
|
33
|
+
}
|
|
34
|
+
const normalizedArgs = normalizeArgs(args);
|
|
35
|
+
// Validate operation parameter
|
|
36
|
+
if (typeof normalizedArgs.operation !== 'string' ||
|
|
37
|
+
!['get', 'list', 'create', 'update', 'delete', 'get_configuration'].includes(normalizedArgs.operation)) {
|
|
38
|
+
throw new McpError(ErrorCode.InvalidParams, 'Invalid operation parameter. Valid values are: get, list, create, update, delete, get_configuration');
|
|
39
|
+
}
|
|
40
|
+
// Validate parameters based on operation
|
|
41
|
+
switch (normalizedArgs.operation) {
|
|
42
|
+
case 'get':
|
|
43
|
+
case 'update':
|
|
44
|
+
case 'delete':
|
|
45
|
+
case 'get_configuration':
|
|
46
|
+
if (typeof normalizedArgs.boardId !== 'number') {
|
|
47
|
+
throw new McpError(ErrorCode.InvalidParams, `Missing or invalid boardId parameter. Please provide a valid board ID as a number for the ${normalizedArgs.operation} operation.`);
|
|
48
|
+
}
|
|
49
|
+
break;
|
|
50
|
+
case 'create':
|
|
51
|
+
if (typeof normalizedArgs.name !== 'string' || normalizedArgs.name.trim() === '') {
|
|
52
|
+
throw new McpError(ErrorCode.InvalidParams, 'Missing or invalid name parameter. Please provide a valid board name for the create operation.');
|
|
53
|
+
}
|
|
54
|
+
if (typeof normalizedArgs.type !== 'string' || !['scrum', 'kanban'].includes(normalizedArgs.type)) {
|
|
55
|
+
throw new McpError(ErrorCode.InvalidParams, 'Missing or invalid type parameter. Please provide a valid board type (scrum or kanban) for the create operation.');
|
|
56
|
+
}
|
|
57
|
+
if (typeof normalizedArgs.projectKey !== 'string' || normalizedArgs.projectKey.trim() === '') {
|
|
58
|
+
throw new McpError(ErrorCode.InvalidParams, 'Missing or invalid projectKey parameter. Please provide a valid project key for the create operation.');
|
|
59
|
+
}
|
|
60
|
+
break;
|
|
61
|
+
}
|
|
62
|
+
// Validate pagination parameters for list operation
|
|
63
|
+
if (normalizedArgs.operation === 'list') {
|
|
64
|
+
if (normalizedArgs.startAt !== undefined && typeof normalizedArgs.startAt !== 'number') {
|
|
65
|
+
throw new McpError(ErrorCode.InvalidParams, 'Invalid startAt parameter. Please provide a valid number.');
|
|
66
|
+
}
|
|
67
|
+
if (normalizedArgs.maxResults !== undefined && typeof normalizedArgs.maxResults !== 'number') {
|
|
68
|
+
throw new McpError(ErrorCode.InvalidParams, 'Invalid maxResults parameter. Please provide a valid number.');
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
// Validate expand parameter
|
|
72
|
+
if (normalizedArgs.expand !== undefined) {
|
|
73
|
+
if (!Array.isArray(normalizedArgs.expand)) {
|
|
74
|
+
throw new McpError(ErrorCode.InvalidParams, 'Invalid expand parameter. Expected an array of strings.');
|
|
75
|
+
}
|
|
76
|
+
const validExpansions = ['sprints', 'issues', 'configuration'];
|
|
77
|
+
for (const expansion of normalizedArgs.expand) {
|
|
78
|
+
if (typeof expansion !== 'string' || !validExpansions.includes(expansion)) {
|
|
79
|
+
throw new McpError(ErrorCode.InvalidParams, `Invalid expansion: ${expansion}. Valid expansions are: ${validExpansions.join(', ')}`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
// Validate include_sprints parameter
|
|
84
|
+
if (normalizedArgs.includeSprints !== undefined && typeof normalizedArgs.includeSprints !== 'boolean') {
|
|
85
|
+
throw new McpError(ErrorCode.InvalidParams, 'Invalid include_sprints parameter. Expected a boolean value.');
|
|
86
|
+
}
|
|
87
|
+
return true;
|
|
88
|
+
}
|
|
89
|
+
// Handler functions for each operation
|
|
90
|
+
async function handleGetBoard(jiraClient, args) {
|
|
91
|
+
const boardId = args.boardId;
|
|
92
|
+
// Parse expansion options
|
|
93
|
+
const expansionOptions = {};
|
|
94
|
+
if (args.expand) {
|
|
95
|
+
for (const expansion of args.expand) {
|
|
96
|
+
expansionOptions[expansion] = true;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
// If include_sprints is true, add sprints to expansions
|
|
100
|
+
if (args.include_sprints === true) {
|
|
101
|
+
expansionOptions.sprints = true;
|
|
102
|
+
}
|
|
103
|
+
// Get all boards and find the requested one
|
|
104
|
+
const boards = await jiraClient.listBoards();
|
|
105
|
+
const board = boards.find((b) => b.id === boardId);
|
|
106
|
+
if (!board) {
|
|
107
|
+
throw new McpError(ErrorCode.InvalidRequest, `Board not found: ${boardId}`);
|
|
108
|
+
}
|
|
109
|
+
// Convert to BoardData format
|
|
110
|
+
const boardData = {
|
|
111
|
+
...board
|
|
112
|
+
};
|
|
113
|
+
// Handle expansions
|
|
114
|
+
if (expansionOptions.sprints) {
|
|
115
|
+
try {
|
|
116
|
+
// Get sprints for this board
|
|
117
|
+
const sprints = await jiraClient.listBoardSprints(boardId);
|
|
118
|
+
// Add sprints to the response
|
|
119
|
+
boardData.sprints = sprints;
|
|
120
|
+
}
|
|
121
|
+
catch (error) {
|
|
122
|
+
console.error(`Error getting sprints for board ${boardId}:`, error);
|
|
123
|
+
// Continue even if sprints fail
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
// Format the response
|
|
127
|
+
const formattedResponse = BoardFormatter.formatBoard(boardData, expansionOptions);
|
|
128
|
+
return {
|
|
129
|
+
content: [
|
|
130
|
+
{
|
|
131
|
+
type: 'text',
|
|
132
|
+
text: JSON.stringify(formattedResponse, null, 2),
|
|
133
|
+
},
|
|
134
|
+
],
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
async function handleListBoards(jiraClient, args) {
|
|
138
|
+
// Set default pagination values
|
|
139
|
+
const startAt = args.startAt !== undefined ? args.startAt : 0;
|
|
140
|
+
const maxResults = args.maxResults !== undefined ? args.maxResults : 50;
|
|
141
|
+
const includeSprints = args.include_sprints === true;
|
|
142
|
+
// Get all boards
|
|
143
|
+
const boards = await jiraClient.listBoards();
|
|
144
|
+
// Apply pagination
|
|
145
|
+
const paginatedBoards = boards.slice(startAt, startAt + maxResults);
|
|
146
|
+
// Convert to BoardData format
|
|
147
|
+
const boardDataList = paginatedBoards.map(board => ({
|
|
148
|
+
...board
|
|
149
|
+
}));
|
|
150
|
+
// If sprints are requested, get them for each board
|
|
151
|
+
if (includeSprints) {
|
|
152
|
+
// This would be more efficient with a batch API call, but for now we'll do it sequentially
|
|
153
|
+
for (const board of boardDataList) {
|
|
154
|
+
try {
|
|
155
|
+
// Get active sprints for this board
|
|
156
|
+
const sprints = await jiraClient.listBoardSprints(board.id);
|
|
157
|
+
// Add sprints to the board data
|
|
158
|
+
board.sprints = sprints;
|
|
159
|
+
}
|
|
160
|
+
catch (error) {
|
|
161
|
+
console.error(`Error getting sprints for board ${board.id}:`, error);
|
|
162
|
+
// Continue with other boards even if one fails
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
// Format the response
|
|
167
|
+
const formattedBoards = boardDataList.map(board => BoardFormatter.formatBoard(board, { sprints: includeSprints }));
|
|
168
|
+
// Create a response with pagination metadata
|
|
169
|
+
const response = {
|
|
170
|
+
data: formattedBoards,
|
|
171
|
+
_metadata: {
|
|
172
|
+
pagination: {
|
|
173
|
+
startAt,
|
|
174
|
+
maxResults,
|
|
175
|
+
total: boards.length,
|
|
176
|
+
hasMore: startAt + maxResults < boards.length,
|
|
177
|
+
},
|
|
178
|
+
},
|
|
179
|
+
};
|
|
180
|
+
return {
|
|
181
|
+
content: [
|
|
182
|
+
{
|
|
183
|
+
type: 'text',
|
|
184
|
+
text: JSON.stringify(response, null, 2),
|
|
185
|
+
},
|
|
186
|
+
],
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
async function handleCreateBoard(_jiraClient, _args) {
|
|
190
|
+
// Note: This is a placeholder. The current JiraClient doesn't have a createBoard method.
|
|
191
|
+
// You would need to implement this in the JiraClient class.
|
|
192
|
+
throw new McpError(ErrorCode.InternalError, 'Create board operation is not yet implemented');
|
|
193
|
+
// When implemented, it would look something like this:
|
|
194
|
+
/*
|
|
195
|
+
const result = await _jiraClient.createBoard({
|
|
196
|
+
name: _args.name!,
|
|
197
|
+
type: _args.type!,
|
|
198
|
+
projectKey: _args.projectKey!
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
// Get the created board to return
|
|
202
|
+
const createdBoard = await _jiraClient.getBoard(result.id);
|
|
203
|
+
const formattedResponse = BoardFormatter.formatBoard(createdBoard);
|
|
204
|
+
|
|
205
|
+
return {
|
|
206
|
+
content: [
|
|
207
|
+
{
|
|
208
|
+
type: 'text',
|
|
209
|
+
text: JSON.stringify(formattedResponse, null, 2),
|
|
210
|
+
},
|
|
211
|
+
],
|
|
212
|
+
};
|
|
213
|
+
*/
|
|
214
|
+
}
|
|
215
|
+
async function handleUpdateBoard(_jiraClient, _args) {
|
|
216
|
+
// Note: This is a placeholder. The current JiraClient doesn't have an updateBoard method.
|
|
217
|
+
// You would need to implement this in the JiraClient class.
|
|
218
|
+
throw new McpError(ErrorCode.InternalError, 'Update board operation is not yet implemented');
|
|
219
|
+
// When implemented, it would look something like this:
|
|
220
|
+
/*
|
|
221
|
+
await _jiraClient.updateBoard(
|
|
222
|
+
_args.boardId!,
|
|
223
|
+
_args.name
|
|
224
|
+
);
|
|
225
|
+
|
|
226
|
+
// Get the updated board to return
|
|
227
|
+
const updatedBoard = await _jiraClient.getBoard(_args.boardId!);
|
|
228
|
+
const formattedResponse = BoardFormatter.formatBoard(updatedBoard);
|
|
229
|
+
|
|
230
|
+
return {
|
|
231
|
+
content: [
|
|
232
|
+
{
|
|
233
|
+
type: 'text',
|
|
234
|
+
text: JSON.stringify(formattedResponse, null, 2),
|
|
235
|
+
},
|
|
236
|
+
],
|
|
237
|
+
};
|
|
238
|
+
*/
|
|
239
|
+
}
|
|
240
|
+
async function handleDeleteBoard(_jiraClient, _args) {
|
|
241
|
+
// Note: This is a placeholder. The current JiraClient doesn't have a deleteBoard method.
|
|
242
|
+
// You would need to implement this in the JiraClient class.
|
|
243
|
+
throw new McpError(ErrorCode.InternalError, 'Delete board operation is not yet implemented');
|
|
244
|
+
// When implemented, it would look something like this:
|
|
245
|
+
/*
|
|
246
|
+
await _jiraClient.deleteBoard(_args.boardId!);
|
|
247
|
+
|
|
248
|
+
return {
|
|
249
|
+
content: [
|
|
250
|
+
{
|
|
251
|
+
type: 'text',
|
|
252
|
+
text: JSON.stringify({
|
|
253
|
+
success: true,
|
|
254
|
+
message: `Board ${_args.boardId} has been deleted successfully.`,
|
|
255
|
+
}, null, 2),
|
|
256
|
+
},
|
|
257
|
+
],
|
|
258
|
+
};
|
|
259
|
+
*/
|
|
260
|
+
}
|
|
261
|
+
async function handleGetBoardConfiguration(_jiraClient, _args) {
|
|
262
|
+
// Note: This is a placeholder. The current JiraClient doesn't have a getBoardConfiguration method.
|
|
263
|
+
// You would need to implement this in the JiraClient class.
|
|
264
|
+
throw new McpError(ErrorCode.InternalError, 'Get board configuration operation is not yet implemented');
|
|
265
|
+
// When implemented, it would look something like this:
|
|
266
|
+
/*
|
|
267
|
+
const configuration = await _jiraClient.getBoardConfiguration(_args.boardId!);
|
|
268
|
+
|
|
269
|
+
return {
|
|
270
|
+
content: [
|
|
271
|
+
{
|
|
272
|
+
type: 'text',
|
|
273
|
+
text: JSON.stringify(configuration, null, 2),
|
|
274
|
+
},
|
|
275
|
+
],
|
|
276
|
+
};
|
|
277
|
+
*/
|
|
278
|
+
}
|
|
279
|
+
// Main handler function
|
|
280
|
+
export async function setupBoardHandlers(server, jiraClient, request) {
|
|
281
|
+
console.error('Handling board request...');
|
|
282
|
+
const { name } = request.params;
|
|
283
|
+
const args = request.params.arguments || {};
|
|
284
|
+
// Handle the consolidated board management tool
|
|
285
|
+
if (name === 'manage_jira_board') {
|
|
286
|
+
// Normalize arguments to support both snake_case and camelCase
|
|
287
|
+
const normalizedArgs = normalizeArgs(args);
|
|
288
|
+
// Validate arguments
|
|
289
|
+
if (!validateManageJiraBoardArgs(normalizedArgs)) {
|
|
290
|
+
throw new McpError(ErrorCode.InvalidParams, 'Invalid manage_jira_board arguments');
|
|
291
|
+
}
|
|
292
|
+
// Process the operation
|
|
293
|
+
switch (normalizedArgs.operation) {
|
|
294
|
+
case 'get': {
|
|
295
|
+
console.error('Processing get board operation');
|
|
296
|
+
return await handleGetBoard(jiraClient, normalizedArgs);
|
|
297
|
+
}
|
|
298
|
+
case 'list': {
|
|
299
|
+
console.error('Processing list boards operation');
|
|
300
|
+
return await handleListBoards(jiraClient, normalizedArgs);
|
|
301
|
+
}
|
|
302
|
+
case 'create': {
|
|
303
|
+
console.error('Processing create board operation');
|
|
304
|
+
return await handleCreateBoard(jiraClient, normalizedArgs);
|
|
305
|
+
}
|
|
306
|
+
case 'update': {
|
|
307
|
+
console.error('Processing update board operation');
|
|
308
|
+
return await handleUpdateBoard(jiraClient, normalizedArgs);
|
|
309
|
+
}
|
|
310
|
+
case 'delete': {
|
|
311
|
+
console.error('Processing delete board operation');
|
|
312
|
+
return await handleDeleteBoard(jiraClient, normalizedArgs);
|
|
313
|
+
}
|
|
314
|
+
case 'get_configuration': {
|
|
315
|
+
console.error('Processing get board configuration operation');
|
|
316
|
+
return await handleGetBoardConfiguration(jiraClient, normalizedArgs);
|
|
317
|
+
}
|
|
318
|
+
default: {
|
|
319
|
+
console.error(`Unknown operation: ${normalizedArgs.operation}`);
|
|
320
|
+
throw new McpError(ErrorCode.MethodNotFound, `Unknown operation: ${normalizedArgs.operation}`);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
console.error(`Unknown tool requested: ${name}`);
|
|
325
|
+
throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${name}`);
|
|
326
|
+
}
|