@ecubelabs/atlassian-mcp 1.2.0 → 1.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.
@@ -0,0 +1,1593 @@
1
+ import { z } from 'zod';
2
+ import { JiraService } from './libs/jira-client.js';
3
+ export const registerJiraTools = (server) => {
4
+ // Initialize Jira service
5
+ const jiraService = new JiraService();
6
+ // Register Jira tools
7
+ server.tool('get-issue', 'Get details of a Jira issue by key', {
8
+ issueKey: z.string().describe('Jira issue key (e.g. PROJ-123)'),
9
+ expand: z
10
+ .enum([
11
+ 'renderedFields',
12
+ 'names',
13
+ 'schema',
14
+ 'transitions',
15
+ 'editmeta',
16
+ 'changelog',
17
+ 'versionedRepresentations',
18
+ ])
19
+ .array()
20
+ .optional()
21
+ .describe('Optional fields to expand in the response. Options include: renderedFields (HTML format), names (display names), schema (field type descriptions), transitions (possible transitions), editmeta (field editing info), changelog (recent updates), versionedRepresentations (field value history)'),
22
+ }, async ({ issueKey, expand }) => {
23
+ try {
24
+ const issue = await jiraService.getIssue(issueKey, expand);
25
+ return {
26
+ content: [
27
+ {
28
+ type: 'text',
29
+ text: JSON.stringify(issue),
30
+ },
31
+ ],
32
+ };
33
+ }
34
+ catch (error) {
35
+ return {
36
+ content: [
37
+ {
38
+ type: 'text',
39
+ text: `Failed to retrieve issue: ${error instanceof Error ? error.message : String(error)}`,
40
+ },
41
+ ],
42
+ };
43
+ }
44
+ });
45
+ server.tool('search-issues', 'Search Jira issues using JQL', {
46
+ jql: z.string().describe('JQL query string. Must be a bounded query with search restrictions.'),
47
+ fields: z
48
+ .array(z.string())
49
+ .optional()
50
+ .describe("Optional fields to include in the response. Examples: ['summary', 'comment'] or ['*all', '-comment']"),
51
+ maxResults: z
52
+ .number()
53
+ .min(1)
54
+ .max(5000)
55
+ .optional()
56
+ .describe('Maximum number of results to return per page (default: 50, max: 5000)'),
57
+ nextPageToken: z
58
+ .string()
59
+ .optional()
60
+ .describe('Token for fetching the next page of results. The first page has null nextPageToken.'),
61
+ expand: z
62
+ .enum([
63
+ 'renderedFields',
64
+ 'names',
65
+ 'schema',
66
+ 'transitions',
67
+ 'editmeta',
68
+ 'changelog',
69
+ 'versionedRepresentations',
70
+ ])
71
+ .array()
72
+ .optional()
73
+ .describe('Comma-separated values to expand in response: renderedFields, names, schema, transitions, operations, editmeta, changelog, versionedRepresentations'),
74
+ properties: z
75
+ .array(z.string())
76
+ .max(5)
77
+ .optional()
78
+ .describe('List of up to 5 issue properties to include in results'),
79
+ fieldsByKeys: z
80
+ .boolean()
81
+ .optional()
82
+ .describe('Reference fields by their key rather than ID (default: false)'),
83
+ failFast: z
84
+ .boolean()
85
+ .optional()
86
+ .describe("Fail request early if we can't retrieve all field data (default: false)"),
87
+ reconcileIssues: z
88
+ .array(z.number())
89
+ .max(50)
90
+ .optional()
91
+ .describe('Strong consistency issue IDs to be reconciled with search results (max: 50 IDs)'),
92
+ }, async ({ jql, fields, maxResults, nextPageToken, expand, properties, fieldsByKeys, failFast, reconcileIssues, }) => {
93
+ try {
94
+ const result = await jiraService.searchIssues({
95
+ jql,
96
+ fields,
97
+ maxResults,
98
+ nextPageToken,
99
+ expand,
100
+ properties,
101
+ fieldsByKeys,
102
+ failFast,
103
+ reconcileIssues,
104
+ });
105
+ return {
106
+ content: [
107
+ {
108
+ type: 'text',
109
+ text: JSON.stringify(result),
110
+ },
111
+ ],
112
+ };
113
+ }
114
+ catch (error) {
115
+ return {
116
+ content: [
117
+ {
118
+ type: 'text',
119
+ text: `Failed to search issues: ${error instanceof Error ? error.message : String(error)}`,
120
+ },
121
+ ],
122
+ };
123
+ }
124
+ });
125
+ server.tool('get-comments', 'Get comments for a Jira issue', {
126
+ issueKey: z.string().describe('Jira issue key (e.g. PROJ-123)'),
127
+ startAt: z
128
+ .number()
129
+ .min(0)
130
+ .optional()
131
+ .describe('The index of the first item to return (page offset). Default: 0'),
132
+ maxResults: z
133
+ .number()
134
+ .min(1)
135
+ .max(100)
136
+ .optional()
137
+ .describe('Maximum number of items per page. Default: 100'),
138
+ orderBy: z
139
+ .enum(['created', '-created', '+created'])
140
+ .optional()
141
+ .describe('Order comments by creation date. Default: created'),
142
+ expand: z
143
+ .array(z.enum(['renderedBody']))
144
+ .optional()
145
+ .describe('Include additional information: renderedBody (comment body in HTML format)'),
146
+ }, async ({ issueKey, startAt, maxResults, orderBy, expand }) => {
147
+ try {
148
+ const result = await jiraService.getComments(issueKey, {
149
+ startAt,
150
+ maxResults,
151
+ orderBy,
152
+ expand,
153
+ });
154
+ return {
155
+ content: [
156
+ {
157
+ type: 'text',
158
+ text: JSON.stringify(result),
159
+ },
160
+ ],
161
+ };
162
+ }
163
+ catch (error) {
164
+ return {
165
+ content: [
166
+ {
167
+ type: 'text',
168
+ text: `Failed to retrieve comments: ${error instanceof Error ? error.message : String(error)}`,
169
+ },
170
+ ],
171
+ };
172
+ }
173
+ });
174
+ server.tool('get-projects', 'Get list of Jira projects', {
175
+ startAt: z
176
+ .number()
177
+ .min(0)
178
+ .optional()
179
+ .describe('The index of the first item to return (page offset). Default: 0'),
180
+ maxResults: z
181
+ .number()
182
+ .min(1)
183
+ .max(100)
184
+ .optional()
185
+ .describe('Maximum number of items per page. Default: 50, Maximum: 100'),
186
+ orderBy: z
187
+ .enum([
188
+ 'category',
189
+ '-category',
190
+ '+category',
191
+ 'key',
192
+ '-key',
193
+ '+key',
194
+ 'name',
195
+ '-name',
196
+ '+name',
197
+ 'owner',
198
+ '-owner',
199
+ '+owner',
200
+ 'issueCount',
201
+ '-issueCount',
202
+ '+issueCount',
203
+ 'lastIssueUpdatedTime',
204
+ '-lastIssueUpdatedTime',
205
+ '+lastIssueUpdatedTime',
206
+ 'archivedDate',
207
+ '-archivedDate',
208
+ '+archivedDate',
209
+ 'deletedDate',
210
+ '-deletedDate',
211
+ '+deletedDate',
212
+ ])
213
+ .optional()
214
+ .describe('Field to order results by. Default: key'),
215
+ id: z.array(z.number().int()).max(50).optional().describe('Filter by project IDs. Maximum: 50 IDs'),
216
+ keys: z.array(z.string()).max(50).optional().describe('Filter by project keys. Maximum: 50 keys'),
217
+ query: z.string().optional().describe('Filter by matching key or name (case insensitive)'),
218
+ typeKey: z
219
+ .string()
220
+ .optional()
221
+ .describe('Filter by project type. Accepts comma-separated list: business, service_desk, software'),
222
+ categoryId: z.number().int().optional().describe('Filter by project category ID'),
223
+ action: z
224
+ .enum(['view', 'browse', 'edit', 'create'])
225
+ .optional()
226
+ .describe("Filter by user's project permissions. Default: view"),
227
+ expand: z
228
+ .array(z.enum(['description', 'projectKeys', 'lead', 'issueTypes', 'url', 'insight']))
229
+ .optional()
230
+ .describe('Additional fields to include: description, projectKeys, lead, issueTypes, url, insight'),
231
+ status: z
232
+ .array(z.enum(['live', 'archived', 'deleted']))
233
+ .optional()
234
+ .describe('EXPERIMENTAL. Filter by project status'),
235
+ }, async ({ startAt, maxResults, orderBy, id, keys, query, typeKey, categoryId, action, expand, status }) => {
236
+ try {
237
+ const result = await jiraService.getProjects({
238
+ startAt,
239
+ maxResults,
240
+ orderBy,
241
+ id,
242
+ keys,
243
+ query,
244
+ typeKey,
245
+ categoryId,
246
+ action,
247
+ expand,
248
+ status,
249
+ });
250
+ return {
251
+ content: [
252
+ {
253
+ type: 'text',
254
+ text: JSON.stringify(result),
255
+ },
256
+ ],
257
+ };
258
+ }
259
+ catch (error) {
260
+ return {
261
+ content: [
262
+ {
263
+ type: 'text',
264
+ text: `Failed to retrieve projects: ${error instanceof Error ? error.message : String(error)}`,
265
+ },
266
+ ],
267
+ };
268
+ }
269
+ });
270
+ server.tool('get-create-metadata-issue-types', 'Get issue types available for creating issues in a project', {
271
+ projectIdOrKey: z.string().describe('Project ID (numeric) or project key (e.g. PROJ)'),
272
+ }, async ({ projectIdOrKey }) => {
273
+ try {
274
+ const result = await jiraService.getCreateMetadataIssueTypes(projectIdOrKey);
275
+ return {
276
+ content: [
277
+ {
278
+ type: 'text',
279
+ text: JSON.stringify(result),
280
+ },
281
+ ],
282
+ };
283
+ }
284
+ catch (error) {
285
+ return {
286
+ content: [
287
+ {
288
+ type: 'text',
289
+ text: `Failed to retrieve issue types: ${error instanceof Error ? error.message : String(error)}`,
290
+ },
291
+ ],
292
+ };
293
+ }
294
+ });
295
+ server.tool('get-create-field-metadata', 'Get create field metadata for a project and issue type id', {
296
+ projectIdOrKey: z.string().describe('Project ID (numeric) or project key (e.g. PROJ)'),
297
+ issueTypeId: z.string().describe('Issue type ID'),
298
+ }, async ({ projectIdOrKey, issueTypeId }) => {
299
+ try {
300
+ const result = await jiraService.getCreateFieldMetadata(projectIdOrKey, issueTypeId);
301
+ return {
302
+ content: [
303
+ {
304
+ type: 'text',
305
+ text: JSON.stringify(result),
306
+ },
307
+ ],
308
+ };
309
+ }
310
+ catch (error) {
311
+ return {
312
+ content: [
313
+ {
314
+ type: 'text',
315
+ text: `Failed to retrieve field metadata: ${error instanceof Error ? error.message : String(error)}`,
316
+ },
317
+ ],
318
+ };
319
+ }
320
+ });
321
+ server.tool('get-fields', 'Get Jira fields (optionally search/paginate)', {
322
+ type: z.enum(['all', 'custom', 'system']).optional().describe('Field type to filter'),
323
+ query: z.string().optional().describe('Search string to filter fields'),
324
+ orderBy: z.enum(['name', '-name']).optional().describe('Order results by name ascending/descending'),
325
+ startAt: z.number().min(0).optional().describe('Index of the first item to return'),
326
+ maxResults: z.number().min(1).max(1000).optional().describe('Max number of items to return'),
327
+ }, async ({ type, query, orderBy, startAt, maxResults }) => {
328
+ try {
329
+ const result = await jiraService.getFields({
330
+ type,
331
+ query,
332
+ orderBy,
333
+ startAt,
334
+ maxResults,
335
+ });
336
+ return {
337
+ content: [
338
+ {
339
+ type: 'text',
340
+ text: JSON.stringify(result),
341
+ },
342
+ ],
343
+ };
344
+ }
345
+ catch (error) {
346
+ return {
347
+ content: [
348
+ {
349
+ type: 'text',
350
+ text: `Failed to retrieve fields: ${error instanceof Error ? error.message : String(error)}`,
351
+ },
352
+ ],
353
+ };
354
+ }
355
+ });
356
+ server.tool('parse-jql', 'Parse a JQL query and return its AST', {
357
+ query: z.string().describe('JQL query to parse'),
358
+ validation: z
359
+ .enum(['strict', 'warn'])
360
+ .optional()
361
+ .describe('Validation level during parse (default: strict)'),
362
+ }, async ({ query, validation }) => {
363
+ try {
364
+ const result = await jiraService.parseJql(query, validation);
365
+ return {
366
+ content: [
367
+ {
368
+ type: 'text',
369
+ text: JSON.stringify(result),
370
+ },
371
+ ],
372
+ };
373
+ }
374
+ catch (error) {
375
+ return {
376
+ content: [
377
+ {
378
+ type: 'text',
379
+ text: `Failed to parse JQL: ${error instanceof Error ? error.message : String(error)}`,
380
+ },
381
+ ],
382
+ };
383
+ }
384
+ });
385
+ server.tool('create-issue', 'Create a new Jira issue', {
386
+ projectKey: z.string().describe('Project key (e.g. PROJ)'),
387
+ summary: z.string().describe('Issue summary'),
388
+ issueTypeId: z.string().optional().describe('Issue type ID'),
389
+ issueTypeName: z.string().optional().describe('Issue type name'),
390
+ description: z.string().optional().describe('Issue description (plain text)'),
391
+ assigneeAccountId: z.string().optional().describe('Assignee accountId (Jira Cloud)'),
392
+ reporterAccountId: z
393
+ .string()
394
+ .optional()
395
+ .describe('Reporter accountId. If omitted, server attempts to use current user when allowed'),
396
+ priorityId: z.string().optional().describe('Priority ID'),
397
+ labels: z.array(z.string()).optional().describe('Labels to apply'),
398
+ additionalFields: z
399
+ .record(z.any())
400
+ .optional()
401
+ .describe('Additional raw Jira fields to include under fields'),
402
+ }, async ({ projectKey, summary, issueTypeId, issueTypeName, description, assigneeAccountId, reporterAccountId, priorityId, labels, additionalFields, }) => {
403
+ try {
404
+ const issue = await jiraService.createIssue({
405
+ projectKey,
406
+ summary,
407
+ issueTypeId,
408
+ issueTypeName,
409
+ description,
410
+ assigneeAccountId,
411
+ reporterAccountId,
412
+ priorityId,
413
+ labels,
414
+ additionalFields,
415
+ });
416
+ return {
417
+ content: [
418
+ {
419
+ type: 'text',
420
+ text: JSON.stringify(issue),
421
+ },
422
+ ],
423
+ };
424
+ }
425
+ catch (error) {
426
+ return {
427
+ content: [
428
+ {
429
+ type: 'text',
430
+ text: `Failed to create issue: ${error instanceof Error ? error.message : String(error)}`,
431
+ },
432
+ ],
433
+ };
434
+ }
435
+ });
436
+ server.tool('assign-issue', 'Assign or unassign a Jira issue', {
437
+ issueKey: z.string().describe('Jira issue key (e.g. PROJ-123)'),
438
+ assigneeAccountId: z.string().optional().describe('AccountId of assignee. Omit if unassigning.'),
439
+ unassign: z.boolean().optional().describe('Set true to remove current assignee'),
440
+ }, async ({ issueKey, assigneeAccountId, unassign }) => {
441
+ try {
442
+ if (unassign) {
443
+ await jiraService.assignIssue(issueKey);
444
+ }
445
+ else {
446
+ await jiraService.assignIssue(issueKey, assigneeAccountId);
447
+ }
448
+ return {
449
+ content: [
450
+ {
451
+ type: 'text',
452
+ text: 'Issue assigned successfully',
453
+ },
454
+ ],
455
+ };
456
+ }
457
+ catch (error) {
458
+ return {
459
+ content: [
460
+ {
461
+ type: 'text',
462
+ text: `Failed to assign issue: ${error instanceof Error ? error.message : String(error)}`,
463
+ },
464
+ ],
465
+ };
466
+ }
467
+ });
468
+ server.tool('edit-issue', 'Edit fields on a Jira issue', {
469
+ issueKey: z.string().describe('Jira issue key (e.g. PROJ-123)'),
470
+ summary: z.string().optional().describe('New summary'),
471
+ description: z.string().optional().describe('New description (plain text)'),
472
+ assigneeAccountId: z
473
+ .string()
474
+ .optional()
475
+ .describe('New assignee accountId. Use with unassignAssignee=false'),
476
+ unassignAssignee: z.boolean().optional().describe('Set true to remove current assignee'),
477
+ priorityId: z.string().optional().describe('New priority ID'),
478
+ labels: z.array(z.string()).optional().describe('Replace labels'),
479
+ additionalFields: z.record(z.any()).optional().describe('Additional raw fields to include under fields'),
480
+ }, async ({ issueKey, summary, description, assigneeAccountId, unassignAssignee, priorityId, labels, additionalFields, }) => {
481
+ try {
482
+ await jiraService.editIssue(issueKey, {
483
+ summary,
484
+ description,
485
+ assigneeAccountId,
486
+ unassignAssignee,
487
+ priorityId,
488
+ labels,
489
+ additionalFields,
490
+ });
491
+ return {
492
+ content: [
493
+ {
494
+ type: 'text',
495
+ text: 'Issue updated successfully',
496
+ },
497
+ ],
498
+ };
499
+ }
500
+ catch (error) {
501
+ return {
502
+ content: [
503
+ {
504
+ type: 'text',
505
+ text: `Failed to update issue: ${error instanceof Error ? error.message : String(error)}`,
506
+ },
507
+ ],
508
+ };
509
+ }
510
+ });
511
+ server.tool('search-users', 'Search for users', {
512
+ query: z.string().optional().describe('Query string to search users'),
513
+ accountId: z.string().optional().describe('Account ID to search for'),
514
+ startAt: z.number().min(0).optional().describe('The index of the first item to return'),
515
+ maxResults: z.number().min(1).max(1000).optional().describe('Maximum number of items to return'),
516
+ property: z.string().optional().describe('Property key to search for in user properties'),
517
+ }, async ({ query, accountId, startAt, maxResults, property }) => {
518
+ try {
519
+ const users = await jiraService.searchUsers({
520
+ query,
521
+ accountId,
522
+ startAt,
523
+ maxResults,
524
+ property,
525
+ });
526
+ return {
527
+ content: [
528
+ {
529
+ type: 'text',
530
+ text: JSON.stringify(users),
531
+ },
532
+ ],
533
+ };
534
+ }
535
+ catch (error) {
536
+ return {
537
+ content: [
538
+ {
539
+ type: 'text',
540
+ text: `Failed to search users: ${error instanceof Error ? error.message : String(error)}`,
541
+ },
542
+ ],
543
+ };
544
+ }
545
+ });
546
+ server.tool('get-user-picker', 'Get user picker suggestions for autocomplete', {
547
+ query: z.string().describe('Query string for user search'),
548
+ maxResults: z.number().min(1).max(1000).optional().describe('Maximum number of users to return'),
549
+ showAvatar: z.boolean().optional().describe('Whether to show user avatars'),
550
+ exclude: z.array(z.string()).optional().describe('List of users to exclude from results'),
551
+ excludeAccountIds: z.array(z.string()).optional().describe('List of account IDs to exclude'),
552
+ avatarSize: z.string().optional().describe('Size of avatars to return'),
553
+ excludeConnectUsers: z.boolean().optional().describe('Whether to exclude Connect app users'),
554
+ }, async ({ query, maxResults, showAvatar, exclude, excludeAccountIds, avatarSize, excludeConnectUsers }) => {
555
+ try {
556
+ const result = await jiraService.getUserPicker({
557
+ query,
558
+ maxResults,
559
+ showAvatar,
560
+ exclude,
561
+ excludeAccountIds,
562
+ avatarSize,
563
+ excludeConnectUsers,
564
+ });
565
+ return {
566
+ content: [
567
+ {
568
+ type: 'text',
569
+ text: JSON.stringify(result),
570
+ },
571
+ ],
572
+ };
573
+ }
574
+ catch (error) {
575
+ return {
576
+ content: [
577
+ {
578
+ type: 'text',
579
+ text: `Failed to get user picker suggestions: ${error instanceof Error ? error.message : String(error)}`,
580
+ },
581
+ ],
582
+ };
583
+ }
584
+ });
585
+ server.tool('search-assignable-users', 'Search for users who can be assigned to issues', {
586
+ projectKeys: z.array(z.string()).optional().describe('Project keys to search assignable users for'),
587
+ query: z.string().optional().describe('Query string to search users'),
588
+ username: z.string().optional().describe('Username to search for'),
589
+ accountId: z.string().optional().describe('Account ID to search for'),
590
+ startAt: z.number().min(0).optional().describe('The index of the first item to return'),
591
+ maxResults: z.number().min(1).max(1000).optional().describe('Maximum number of items to return'),
592
+ }, async ({ projectKeys, query, username, accountId, startAt, maxResults }) => {
593
+ try {
594
+ const users = await jiraService.searchAssignableUsers({
595
+ projectKeys,
596
+ query,
597
+ username,
598
+ accountId,
599
+ startAt,
600
+ maxResults,
601
+ });
602
+ return {
603
+ content: [
604
+ {
605
+ type: 'text',
606
+ text: JSON.stringify(users),
607
+ },
608
+ ],
609
+ };
610
+ }
611
+ catch (error) {
612
+ return {
613
+ content: [
614
+ {
615
+ type: 'text',
616
+ text: `Failed to search assignable users: ${error instanceof Error ? error.message : String(error)}`,
617
+ },
618
+ ],
619
+ };
620
+ }
621
+ });
622
+ server.tool('search-users-by-permission', 'Search for users who have specific permissions', {
623
+ permissions: z.string().describe('Comma-separated list of permissions (e.g., BROWSE_PROJECTS,EDIT_ISSUES)'),
624
+ projectKey: z.string().optional().describe('Project key to check permissions for'),
625
+ issueKey: z.string().optional().describe('Issue key to check permissions for'),
626
+ query: z.string().optional().describe('Query string to search users'),
627
+ username: z.string().optional().describe('Username to search for'),
628
+ accountId: z.string().optional().describe('Account ID to search for'),
629
+ startAt: z.number().min(0).optional().describe('The index of the first item to return'),
630
+ maxResults: z.number().min(1).max(1000).optional().describe('Maximum number of items to return'),
631
+ }, async ({ permissions, projectKey, issueKey, query, username, accountId, startAt, maxResults }) => {
632
+ try {
633
+ const users = await jiraService.searchUsersByPermission({
634
+ permissions,
635
+ projectKey,
636
+ issueKey,
637
+ query,
638
+ username,
639
+ accountId,
640
+ startAt,
641
+ maxResults,
642
+ });
643
+ return {
644
+ content: [
645
+ {
646
+ type: 'text',
647
+ text: JSON.stringify(users),
648
+ },
649
+ ],
650
+ };
651
+ }
652
+ catch (error) {
653
+ return {
654
+ content: [
655
+ {
656
+ type: 'text',
657
+ text: `Failed to search users by permission: ${error instanceof Error ? error.message : String(error)}`,
658
+ },
659
+ ],
660
+ };
661
+ }
662
+ });
663
+ server.tool('get-myself', 'Get current user information', {
664
+ expand: z
665
+ .array(z.enum(['groups', 'applicationRoles']))
666
+ .optional()
667
+ .describe('List of fields to expand'),
668
+ }, async ({ expand }) => {
669
+ try {
670
+ const user = await jiraService.getMyself(expand);
671
+ return {
672
+ content: [
673
+ {
674
+ type: 'text',
675
+ text: JSON.stringify(user),
676
+ },
677
+ ],
678
+ };
679
+ }
680
+ catch (error) {
681
+ return {
682
+ content: [
683
+ {
684
+ type: 'text',
685
+ text: `Failed to get current user info: ${error instanceof Error ? error.message : String(error)}`,
686
+ },
687
+ ],
688
+ };
689
+ }
690
+ });
691
+ server.tool('get-my-permissions', "Get current user's permissions", {
692
+ projectKey: z.string().optional().describe('Project key to check permissions for'),
693
+ projectId: z.string().optional().describe('Project ID to check permissions for'),
694
+ issueKey: z.string().optional().describe('Issue key to check permissions for'),
695
+ issueId: z.string().optional().describe('Issue ID to check permissions for'),
696
+ permissions: z.string().optional().describe('Comma-separated list of permission keys to check'),
697
+ projectUuid: z.string().optional().describe('Project UUID'),
698
+ projectConfigurationUuid: z.string().optional().describe('Project configuration UUID'),
699
+ commentId: z.string().optional().describe('Comment ID to check permissions for'),
700
+ }, async ({ projectKey, projectId, issueKey, issueId, permissions, projectUuid, projectConfigurationUuid, commentId, }) => {
701
+ try {
702
+ const result = await jiraService.getMyPermissions({
703
+ projectKey,
704
+ projectId,
705
+ issueKey,
706
+ issueId,
707
+ permissions,
708
+ projectUuid,
709
+ projectConfigurationUuid,
710
+ commentId,
711
+ });
712
+ return {
713
+ content: [
714
+ {
715
+ type: 'text',
716
+ text: JSON.stringify(result),
717
+ },
718
+ ],
719
+ };
720
+ }
721
+ catch (error) {
722
+ return {
723
+ content: [
724
+ {
725
+ type: 'text',
726
+ text: `Failed to get permissions: ${error instanceof Error ? error.message : String(error)}`,
727
+ },
728
+ ],
729
+ };
730
+ }
731
+ });
732
+ server.tool('update-myself', "Update current user's profile information", {
733
+ emailAddress: z.string().email().optional().describe('New email address'),
734
+ displayName: z.string().optional().describe('New display name'),
735
+ timeZone: z.string().optional().describe("New timezone (e.g., 'America/New_York')"),
736
+ locale: z.string().optional().describe("New locale (e.g., 'en_US')"),
737
+ }, async ({ emailAddress, displayName, timeZone, locale }) => {
738
+ try {
739
+ const user = await jiraService.updateMyself({
740
+ emailAddress,
741
+ displayName,
742
+ timeZone,
743
+ locale,
744
+ });
745
+ return {
746
+ content: [
747
+ {
748
+ type: 'text',
749
+ text: JSON.stringify(user),
750
+ },
751
+ ],
752
+ };
753
+ }
754
+ catch (error) {
755
+ return {
756
+ content: [
757
+ {
758
+ type: 'text',
759
+ text: `Failed to update user profile: ${error instanceof Error ? error.message : String(error)}`,
760
+ },
761
+ ],
762
+ };
763
+ }
764
+ });
765
+ server.tool('get-my-preferences', "Get current user's preferences", {
766
+ key: z.string().optional().describe('Specific preference key to retrieve'),
767
+ }, async ({ key }) => {
768
+ try {
769
+ const preferences = await jiraService.getMyPreferences(key);
770
+ return {
771
+ content: [
772
+ {
773
+ type: 'text',
774
+ text: JSON.stringify(preferences),
775
+ },
776
+ ],
777
+ };
778
+ }
779
+ catch (error) {
780
+ return {
781
+ content: [
782
+ {
783
+ type: 'text',
784
+ text: `Failed to get preferences: ${error instanceof Error ? error.message : String(error)}`,
785
+ },
786
+ ],
787
+ };
788
+ }
789
+ });
790
+ server.tool('update-my-preferences', "Update current user's preferences", {
791
+ key: z.string().describe('Preference key to update'),
792
+ value: z.any().describe('New value for the preference'),
793
+ }, async ({ key, value }) => {
794
+ try {
795
+ const result = await jiraService.updateMyPreferences(key, value);
796
+ return {
797
+ content: [
798
+ {
799
+ type: 'text',
800
+ text: JSON.stringify(result),
801
+ },
802
+ ],
803
+ };
804
+ }
805
+ catch (error) {
806
+ return {
807
+ content: [
808
+ {
809
+ type: 'text',
810
+ text: `Failed to update preferences: ${error instanceof Error ? error.message : String(error)}`,
811
+ },
812
+ ],
813
+ };
814
+ }
815
+ });
816
+ server.tool('get-my-locale', "Get current user's locale setting", {}, async () => {
817
+ try {
818
+ const locale = await jiraService.getMyLocale();
819
+ return {
820
+ content: [
821
+ {
822
+ type: 'text',
823
+ text: JSON.stringify(locale),
824
+ },
825
+ ],
826
+ };
827
+ }
828
+ catch (error) {
829
+ return {
830
+ content: [
831
+ {
832
+ type: 'text',
833
+ text: `Failed to get locale: ${error instanceof Error ? error.message : String(error)}`,
834
+ },
835
+ ],
836
+ };
837
+ }
838
+ });
839
+ server.tool('update-my-locale', "Update current user's locale setting", {
840
+ locale: z.string().describe("New locale (e.g., 'en_US', 'ko_KR')"),
841
+ }, async ({ locale }) => {
842
+ try {
843
+ const result = await jiraService.updateMyLocale(locale);
844
+ return {
845
+ content: [
846
+ {
847
+ type: 'text',
848
+ text: JSON.stringify(result),
849
+ },
850
+ ],
851
+ };
852
+ }
853
+ catch (error) {
854
+ return {
855
+ content: [
856
+ {
857
+ type: 'text',
858
+ text: `Failed to update locale: ${error instanceof Error ? error.message : String(error)}`,
859
+ },
860
+ ],
861
+ };
862
+ }
863
+ });
864
+ server.tool('get-priorities', 'Get all priorities', {}, async () => {
865
+ try {
866
+ const priorities = await jiraService.getPriorities();
867
+ return {
868
+ content: [
869
+ {
870
+ type: 'text',
871
+ text: JSON.stringify(priorities),
872
+ },
873
+ ],
874
+ };
875
+ }
876
+ catch (error) {
877
+ return {
878
+ content: [
879
+ {
880
+ type: 'text',
881
+ text: `Failed to get priorities: ${error instanceof Error ? error.message : String(error)}`,
882
+ },
883
+ ],
884
+ };
885
+ }
886
+ });
887
+ server.tool('get-issue-transitions', 'Get available transitions for an issue', {
888
+ issueKey: z.string().describe('Issue key (e.g. PROJ-123)'),
889
+ expand: z
890
+ .array(z.enum(['transitions.fields', 'fields']))
891
+ .optional()
892
+ .describe('Fields to expand in the response'),
893
+ }, async ({ issueKey, expand }) => {
894
+ try {
895
+ const result = await jiraService.getIssueTransitions(issueKey, expand);
896
+ return {
897
+ content: [
898
+ {
899
+ type: 'text',
900
+ text: JSON.stringify(result),
901
+ },
902
+ ],
903
+ };
904
+ }
905
+ catch (error) {
906
+ return {
907
+ content: [
908
+ {
909
+ type: 'text',
910
+ text: `Failed to get issue transitions: ${error instanceof Error ? error.message : String(error)}`,
911
+ },
912
+ ],
913
+ };
914
+ }
915
+ });
916
+ server.tool('transition-issue', 'Transition an issue to a new status', {
917
+ issueKey: z.string().describe('Issue key (e.g. PROJ-123)'),
918
+ transitionId: z.string().describe('ID of the transition to perform'),
919
+ comment: z
920
+ .object({
921
+ body: z.string().describe('Comment text'),
922
+ visibility: z
923
+ .object({
924
+ type: z.enum(['group', 'role']).describe('Visibility type'),
925
+ value: z.string().describe('Group name or role name'),
926
+ })
927
+ .optional()
928
+ .describe('Comment visibility restrictions'),
929
+ })
930
+ .optional()
931
+ .describe('Comment to add when transitioning'),
932
+ fields: z.record(z.any()).optional().describe('Fields to update during transition'),
933
+ update: z.record(z.any()).optional().describe('Additional update operations'),
934
+ historyMetadata: z.record(z.any()).optional().describe('History metadata'),
935
+ properties: z
936
+ .array(z.object({
937
+ key: z.string().describe('Property key'),
938
+ value: z.any().describe('Property value'),
939
+ }))
940
+ .optional()
941
+ .describe('Issue properties to set'),
942
+ }, async ({ issueKey, transitionId, comment, fields, update, historyMetadata, properties }) => {
943
+ try {
944
+ await jiraService.transitionIssue(issueKey, {
945
+ transitionId,
946
+ comment,
947
+ fields,
948
+ update,
949
+ historyMetadata,
950
+ properties: properties,
951
+ });
952
+ return {
953
+ content: [
954
+ {
955
+ type: 'text',
956
+ text: 'Issue transitioned successfully',
957
+ },
958
+ ],
959
+ };
960
+ }
961
+ catch (error) {
962
+ return {
963
+ content: [
964
+ {
965
+ type: 'text',
966
+ text: `Failed to transition issue: ${error instanceof Error ? error.message : String(error)}`,
967
+ },
968
+ ],
969
+ };
970
+ }
971
+ });
972
+ server.tool('get-statuses', 'Get all workflow statuses', {}, async () => {
973
+ try {
974
+ const statuses = await jiraService.getStatuses();
975
+ return {
976
+ content: [
977
+ {
978
+ type: 'text',
979
+ text: JSON.stringify(statuses),
980
+ },
981
+ ],
982
+ };
983
+ }
984
+ catch (error) {
985
+ return {
986
+ content: [
987
+ {
988
+ type: 'text',
989
+ text: `Failed to get statuses: ${error instanceof Error ? error.message : String(error)}`,
990
+ },
991
+ ],
992
+ };
993
+ }
994
+ });
995
+ server.tool('get-status', 'Get a specific status by ID or name', {
996
+ idOrName: z.string().describe('Status ID or name'),
997
+ }, async ({ idOrName }) => {
998
+ try {
999
+ const status = await jiraService.getStatus(idOrName);
1000
+ return {
1001
+ content: [
1002
+ {
1003
+ type: 'text',
1004
+ text: JSON.stringify(status),
1005
+ },
1006
+ ],
1007
+ };
1008
+ }
1009
+ catch (error) {
1010
+ return {
1011
+ content: [
1012
+ {
1013
+ type: 'text',
1014
+ text: `Failed to get status: ${error instanceof Error ? error.message : String(error)}`,
1015
+ },
1016
+ ],
1017
+ };
1018
+ }
1019
+ });
1020
+ server.tool('search-statuses', 'Search statuses with filters', {
1021
+ startAt: z.number().min(0).optional().describe('The index of the first item to return'),
1022
+ maxResults: z.number().min(1).max(100).optional().describe('Maximum number of items to return'),
1023
+ searchString: z.string().optional().describe('Search string to filter statuses'),
1024
+ projectId: z.string().optional().describe('Project ID to filter statuses for'),
1025
+ expand: z.string().optional().describe('Fields to expand in the response'),
1026
+ }, async ({ startAt, maxResults, searchString, projectId, expand }) => {
1027
+ try {
1028
+ const result = await jiraService.searchStatuses({
1029
+ startAt,
1030
+ maxResults,
1031
+ searchString,
1032
+ projectId,
1033
+ expand,
1034
+ });
1035
+ return {
1036
+ content: [
1037
+ {
1038
+ type: 'text',
1039
+ text: JSON.stringify(result),
1040
+ },
1041
+ ],
1042
+ };
1043
+ }
1044
+ catch (error) {
1045
+ return {
1046
+ content: [
1047
+ {
1048
+ type: 'text',
1049
+ text: `Failed to search statuses: ${error instanceof Error ? error.message : String(error)}`,
1050
+ },
1051
+ ],
1052
+ };
1053
+ }
1054
+ });
1055
+ server.tool('get-priority', 'Get a specific priority by ID', {
1056
+ id: z.string().describe('Priority ID'),
1057
+ }, async ({ id }) => {
1058
+ try {
1059
+ const priority = await jiraService.getPriority(id);
1060
+ return {
1061
+ content: [
1062
+ {
1063
+ type: 'text',
1064
+ text: JSON.stringify(priority),
1065
+ },
1066
+ ],
1067
+ };
1068
+ }
1069
+ catch (error) {
1070
+ return {
1071
+ content: [
1072
+ {
1073
+ type: 'text',
1074
+ text: `Failed to get priority: ${error instanceof Error ? error.message : String(error)}`,
1075
+ },
1076
+ ],
1077
+ };
1078
+ }
1079
+ });
1080
+ server.tool('search-priorities', 'Search priorities with filters', {
1081
+ startAt: z.number().min(0).optional().describe('The index of the first item to return'),
1082
+ maxResults: z.number().min(1).max(100).optional().describe('Maximum number of items to return'),
1083
+ id: z.array(z.string()).optional().describe('List of priority IDs to filter by'),
1084
+ projectId: z.array(z.string()).optional().describe('List of project IDs to filter by'),
1085
+ priorityName: z.string().optional().describe('Priority name to search for'),
1086
+ onlyDefault: z.boolean().optional().describe('Whether to return only default priority'),
1087
+ }, async ({ startAt, maxResults, id, projectId, priorityName, onlyDefault }) => {
1088
+ try {
1089
+ const result = await jiraService.searchPriorities({
1090
+ startAt,
1091
+ maxResults,
1092
+ id,
1093
+ projectId,
1094
+ priorityName,
1095
+ onlyDefault,
1096
+ });
1097
+ return {
1098
+ content: [
1099
+ {
1100
+ type: 'text',
1101
+ text: JSON.stringify(result),
1102
+ },
1103
+ ],
1104
+ };
1105
+ }
1106
+ catch (error) {
1107
+ return {
1108
+ content: [
1109
+ {
1110
+ type: 'text',
1111
+ text: `Failed to search priorities: ${error instanceof Error ? error.message : String(error)}`,
1112
+ },
1113
+ ],
1114
+ };
1115
+ }
1116
+ });
1117
+ server.tool('create-comment', 'Add a comment to an issue', {
1118
+ issueKey: z.string().describe('Issue key (e.g. PROJ-123)'),
1119
+ body: z.string().describe('Comment text'),
1120
+ visibility: z
1121
+ .object({
1122
+ type: z.enum(['group', 'role']).describe('Visibility type'),
1123
+ value: z.string().describe('Group name or role name'),
1124
+ })
1125
+ .optional()
1126
+ .describe('Comment visibility restrictions'),
1127
+ properties: z
1128
+ .array(z.object({
1129
+ key: z.string().describe('Property key'),
1130
+ value: z.any().describe('Property value'),
1131
+ }))
1132
+ .optional()
1133
+ .describe('Comment properties'),
1134
+ }, async ({ issueKey, body, visibility, properties }) => {
1135
+ try {
1136
+ const comment = await jiraService.createComment(issueKey, {
1137
+ body,
1138
+ visibility,
1139
+ properties: properties,
1140
+ });
1141
+ return {
1142
+ content: [
1143
+ {
1144
+ type: 'text',
1145
+ text: JSON.stringify(comment),
1146
+ },
1147
+ ],
1148
+ };
1149
+ }
1150
+ catch (error) {
1151
+ return {
1152
+ content: [
1153
+ {
1154
+ type: 'text',
1155
+ text: `Failed to create comment: ${error instanceof Error ? error.message : String(error)}`,
1156
+ },
1157
+ ],
1158
+ };
1159
+ }
1160
+ });
1161
+ server.tool('update-comment', 'Update an existing comment', {
1162
+ issueKey: z.string().describe('Issue key (e.g. PROJ-123)'),
1163
+ commentId: z.string().describe('Comment ID to update'),
1164
+ body: z.string().describe('New comment text'),
1165
+ visibility: z
1166
+ .object({
1167
+ type: z.enum(['group', 'role']).describe('Visibility type'),
1168
+ value: z.string().describe('Group name or role name'),
1169
+ })
1170
+ .optional()
1171
+ .describe('Comment visibility restrictions'),
1172
+ properties: z
1173
+ .array(z.object({
1174
+ key: z.string().describe('Property key'),
1175
+ value: z.any().describe('Property value'),
1176
+ }))
1177
+ .optional()
1178
+ .describe('Comment properties'),
1179
+ }, async ({ issueKey, commentId, body, visibility, properties }) => {
1180
+ try {
1181
+ const comment = await jiraService.updateComment(issueKey, commentId, {
1182
+ body,
1183
+ visibility,
1184
+ properties: properties,
1185
+ });
1186
+ return {
1187
+ content: [
1188
+ {
1189
+ type: 'text',
1190
+ text: JSON.stringify(comment),
1191
+ },
1192
+ ],
1193
+ };
1194
+ }
1195
+ catch (error) {
1196
+ return {
1197
+ content: [
1198
+ {
1199
+ type: 'text',
1200
+ text: `Failed to update comment: ${error instanceof Error ? error.message : String(error)}`,
1201
+ },
1202
+ ],
1203
+ };
1204
+ }
1205
+ });
1206
+ server.tool('delete-comment', 'Delete a comment from an issue', {
1207
+ issueKey: z.string().describe('Issue key (e.g. PROJ-123)'),
1208
+ commentId: z.string().describe('Comment ID to delete'),
1209
+ }, async ({ issueKey, commentId }) => {
1210
+ try {
1211
+ await jiraService.deleteComment(issueKey, commentId);
1212
+ return {
1213
+ content: [
1214
+ {
1215
+ type: 'text',
1216
+ text: 'Comment deleted successfully',
1217
+ },
1218
+ ],
1219
+ };
1220
+ }
1221
+ catch (error) {
1222
+ return {
1223
+ content: [
1224
+ {
1225
+ type: 'text',
1226
+ text: `Failed to delete comment: ${error instanceof Error ? error.message : String(error)}`,
1227
+ },
1228
+ ],
1229
+ };
1230
+ }
1231
+ });
1232
+ server.tool('delete-issue', 'Delete an issue', {
1233
+ issueKey: z.string().describe('Issue key (e.g. PROJ-123)'),
1234
+ deleteSubtasks: z.boolean().optional().describe('Whether to delete subtasks (default: false)'),
1235
+ }, async ({ issueKey, deleteSubtasks }) => {
1236
+ try {
1237
+ await jiraService.deleteIssue(issueKey, deleteSubtasks);
1238
+ return {
1239
+ content: [
1240
+ {
1241
+ type: 'text',
1242
+ text: 'Issue deleted successfully',
1243
+ },
1244
+ ],
1245
+ };
1246
+ }
1247
+ catch (error) {
1248
+ return {
1249
+ content: [
1250
+ {
1251
+ type: 'text',
1252
+ text: `Failed to delete issue: ${error instanceof Error ? error.message : String(error)}`,
1253
+ },
1254
+ ],
1255
+ };
1256
+ }
1257
+ });
1258
+ server.tool('get-issue-link-types', 'Get available issue link types', {}, async () => {
1259
+ try {
1260
+ const result = await jiraService.getIssueLinkTypes();
1261
+ return {
1262
+ content: [
1263
+ {
1264
+ type: 'text',
1265
+ text: JSON.stringify(result),
1266
+ },
1267
+ ],
1268
+ };
1269
+ }
1270
+ catch (error) {
1271
+ return {
1272
+ content: [
1273
+ {
1274
+ type: 'text',
1275
+ text: `Failed to get issue link types: ${error instanceof Error ? error.message : String(error)}`,
1276
+ },
1277
+ ],
1278
+ };
1279
+ }
1280
+ });
1281
+ server.tool('get-issue-link-type', 'Get a specific issue link type by ID', {
1282
+ issueLinkTypeId: z.string().describe('Issue link type ID'),
1283
+ }, async ({ issueLinkTypeId }) => {
1284
+ try {
1285
+ const result = await jiraService.getIssueLinkType(issueLinkTypeId);
1286
+ return {
1287
+ content: [
1288
+ {
1289
+ type: 'text',
1290
+ text: JSON.stringify(result),
1291
+ },
1292
+ ],
1293
+ };
1294
+ }
1295
+ catch (error) {
1296
+ return {
1297
+ content: [
1298
+ {
1299
+ type: 'text',
1300
+ text: `Failed to get issue link type: ${error instanceof Error ? error.message : String(error)}`,
1301
+ },
1302
+ ],
1303
+ };
1304
+ }
1305
+ });
1306
+ server.tool('create-issue-link', 'Create a link between two issues', {
1307
+ linkTypeId: z.string().optional().describe('Link type ID (either this or linkTypeName must be provided)'),
1308
+ linkTypeName: z.string().optional().describe('Link type name (either this or linkTypeId must be provided)'),
1309
+ inwardIssueId: z
1310
+ .string()
1311
+ .optional()
1312
+ .describe('Inward issue ID (either this or inwardIssueKey must be provided)'),
1313
+ inwardIssueKey: z
1314
+ .string()
1315
+ .optional()
1316
+ .describe('Inward issue key (either this or inwardIssueId must be provided)'),
1317
+ outwardIssueId: z
1318
+ .string()
1319
+ .optional()
1320
+ .describe('Outward issue ID (either this or outwardIssueKey must be provided)'),
1321
+ outwardIssueKey: z
1322
+ .string()
1323
+ .optional()
1324
+ .describe('Outward issue key (either this or outwardIssueId must be provided)'),
1325
+ comment: z
1326
+ .object({
1327
+ body: z.string().describe('Comment text'),
1328
+ visibility: z
1329
+ .object({
1330
+ type: z.enum(['group', 'role']).describe('Visibility type'),
1331
+ value: z.string().describe('Group name or role name'),
1332
+ })
1333
+ .optional()
1334
+ .describe('Comment visibility restrictions'),
1335
+ })
1336
+ .optional()
1337
+ .describe('Optional comment when creating the link'),
1338
+ }, async ({ linkTypeId, linkTypeName, inwardIssueId, inwardIssueKey, outwardIssueId, outwardIssueKey, comment, }) => {
1339
+ try {
1340
+ // Validate required parameters
1341
+ if (!linkTypeId && !linkTypeName) {
1342
+ throw new Error('Either linkTypeId or linkTypeName must be provided');
1343
+ }
1344
+ if (!inwardIssueId && !inwardIssueKey) {
1345
+ throw new Error('Either inwardIssueId or inwardIssueKey must be provided');
1346
+ }
1347
+ if (!outwardIssueId && !outwardIssueKey) {
1348
+ throw new Error('Either outwardIssueId or outwardIssueKey must be provided');
1349
+ }
1350
+ const type = {};
1351
+ if (linkTypeId)
1352
+ type.id = linkTypeId;
1353
+ if (linkTypeName)
1354
+ type.name = linkTypeName;
1355
+ const inwardIssue = {};
1356
+ if (inwardIssueId)
1357
+ inwardIssue.id = inwardIssueId;
1358
+ if (inwardIssueKey)
1359
+ inwardIssue.key = inwardIssueKey;
1360
+ const outwardIssue = {};
1361
+ if (outwardIssueId)
1362
+ outwardIssue.id = outwardIssueId;
1363
+ if (outwardIssueKey)
1364
+ outwardIssue.key = outwardIssueKey;
1365
+ await jiraService.createIssueLink({
1366
+ type,
1367
+ inwardIssue,
1368
+ outwardIssue,
1369
+ comment,
1370
+ });
1371
+ return {
1372
+ content: [
1373
+ {
1374
+ type: 'text',
1375
+ text: 'Issue link created successfully',
1376
+ },
1377
+ ],
1378
+ };
1379
+ }
1380
+ catch (error) {
1381
+ return {
1382
+ content: [
1383
+ {
1384
+ type: 'text',
1385
+ text: `Failed to create issue link: ${error instanceof Error ? error.message : String(error)}`,
1386
+ },
1387
+ ],
1388
+ };
1389
+ }
1390
+ });
1391
+ server.tool('get-issue-link', 'Get details of a specific issue link', {
1392
+ linkId: z.string().describe('Issue link ID'),
1393
+ }, async ({ linkId }) => {
1394
+ try {
1395
+ const result = await jiraService.getIssueLink(linkId);
1396
+ return {
1397
+ content: [
1398
+ {
1399
+ type: 'text',
1400
+ text: JSON.stringify(result),
1401
+ },
1402
+ ],
1403
+ };
1404
+ }
1405
+ catch (error) {
1406
+ return {
1407
+ content: [
1408
+ {
1409
+ type: 'text',
1410
+ text: `Failed to get issue link: ${error instanceof Error ? error.message : String(error)}`,
1411
+ },
1412
+ ],
1413
+ };
1414
+ }
1415
+ });
1416
+ server.tool('delete-issue-link', 'Delete a link between issues', {
1417
+ linkId: z.string().describe('Issue link ID to delete'),
1418
+ }, async ({ linkId }) => {
1419
+ try {
1420
+ await jiraService.deleteIssueLink(linkId);
1421
+ return {
1422
+ content: [
1423
+ {
1424
+ type: 'text',
1425
+ text: 'Issue link deleted successfully',
1426
+ },
1427
+ ],
1428
+ };
1429
+ }
1430
+ catch (error) {
1431
+ return {
1432
+ content: [
1433
+ {
1434
+ type: 'text',
1435
+ text: `Failed to delete issue link: ${error instanceof Error ? error.message : String(error)}`,
1436
+ },
1437
+ ],
1438
+ };
1439
+ }
1440
+ });
1441
+ server.tool('get-issue-with-links', 'Get an issue with all its links', {
1442
+ issueKey: z.string().describe('Issue key (e.g. PROJ-123)'),
1443
+ }, async ({ issueKey }) => {
1444
+ try {
1445
+ const result = await jiraService.getIssueWithLinks(issueKey);
1446
+ return {
1447
+ content: [
1448
+ {
1449
+ type: 'text',
1450
+ text: JSON.stringify(result),
1451
+ },
1452
+ ],
1453
+ };
1454
+ }
1455
+ catch (error) {
1456
+ return {
1457
+ content: [
1458
+ {
1459
+ type: 'text',
1460
+ text: `Failed to get issue with links: ${error instanceof Error ? error.message : String(error)}`,
1461
+ },
1462
+ ],
1463
+ };
1464
+ }
1465
+ });
1466
+ server.tool('get-issue-watchers', 'Get watchers of an issue', {
1467
+ issueKey: z.string().describe('Issue key (e.g. PROJ-123)'),
1468
+ }, async ({ issueKey }) => {
1469
+ try {
1470
+ const result = await jiraService.getIssueWatchers(issueKey);
1471
+ return {
1472
+ content: [
1473
+ {
1474
+ type: 'text',
1475
+ text: JSON.stringify(result),
1476
+ },
1477
+ ],
1478
+ };
1479
+ }
1480
+ catch (error) {
1481
+ return {
1482
+ content: [
1483
+ {
1484
+ type: 'text',
1485
+ text: `Failed to get issue watchers: ${error instanceof Error ? error.message : String(error)}`,
1486
+ },
1487
+ ],
1488
+ };
1489
+ }
1490
+ });
1491
+ server.tool('add-watcher', 'Add a user as watcher to an issue', {
1492
+ issueKey: z.string().describe('Issue key (e.g. PROJ-123)'),
1493
+ accountId: z.string().describe('Account ID of the user to add as watcher'),
1494
+ }, async ({ issueKey, accountId }) => {
1495
+ try {
1496
+ await jiraService.addWatcher(issueKey, accountId);
1497
+ return {
1498
+ content: [
1499
+ {
1500
+ type: 'text',
1501
+ text: 'Watcher added successfully',
1502
+ },
1503
+ ],
1504
+ };
1505
+ }
1506
+ catch (error) {
1507
+ return {
1508
+ content: [
1509
+ {
1510
+ type: 'text',
1511
+ text: `Failed to add watcher: ${error instanceof Error ? error.message : String(error)}`,
1512
+ },
1513
+ ],
1514
+ };
1515
+ }
1516
+ });
1517
+ server.tool('remove-watcher', 'Remove a user as watcher from an issue', {
1518
+ issueKey: z.string().describe('Issue key (e.g. PROJ-123)'),
1519
+ accountId: z.string().describe('Account ID of the user to remove as watcher'),
1520
+ }, async ({ issueKey, accountId }) => {
1521
+ try {
1522
+ await jiraService.removeWatcher(issueKey, accountId);
1523
+ return {
1524
+ content: [
1525
+ {
1526
+ type: 'text',
1527
+ text: 'Watcher removed successfully',
1528
+ },
1529
+ ],
1530
+ };
1531
+ }
1532
+ catch (error) {
1533
+ return {
1534
+ content: [
1535
+ {
1536
+ type: 'text',
1537
+ text: `Failed to remove watcher: ${error instanceof Error ? error.message : String(error)}`,
1538
+ },
1539
+ ],
1540
+ };
1541
+ }
1542
+ });
1543
+ server.tool('watch-issue', 'Start watching an issue (for current user)', {
1544
+ issueKey: z.string().describe('Issue key (e.g. PROJ-123)'),
1545
+ }, async ({ issueKey }) => {
1546
+ try {
1547
+ await jiraService.watchIssue(issueKey);
1548
+ return {
1549
+ content: [
1550
+ {
1551
+ type: 'text',
1552
+ text: 'Now watching the issue',
1553
+ },
1554
+ ],
1555
+ };
1556
+ }
1557
+ catch (error) {
1558
+ return {
1559
+ content: [
1560
+ {
1561
+ type: 'text',
1562
+ text: `Failed to watch issue: ${error instanceof Error ? error.message : String(error)}`,
1563
+ },
1564
+ ],
1565
+ };
1566
+ }
1567
+ });
1568
+ server.tool('unwatch-issue', 'Stop watching an issue (for current user)', {
1569
+ issueKey: z.string().describe('Issue key (e.g. PROJ-123)'),
1570
+ }, async ({ issueKey }) => {
1571
+ try {
1572
+ await jiraService.unwatchIssue(issueKey);
1573
+ return {
1574
+ content: [
1575
+ {
1576
+ type: 'text',
1577
+ text: 'Stopped watching the issue',
1578
+ },
1579
+ ],
1580
+ };
1581
+ }
1582
+ catch (error) {
1583
+ return {
1584
+ content: [
1585
+ {
1586
+ type: 'text',
1587
+ text: `Failed to unwatch issue: ${error instanceof Error ? error.message : String(error)}`,
1588
+ },
1589
+ ],
1590
+ };
1591
+ }
1592
+ });
1593
+ };