@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.
@@ -1,31 +1,7 @@
1
1
  import { ErrorCode, McpError } from '@modelcontextprotocol/sdk/types.js';
2
2
  import { MarkdownRenderer } from '../mcp/markdown-renderer.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
- }
3
+ import { boardNextSteps } from '../utils/next-steps.js';
4
+ import { normalizeArgs } from '../utils/normalize-args.js';
29
5
  // Validate the consolidated board management arguments
30
6
  function validateManageJiraBoardArgs(args) {
31
7
  if (typeof args !== 'object' || args === null) {
@@ -34,28 +10,14 @@ function validateManageJiraBoardArgs(args) {
34
10
  const normalizedArgs = normalizeArgs(args);
35
11
  // Validate operation parameter
36
12
  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');
13
+ !['get', 'list'].includes(normalizedArgs.operation)) {
14
+ throw new McpError(ErrorCode.InvalidParams, 'Invalid operation parameter. Valid values are: get, list');
39
15
  }
40
16
  // Validate parameters based on operation
41
17
  switch (normalizedArgs.operation) {
42
18
  case 'get':
43
- case 'update':
44
- case 'delete':
45
- case 'get_configuration':
46
19
  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.');
20
+ throw new McpError(ErrorCode.InvalidParams, 'Missing or invalid boardId parameter. Please provide a valid board ID as a number for the get operation.');
59
21
  }
60
22
  break;
61
23
  }
@@ -97,7 +59,7 @@ async function handleGetBoard(jiraClient, args) {
97
59
  }
98
60
  }
99
61
  // If include_sprints is true, add sprints to expansions
100
- if (args.include_sprints === true) {
62
+ if (args.includeSprints === true) {
101
63
  expansionOptions.sprints = true;
102
64
  }
103
65
  // Get all boards and find the requested one
@@ -143,7 +105,7 @@ async function handleGetBoard(jiraClient, args) {
143
105
  content: [
144
106
  {
145
107
  type: 'text',
146
- text: markdown,
108
+ text: markdown + boardNextSteps('get', boardId),
147
109
  },
148
110
  ],
149
111
  };
@@ -152,7 +114,7 @@ async function handleListBoards(jiraClient, args) {
152
114
  // Set default pagination values
153
115
  const startAt = args.startAt !== undefined ? args.startAt : 0;
154
116
  const maxResults = args.maxResults !== undefined ? args.maxResults : 50;
155
- const includeSprints = args.include_sprints === true;
117
+ const includeSprints = args.includeSprints === true;
156
118
  // Get all boards
157
119
  const boards = await jiraClient.listBoards();
158
120
  // Apply pagination
@@ -204,6 +166,7 @@ async function handleListBoards(jiraClient, args) {
204
166
  else {
205
167
  markdown += `Showing all ${boardDataList.length} board${boardDataList.length !== 1 ? 's' : ''}`;
206
168
  }
169
+ markdown += boardNextSteps('list');
207
170
  return {
208
171
  content: [
209
172
  {
@@ -213,98 +176,8 @@ async function handleListBoards(jiraClient, args) {
213
176
  ],
214
177
  };
215
178
  }
216
- async function handleCreateBoard(_jiraClient, _args) {
217
- // Note: This is a placeholder. The current JiraClient doesn't have a createBoard method.
218
- // You would need to implement this in the JiraClient class.
219
- throw new McpError(ErrorCode.InternalError, 'Create board operation is not yet implemented');
220
- // When implemented, it would look something like this:
221
- /*
222
- const result = await _jiraClient.createBoard({
223
- name: _args.name!,
224
- type: _args.type!,
225
- projectKey: _args.projectKey!
226
- });
227
-
228
- // Get the created board to return
229
- const createdBoard = await _jiraClient.getBoard(result.id);
230
- const formattedResponse = BoardFormatter.formatBoard(createdBoard);
231
-
232
- return {
233
- content: [
234
- {
235
- type: 'text',
236
- text: JSON.stringify(formattedResponse, null, 2),
237
- },
238
- ],
239
- };
240
- */
241
- }
242
- async function handleUpdateBoard(_jiraClient, _args) {
243
- // Note: This is a placeholder. The current JiraClient doesn't have an updateBoard method.
244
- // You would need to implement this in the JiraClient class.
245
- throw new McpError(ErrorCode.InternalError, 'Update board operation is not yet implemented');
246
- // When implemented, it would look something like this:
247
- /*
248
- await _jiraClient.updateBoard(
249
- _args.boardId!,
250
- _args.name
251
- );
252
-
253
- // Get the updated board to return
254
- const updatedBoard = await _jiraClient.getBoard(_args.boardId!);
255
- const formattedResponse = BoardFormatter.formatBoard(updatedBoard);
256
-
257
- return {
258
- content: [
259
- {
260
- type: 'text',
261
- text: JSON.stringify(formattedResponse, null, 2),
262
- },
263
- ],
264
- };
265
- */
266
- }
267
- async function handleDeleteBoard(_jiraClient, _args) {
268
- // Note: This is a placeholder. The current JiraClient doesn't have a deleteBoard method.
269
- // You would need to implement this in the JiraClient class.
270
- throw new McpError(ErrorCode.InternalError, 'Delete board operation is not yet implemented');
271
- // When implemented, it would look something like this:
272
- /*
273
- await _jiraClient.deleteBoard(_args.boardId!);
274
-
275
- return {
276
- content: [
277
- {
278
- type: 'text',
279
- text: JSON.stringify({
280
- success: true,
281
- message: `Board ${_args.boardId} has been deleted successfully.`,
282
- }, null, 2),
283
- },
284
- ],
285
- };
286
- */
287
- }
288
- async function handleGetBoardConfiguration(_jiraClient, _args) {
289
- // Note: This is a placeholder. The current JiraClient doesn't have a getBoardConfiguration method.
290
- // You would need to implement this in the JiraClient class.
291
- throw new McpError(ErrorCode.InternalError, 'Get board configuration operation is not yet implemented');
292
- // When implemented, it would look something like this:
293
- /*
294
- const configuration = await _jiraClient.getBoardConfiguration(_args.boardId!);
295
-
296
- return {
297
- content: [
298
- {
299
- type: 'text',
300
- text: JSON.stringify(configuration, null, 2),
301
- },
302
- ],
303
- };
304
- */
305
- }
306
179
  // Main handler function
307
- export async function setupBoardHandlers(server, jiraClient, request) {
180
+ export async function handleBoardRequest(jiraClient, request) {
308
181
  console.error('Handling board request...');
309
182
  const { name } = request.params;
310
183
  const args = request.params.arguments || {};
@@ -326,22 +199,6 @@ export async function setupBoardHandlers(server, jiraClient, request) {
326
199
  console.error('Processing list boards operation');
327
200
  return await handleListBoards(jiraClient, normalizedArgs);
328
201
  }
329
- case 'create': {
330
- console.error('Processing create board operation');
331
- return await handleCreateBoard(jiraClient, normalizedArgs);
332
- }
333
- case 'update': {
334
- console.error('Processing update board operation');
335
- return await handleUpdateBoard(jiraClient, normalizedArgs);
336
- }
337
- case 'delete': {
338
- console.error('Processing delete board operation');
339
- return await handleDeleteBoard(jiraClient, normalizedArgs);
340
- }
341
- case 'get_configuration': {
342
- console.error('Processing get board configuration operation');
343
- return await handleGetBoardConfiguration(jiraClient, normalizedArgs);
344
- }
345
202
  default: {
346
203
  console.error(`Unknown operation: ${normalizedArgs.operation}`);
347
204
  throw new McpError(ErrorCode.MethodNotFound, `Unknown operation: ${normalizedArgs.operation}`);
@@ -1,28 +1,7 @@
1
1
  import { ErrorCode, McpError } from '@modelcontextprotocol/sdk/types.js';
2
2
  import { MarkdownRenderer } from '../mcp/markdown-renderer.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 === 'filter_id') {
9
- normalized['filterId'] = value;
10
- }
11
- else if (key === 'share_permissions') {
12
- normalized['sharePermissions'] = value;
13
- }
14
- else if (key === 'start_at') {
15
- normalized['startAt'] = value;
16
- }
17
- else if (key === 'max_results') {
18
- normalized['maxResults'] = value;
19
- }
20
- else {
21
- normalized[key] = value;
22
- }
23
- }
24
- return normalized;
25
- }
3
+ import { filterNextSteps } from '../utils/next-steps.js';
4
+ import { normalizeArgs } from '../utils/normalize-args.js';
26
5
  // Validate the consolidated filter management arguments
27
6
  function validateManageJiraFilterArgs(args) {
28
7
  if (typeof args !== 'object' || args === null) {
@@ -157,7 +136,7 @@ async function handleGetFilter(jiraClient, args) {
157
136
  content: [
158
137
  {
159
138
  type: 'text',
160
- text: markdown,
139
+ text: markdown + filterNextSteps('get', filterId),
161
140
  },
162
141
  ],
163
142
  };
@@ -218,6 +197,7 @@ async function handleListFilters(jiraClient, args) {
218
197
  else {
219
198
  markdown += `Showing all ${filterDataList.length} filter${filterDataList.length !== 1 ? 's' : ''}`;
220
199
  }
200
+ markdown += filterNextSteps('list');
221
201
  return {
222
202
  content: [
223
203
  {
@@ -228,84 +208,13 @@ async function handleListFilters(jiraClient, args) {
228
208
  };
229
209
  }
230
210
  async function handleCreateFilter(_jiraClient, _args) {
231
- // Note: This is a placeholder. The current JiraClient doesn't have a createFilter method.
232
- // You would need to implement this in the JiraClient class.
233
211
  throw new McpError(ErrorCode.InternalError, 'Create filter operation is not yet implemented');
234
- // When implemented, it would look something like this:
235
- /*
236
- const result = await _jiraClient.createFilter({
237
- name: _args.name!,
238
- jql: _args.jql!,
239
- description: _args.description,
240
- favourite: _args.favourite,
241
- sharePermissions: _args.sharePermissions
242
- });
243
-
244
- // Get the created filter to return
245
- const createdFilter = await _jiraClient.getFilter(result.id);
246
- const formattedResponse = FilterFormatter.formatFilter(createdFilter);
247
-
248
- return {
249
- content: [
250
- {
251
- type: 'text',
252
- text: JSON.stringify(formattedResponse, null, 2),
253
- },
254
- ],
255
- };
256
- */
257
212
  }
258
213
  async function handleUpdateFilter(_jiraClient, _args) {
259
- // Note: This is a placeholder. The current JiraClient doesn't have an updateFilter method.
260
- // You would need to implement this in the JiraClient class.
261
214
  throw new McpError(ErrorCode.InternalError, 'Update filter operation is not yet implemented');
262
- // When implemented, it would look something like this:
263
- /*
264
- await _jiraClient.updateFilter(
265
- _args.filterId!,
266
- {
267
- name: _args.name,
268
- jql: _args.jql,
269
- description: _args.description,
270
- favourite: _args.favourite,
271
- sharePermissions: _args.sharePermissions
272
- }
273
- );
274
-
275
- // Get the updated filter to return
276
- const updatedFilter = await _jiraClient.getFilter(_args.filterId!);
277
- const formattedResponse = FilterFormatter.formatFilter(updatedFilter);
278
-
279
- return {
280
- content: [
281
- {
282
- type: 'text',
283
- text: JSON.stringify(formattedResponse, null, 2),
284
- },
285
- ],
286
- };
287
- */
288
215
  }
289
216
  async function handleDeleteFilter(_jiraClient, _args) {
290
- // Note: This is a placeholder. The current JiraClient doesn't have a deleteFilter method.
291
- // You would need to implement this in the JiraClient class.
292
217
  throw new McpError(ErrorCode.InternalError, 'Delete filter operation is not yet implemented');
293
- // When implemented, it would look something like this:
294
- /*
295
- await _jiraClient.deleteFilter(_args.filterId!);
296
-
297
- return {
298
- content: [
299
- {
300
- type: 'text',
301
- text: JSON.stringify({
302
- success: true,
303
- message: `Filter ${_args.filterId} has been deleted successfully.`,
304
- }, null, 2),
305
- },
306
- ],
307
- };
308
- */
309
218
  }
310
219
  async function handleExecuteFilter(jiraClient, _args) {
311
220
  const filterId = _args.filterId;
@@ -322,7 +231,7 @@ async function handleExecuteFilter(jiraClient, _args) {
322
231
  content: [
323
232
  {
324
233
  type: 'text',
325
- text: markdown,
234
+ text: markdown + filterNextSteps('execute_filter', filterId),
326
235
  },
327
236
  ],
328
237
  };
@@ -353,7 +262,7 @@ async function handleExecuteJql(jiraClient, args) {
353
262
  content: [
354
263
  {
355
264
  type: 'text',
356
- text: markdown,
265
+ text: markdown + filterNextSteps('execute_jql', undefined, args.jql),
357
266
  },
358
267
  ],
359
268
  };
@@ -367,7 +276,7 @@ async function handleExecuteJql(jiraClient, args) {
367
276
  }
368
277
  }
369
278
  // Main handler function
370
- export async function setupFilterHandlers(server, jiraClient, request) {
279
+ export async function handleFilterRequest(jiraClient, request) {
371
280
  console.error('Handling filter request...');
372
281
  const { name } = request.params;
373
282
  const args = request.params.arguments || {};