xray-mcp 1.2.3 → 1.2.5

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,936 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
4
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
5
+ import {
6
+ CallToolRequestSchema,
7
+ ListToolsRequestSchema,
8
+ Tool,
9
+ } from '@modelcontextprotocol/sdk/types.js';
10
+ import { XrayClient, TestCase, TestExecution, TestRunStatus, TestPlan, TestSet } from './xray-client.js';
11
+
12
+ // Validate required environment variables
13
+ const XRAY_CLIENT_ID = process.env.XRAY_CLIENT_ID;
14
+ const XRAY_CLIENT_SECRET = process.env.XRAY_CLIENT_SECRET;
15
+
16
+ if (!XRAY_CLIENT_ID || !XRAY_CLIENT_SECRET) {
17
+ console.error('Error: XRAY_CLIENT_ID and XRAY_CLIENT_SECRET must be set in environment variables');
18
+ process.exit(1);
19
+ }
20
+
21
+ // Initialize Xray client
22
+ const xrayClient = new XrayClient({
23
+ clientId: XRAY_CLIENT_ID,
24
+ clientSecret: XRAY_CLIENT_SECRET,
25
+ });
26
+
27
+ // Define available tools
28
+ const tools: Tool[] = [
29
+ {
30
+ name: 'create_test_case',
31
+ description: 'Create a new test case in Xray Cloud',
32
+ inputSchema: {
33
+ type: 'object',
34
+ properties: {
35
+ projectKey: {
36
+ type: 'string',
37
+ description: 'The Jira project key (e.g., "PROJ")',
38
+ },
39
+ summary: {
40
+ type: 'string',
41
+ description: 'The test case summary/title',
42
+ },
43
+ description: {
44
+ type: 'string',
45
+ description: 'The test case description',
46
+ },
47
+ testType: {
48
+ type: 'string',
49
+ enum: ['Manual', 'Cucumber', 'Generic'],
50
+ description: 'The type of test case',
51
+ default: 'Manual',
52
+ },
53
+ labels: {
54
+ type: 'array',
55
+ items: { type: 'string' },
56
+ description: 'Labels to attach to the test case',
57
+ },
58
+ priority: {
59
+ type: 'string',
60
+ description: 'Priority of the test case (e.g., "High", "Medium", "Low")',
61
+ },
62
+ },
63
+ required: ['projectKey', 'summary'],
64
+ },
65
+ },
66
+ {
67
+ name: 'get_test_case',
68
+ description: 'Get details of a specific test case by key',
69
+ inputSchema: {
70
+ type: 'object',
71
+ properties: {
72
+ testKey: {
73
+ type: 'string',
74
+ description: 'The test case key (e.g., "PROJ-123")',
75
+ },
76
+ },
77
+ required: ['testKey'],
78
+ },
79
+ },
80
+ {
81
+ name: 'update_test_case',
82
+ description: 'Update an existing test case',
83
+ inputSchema: {
84
+ type: 'object',
85
+ properties: {
86
+ testKey: {
87
+ type: 'string',
88
+ description: 'The test case key (e.g., "PROJ-123")',
89
+ },
90
+ summary: {
91
+ type: 'string',
92
+ description: 'New summary/title for the test case',
93
+ },
94
+ description: {
95
+ type: 'string',
96
+ description: 'New description for the test case',
97
+ },
98
+ labels: {
99
+ type: 'array',
100
+ items: { type: 'string' },
101
+ description: 'New labels for the test case',
102
+ },
103
+ priority: {
104
+ type: 'string',
105
+ description: 'New priority for the test case',
106
+ },
107
+ },
108
+ required: ['testKey'],
109
+ },
110
+ },
111
+ {
112
+ name: 'delete_test_case',
113
+ description: 'Delete a test case',
114
+ inputSchema: {
115
+ type: 'object',
116
+ properties: {
117
+ testKey: {
118
+ type: 'string',
119
+ description: 'The test case key to delete (e.g., "PROJ-123")',
120
+ },
121
+ },
122
+ required: ['testKey'],
123
+ },
124
+ },
125
+ {
126
+ name: 'search_test_cases',
127
+ description: 'Search for test cases using JQL (Jira Query Language)',
128
+ inputSchema: {
129
+ type: 'object',
130
+ properties: {
131
+ jql: {
132
+ type: 'string',
133
+ description: 'JQL query to search test cases (e.g., "project = PROJ AND labels = automation")',
134
+ },
135
+ maxResults: {
136
+ type: 'number',
137
+ description: 'Maximum number of results to return',
138
+ default: 50,
139
+ },
140
+ },
141
+ required: ['jql'],
142
+ },
143
+ },
144
+ {
145
+ name: 'get_project_test_cases',
146
+ description: 'Get all test cases for a specific project',
147
+ inputSchema: {
148
+ type: 'object',
149
+ properties: {
150
+ projectKey: {
151
+ type: 'string',
152
+ description: 'The Jira project key (e.g., "PROJ")',
153
+ },
154
+ maxResults: {
155
+ type: 'number',
156
+ description: 'Maximum number of results to return',
157
+ default: 50,
158
+ },
159
+ },
160
+ required: ['projectKey'],
161
+ },
162
+ },
163
+ // Test Execution tools
164
+ {
165
+ name: 'create_test_execution',
166
+ description: 'Create a new test execution in Xray Cloud to run tests',
167
+ inputSchema: {
168
+ type: 'object',
169
+ properties: {
170
+ projectKey: {
171
+ type: 'string',
172
+ description: 'The Jira project key (e.g., "PROJ")',
173
+ },
174
+ summary: {
175
+ type: 'string',
176
+ description: 'The test execution summary/title',
177
+ },
178
+ description: {
179
+ type: 'string',
180
+ description: 'The test execution description',
181
+ },
182
+ testIssueIds: {
183
+ type: 'array',
184
+ items: { type: 'string' },
185
+ description: 'Array of test issue IDs to include in this execution (e.g., ["10001", "10002"])',
186
+ },
187
+ testEnvironments: {
188
+ type: 'array',
189
+ items: { type: 'string' },
190
+ description: 'Array of test environments (e.g., ["Chrome", "iOS"])',
191
+ },
192
+ },
193
+ required: ['projectKey', 'summary'],
194
+ },
195
+ },
196
+ {
197
+ name: 'get_test_execution',
198
+ description: 'Get details of a specific test execution by key, including all test runs',
199
+ inputSchema: {
200
+ type: 'object',
201
+ properties: {
202
+ testExecutionKey: {
203
+ type: 'string',
204
+ description: 'The test execution key (e.g., "PROJ-456")',
205
+ },
206
+ },
207
+ required: ['testExecutionKey'],
208
+ },
209
+ },
210
+ {
211
+ name: 'search_test_executions',
212
+ description: 'Search for test executions using JQL (Jira Query Language)',
213
+ inputSchema: {
214
+ type: 'object',
215
+ properties: {
216
+ jql: {
217
+ type: 'string',
218
+ description: 'JQL query to search test executions (e.g., "project = PROJ AND created >= -7d")',
219
+ },
220
+ maxResults: {
221
+ type: 'number',
222
+ description: 'Maximum number of results to return',
223
+ default: 50,
224
+ },
225
+ },
226
+ required: ['jql'],
227
+ },
228
+ },
229
+ {
230
+ name: 'get_project_test_executions',
231
+ description: 'Get all test executions for a specific project',
232
+ inputSchema: {
233
+ type: 'object',
234
+ properties: {
235
+ projectKey: {
236
+ type: 'string',
237
+ description: 'The Jira project key (e.g., "PROJ")',
238
+ },
239
+ maxResults: {
240
+ type: 'number',
241
+ description: 'Maximum number of results to return',
242
+ default: 50,
243
+ },
244
+ },
245
+ required: ['projectKey'],
246
+ },
247
+ },
248
+ {
249
+ name: 'update_test_run_status',
250
+ description: 'Update the status of a specific test run (e.g., mark as PASS or FAIL)',
251
+ inputSchema: {
252
+ type: 'object',
253
+ properties: {
254
+ testRunId: {
255
+ type: 'string',
256
+ description: 'The test run ID (obtained from test execution details)',
257
+ },
258
+ status: {
259
+ type: 'string',
260
+ enum: ['TODO', 'EXECUTING', 'PASS', 'FAIL', 'ABORTED', 'PASSED', 'FAILED'],
261
+ description: 'The new status for the test run',
262
+ },
263
+ },
264
+ required: ['testRunId', 'status'],
265
+ },
266
+ },
267
+ // Test Plan tools
268
+ {
269
+ name: 'create_test_plan',
270
+ description: 'Create a new test plan in Xray Cloud to organize tests',
271
+ inputSchema: {
272
+ type: 'object',
273
+ properties: {
274
+ projectKey: {
275
+ type: 'string',
276
+ description: 'The Jira project key (e.g., "PROJ")',
277
+ },
278
+ summary: {
279
+ type: 'string',
280
+ description: 'The test plan summary/title',
281
+ },
282
+ description: {
283
+ type: 'string',
284
+ description: 'The test plan description',
285
+ },
286
+ testIssueIds: {
287
+ type: 'array',
288
+ items: { type: 'string' },
289
+ description: 'Array of test issue IDs to include in this plan',
290
+ },
291
+ },
292
+ required: ['projectKey', 'summary'],
293
+ },
294
+ },
295
+ {
296
+ name: 'get_test_plan',
297
+ description: 'Get details of a specific test plan by key, including all tests',
298
+ inputSchema: {
299
+ type: 'object',
300
+ properties: {
301
+ testPlanKey: {
302
+ type: 'string',
303
+ description: 'The test plan key (e.g., "PROJ-789")',
304
+ },
305
+ },
306
+ required: ['testPlanKey'],
307
+ },
308
+ },
309
+ {
310
+ name: 'search_test_plans',
311
+ description: 'Search for test plans using JQL (Jira Query Language)',
312
+ inputSchema: {
313
+ type: 'object',
314
+ properties: {
315
+ jql: {
316
+ type: 'string',
317
+ description: 'JQL query to search test plans',
318
+ },
319
+ maxResults: {
320
+ type: 'number',
321
+ description: 'Maximum number of results to return',
322
+ default: 50,
323
+ },
324
+ },
325
+ required: ['jql'],
326
+ },
327
+ },
328
+ {
329
+ name: 'get_project_test_plans',
330
+ description: 'Get all test plans for a specific project',
331
+ inputSchema: {
332
+ type: 'object',
333
+ properties: {
334
+ projectKey: {
335
+ type: 'string',
336
+ description: 'The Jira project key (e.g., "PROJ")',
337
+ },
338
+ maxResults: {
339
+ type: 'number',
340
+ description: 'Maximum number of results to return',
341
+ default: 50,
342
+ },
343
+ },
344
+ required: ['projectKey'],
345
+ },
346
+ },
347
+ {
348
+ name: 'add_tests_to_test_plan',
349
+ description: 'Add tests to an existing test plan',
350
+ inputSchema: {
351
+ type: 'object',
352
+ properties: {
353
+ testPlanIssueId: {
354
+ type: 'string',
355
+ description: 'The test plan issue ID (not key)',
356
+ },
357
+ testIssueIds: {
358
+ type: 'array',
359
+ items: { type: 'string' },
360
+ description: 'Array of test issue IDs to add',
361
+ },
362
+ },
363
+ required: ['testPlanIssueId', 'testIssueIds'],
364
+ },
365
+ },
366
+ {
367
+ name: 'remove_tests_from_test_plan',
368
+ description: 'Remove tests from an existing test plan',
369
+ inputSchema: {
370
+ type: 'object',
371
+ properties: {
372
+ testPlanIssueId: {
373
+ type: 'string',
374
+ description: 'The test plan issue ID (not key)',
375
+ },
376
+ testIssueIds: {
377
+ type: 'array',
378
+ items: { type: 'string' },
379
+ description: 'Array of test issue IDs to remove',
380
+ },
381
+ },
382
+ required: ['testPlanIssueId', 'testIssueIds'],
383
+ },
384
+ },
385
+ // Test Set tools
386
+ {
387
+ name: 'create_test_set',
388
+ description: 'Create a new test set in Xray Cloud to group related tests',
389
+ inputSchema: {
390
+ type: 'object',
391
+ properties: {
392
+ projectKey: {
393
+ type: 'string',
394
+ description: 'The Jira project key (e.g., "PROJ")',
395
+ },
396
+ summary: {
397
+ type: 'string',
398
+ description: 'The test set summary/title',
399
+ },
400
+ description: {
401
+ type: 'string',
402
+ description: 'The test set description',
403
+ },
404
+ testIssueIds: {
405
+ type: 'array',
406
+ items: { type: 'string' },
407
+ description: 'Array of test issue IDs to include in this set',
408
+ },
409
+ },
410
+ required: ['projectKey', 'summary'],
411
+ },
412
+ },
413
+ {
414
+ name: 'get_test_set',
415
+ description: 'Get details of a specific test set by key, including all tests',
416
+ inputSchema: {
417
+ type: 'object',
418
+ properties: {
419
+ testSetKey: {
420
+ type: 'string',
421
+ description: 'The test set key (e.g., "PROJ-890")',
422
+ },
423
+ },
424
+ required: ['testSetKey'],
425
+ },
426
+ },
427
+ {
428
+ name: 'search_test_sets',
429
+ description: 'Search for test sets using JQL (Jira Query Language)',
430
+ inputSchema: {
431
+ type: 'object',
432
+ properties: {
433
+ jql: {
434
+ type: 'string',
435
+ description: 'JQL query to search test sets',
436
+ },
437
+ maxResults: {
438
+ type: 'number',
439
+ description: 'Maximum number of results to return',
440
+ default: 50,
441
+ },
442
+ },
443
+ required: ['jql'],
444
+ },
445
+ },
446
+ {
447
+ name: 'get_project_test_sets',
448
+ description: 'Get all test sets for a specific project',
449
+ inputSchema: {
450
+ type: 'object',
451
+ properties: {
452
+ projectKey: {
453
+ type: 'string',
454
+ description: 'The Jira project key (e.g., "PROJ")',
455
+ },
456
+ maxResults: {
457
+ type: 'number',
458
+ description: 'Maximum number of results to return',
459
+ default: 50,
460
+ },
461
+ },
462
+ required: ['projectKey'],
463
+ },
464
+ },
465
+ {
466
+ name: 'add_tests_to_test_set',
467
+ description: 'Add tests to an existing test set',
468
+ inputSchema: {
469
+ type: 'object',
470
+ properties: {
471
+ testSetIssueId: {
472
+ type: 'string',
473
+ description: 'The test set issue ID (not key)',
474
+ },
475
+ testIssueIds: {
476
+ type: 'array',
477
+ items: { type: 'string' },
478
+ description: 'Array of test issue IDs to add',
479
+ },
480
+ },
481
+ required: ['testSetIssueId', 'testIssueIds'],
482
+ },
483
+ },
484
+ {
485
+ name: 'remove_tests_from_test_set',
486
+ description: 'Remove tests from an existing test set',
487
+ inputSchema: {
488
+ type: 'object',
489
+ properties: {
490
+ testSetIssueId: {
491
+ type: 'string',
492
+ description: 'The test set issue ID (not key)',
493
+ },
494
+ testIssueIds: {
495
+ type: 'array',
496
+ items: { type: 'string' },
497
+ description: 'Array of test issue IDs to remove',
498
+ },
499
+ },
500
+ required: ['testSetIssueId', 'testIssueIds'],
501
+ },
502
+ },
503
+ ];
504
+
505
+ // Create server instance
506
+ const server = new Server(
507
+ {
508
+ name: 'xray-mcp-server',
509
+ version: '1.0.0',
510
+ },
511
+ {
512
+ capabilities: {
513
+ tools: {},
514
+ },
515
+ }
516
+ );
517
+
518
+ // Handle tool listing
519
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
520
+ return { tools };
521
+ });
522
+
523
+ // Handle tool execution
524
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
525
+ const { name, arguments: args } = request.params;
526
+
527
+ if (!args) {
528
+ throw new Error('Missing arguments');
529
+ }
530
+
531
+ try {
532
+ switch (name) {
533
+ case 'create_test_case': {
534
+ const testCase: TestCase = {
535
+ projectKey: args.projectKey as string,
536
+ summary: args.summary as string,
537
+ description: args.description as string | undefined,
538
+ testType: args.testType as 'Manual' | 'Cucumber' | 'Generic' | undefined,
539
+ labels: args.labels as string[] | undefined,
540
+ priority: args.priority as string | undefined,
541
+ };
542
+
543
+ const result = await xrayClient.createTestCase(testCase);
544
+ return {
545
+ content: [
546
+ {
547
+ type: 'text',
548
+ text: JSON.stringify(result, null, 2),
549
+ },
550
+ ],
551
+ };
552
+ }
553
+
554
+ case 'get_test_case': {
555
+ const result = await xrayClient.getTestCase(args.testKey as string);
556
+ return {
557
+ content: [
558
+ {
559
+ type: 'text',
560
+ text: JSON.stringify(result, null, 2),
561
+ },
562
+ ],
563
+ };
564
+ }
565
+
566
+ case 'update_test_case': {
567
+ const updates: Partial<TestCase> = {};
568
+ if (args.summary) updates.summary = args.summary as string;
569
+ if (args.description) updates.description = args.description as string;
570
+ if (args.labels) updates.labels = args.labels as string[];
571
+ if (args.priority) updates.priority = args.priority as string;
572
+
573
+ await xrayClient.updateTestCase(args.testKey as string, updates);
574
+ return {
575
+ content: [
576
+ {
577
+ type: 'text',
578
+ text: `Test case ${args.testKey} updated successfully`,
579
+ },
580
+ ],
581
+ };
582
+ }
583
+
584
+ case 'delete_test_case': {
585
+ await xrayClient.deleteTestCase(args.testKey as string);
586
+ return {
587
+ content: [
588
+ {
589
+ type: 'text',
590
+ text: `Test case ${args.testKey} deleted successfully`,
591
+ },
592
+ ],
593
+ };
594
+ }
595
+
596
+ case 'search_test_cases': {
597
+ const result = await xrayClient.searchTestCases(
598
+ args.jql as string,
599
+ args.maxResults as number | undefined
600
+ );
601
+ return {
602
+ content: [
603
+ {
604
+ type: 'text',
605
+ text: JSON.stringify(result, null, 2),
606
+ },
607
+ ],
608
+ };
609
+ }
610
+
611
+ case 'get_project_test_cases': {
612
+ const result = await xrayClient.getTestCasesByProject(
613
+ args.projectKey as string,
614
+ args.maxResults as number | undefined
615
+ );
616
+ return {
617
+ content: [
618
+ {
619
+ type: 'text',
620
+ text: JSON.stringify(result, null, 2),
621
+ },
622
+ ],
623
+ };
624
+ }
625
+
626
+ // Test Execution handlers
627
+ case 'create_test_execution': {
628
+ const testExecution: TestExecution = {
629
+ projectKey: args.projectKey as string,
630
+ summary: args.summary as string,
631
+ description: args.description as string | undefined,
632
+ testIssueIds: args.testIssueIds as string[] | undefined,
633
+ testEnvironments: args.testEnvironments as string[] | undefined,
634
+ };
635
+
636
+ const result = await xrayClient.createTestExecution(testExecution);
637
+ return {
638
+ content: [
639
+ {
640
+ type: 'text',
641
+ text: JSON.stringify(result, null, 2),
642
+ },
643
+ ],
644
+ };
645
+ }
646
+
647
+ case 'get_test_execution': {
648
+ const result = await xrayClient.getTestExecution(args.testExecutionKey as string);
649
+ return {
650
+ content: [
651
+ {
652
+ type: 'text',
653
+ text: JSON.stringify(result, null, 2),
654
+ },
655
+ ],
656
+ };
657
+ }
658
+
659
+ case 'search_test_executions': {
660
+ const result = await xrayClient.searchTestExecutions(
661
+ args.jql as string,
662
+ args.maxResults as number | undefined
663
+ );
664
+ return {
665
+ content: [
666
+ {
667
+ type: 'text',
668
+ text: JSON.stringify(result, null, 2),
669
+ },
670
+ ],
671
+ };
672
+ }
673
+
674
+ case 'get_project_test_executions': {
675
+ const result = await xrayClient.getTestExecutionsByProject(
676
+ args.projectKey as string,
677
+ args.maxResults as number | undefined
678
+ );
679
+ return {
680
+ content: [
681
+ {
682
+ type: 'text',
683
+ text: JSON.stringify(result, null, 2),
684
+ },
685
+ ],
686
+ };
687
+ }
688
+
689
+ case 'update_test_run_status': {
690
+ const result = await xrayClient.updateTestRunStatus(
691
+ args.testRunId as string,
692
+ args.status as TestRunStatus
693
+ );
694
+ return {
695
+ content: [
696
+ {
697
+ type: 'text',
698
+ text: `Test run ${args.testRunId} status updated to ${args.status}: ${result}`,
699
+ },
700
+ ],
701
+ };
702
+ }
703
+
704
+ case 'create_test_plan': {
705
+ const testPlan: TestPlan = {
706
+ projectKey: args.projectKey as string,
707
+ summary: args.summary as string,
708
+ description: args.description as string | undefined,
709
+ testIssueIds: args.testIssueIds as string[] | undefined,
710
+ };
711
+
712
+ const result = await xrayClient.createTestPlan(testPlan);
713
+ return {
714
+ content: [
715
+ {
716
+ type: 'text',
717
+ text: JSON.stringify(result, null, 2),
718
+ },
719
+ ],
720
+ };
721
+ }
722
+
723
+ case 'get_test_plan': {
724
+ const result = await xrayClient.getTestPlan(args.testPlanKey as string);
725
+ return {
726
+ content: [
727
+ {
728
+ type: 'text',
729
+ text: JSON.stringify(result, null, 2),
730
+ },
731
+ ],
732
+ };
733
+ }
734
+
735
+ case 'search_test_plans': {
736
+ const result = await xrayClient.searchTestPlans(
737
+ args.jql as string,
738
+ args.maxResults as number | undefined
739
+ );
740
+ return {
741
+ content: [
742
+ {
743
+ type: 'text',
744
+ text: JSON.stringify(result, null, 2),
745
+ },
746
+ ],
747
+ };
748
+ }
749
+
750
+ case 'get_project_test_plans': {
751
+ const result = await xrayClient.getTestPlansByProject(
752
+ args.projectKey as string,
753
+ args.maxResults as number | undefined
754
+ );
755
+ return {
756
+ content: [
757
+ {
758
+ type: 'text',
759
+ text: JSON.stringify(result, null, 2),
760
+ },
761
+ ],
762
+ };
763
+ }
764
+
765
+ case 'add_tests_to_test_plan': {
766
+ const result = await xrayClient.addTestsToTestPlan(
767
+ args.testPlanIssueId as string,
768
+ args.testIssueIds as string[]
769
+ );
770
+ return {
771
+ content: [
772
+ {
773
+ type: 'text',
774
+ text: JSON.stringify(result, null, 2),
775
+ },
776
+ ],
777
+ };
778
+ }
779
+
780
+ case 'remove_tests_from_test_plan': {
781
+ const result = await xrayClient.removeTestsFromTestPlan(
782
+ args.testPlanIssueId as string,
783
+ args.testIssueIds as string[]
784
+ );
785
+ return {
786
+ content: [
787
+ {
788
+ type: 'text',
789
+ text: JSON.stringify(result, null, 2),
790
+ },
791
+ ],
792
+ };
793
+ }
794
+
795
+ case 'create_test_set': {
796
+ const testSet: TestSet = {
797
+ projectKey: args.projectKey as string,
798
+ summary: args.summary as string,
799
+ description: args.description as string | undefined,
800
+ testIssueIds: args.testIssueIds as string[] | undefined,
801
+ };
802
+
803
+ const result = await xrayClient.createTestSet(testSet);
804
+ return {
805
+ content: [
806
+ {
807
+ type: 'text',
808
+ text: JSON.stringify(result, null, 2),
809
+ },
810
+ ],
811
+ };
812
+ }
813
+
814
+ case 'get_test_set': {
815
+ const result = await xrayClient.getTestSet(args.testSetKey as string);
816
+ return {
817
+ content: [
818
+ {
819
+ type: 'text',
820
+ text: JSON.stringify(result, null, 2),
821
+ },
822
+ ],
823
+ };
824
+ }
825
+
826
+ case 'search_test_sets': {
827
+ const result = await xrayClient.searchTestSets(
828
+ args.jql as string,
829
+ args.maxResults as number | undefined
830
+ );
831
+ return {
832
+ content: [
833
+ {
834
+ type: 'text',
835
+ text: JSON.stringify(result, null, 2),
836
+ },
837
+ ],
838
+ };
839
+ }
840
+
841
+ case 'get_project_test_sets': {
842
+ const result = await xrayClient.getTestSetsByProject(
843
+ args.projectKey as string,
844
+ args.maxResults as number | undefined
845
+ );
846
+ return {
847
+ content: [
848
+ {
849
+ type: 'text',
850
+ text: JSON.stringify(result, null, 2),
851
+ },
852
+ ],
853
+ };
854
+ }
855
+
856
+ case 'add_tests_to_test_set': {
857
+ const result = await xrayClient.addTestsToTestSet(
858
+ args.testSetIssueId as string,
859
+ args.testIssueIds as string[]
860
+ );
861
+ return {
862
+ content: [
863
+ {
864
+ type: 'text',
865
+ text: JSON.stringify(result, null, 2),
866
+ },
867
+ ],
868
+ };
869
+ }
870
+
871
+ case 'remove_tests_from_test_set': {
872
+ const result = await xrayClient.removeTestsFromTestSet(
873
+ args.testSetIssueId as string,
874
+ args.testIssueIds as string[]
875
+ );
876
+ return {
877
+ content: [
878
+ {
879
+ type: 'text',
880
+ text: JSON.stringify(result, null, 2),
881
+ },
882
+ ],
883
+ };
884
+ }
885
+
886
+ default:
887
+ throw new Error(`Unknown tool: ${name}`);
888
+ }
889
+ } catch (error) {
890
+ const errorMessage = error instanceof Error ? error.message : String(error);
891
+ return {
892
+ content: [
893
+ {
894
+ type: 'text',
895
+ text: `Error: ${errorMessage}`,
896
+ },
897
+ ],
898
+ isError: true,
899
+ };
900
+ }
901
+ });
902
+
903
+ // Start the server
904
+ async function main() {
905
+ const transport = new StdioServerTransport();
906
+ await server.connect(transport);
907
+
908
+ // Handle EPIPE errors (broken pipe when client disconnects)
909
+ process.stdout.on('error', (err: NodeJS.ErrnoException) => {
910
+ if (err.code === 'EPIPE') {
911
+ process.exit(0);
912
+ }
913
+ });
914
+
915
+ process.stderr.on('error', (err: NodeJS.ErrnoException) => {
916
+ if (err.code === 'EPIPE') {
917
+ process.exit(0);
918
+ }
919
+ });
920
+
921
+ // Handle graceful shutdown
922
+ process.on('SIGINT', async () => {
923
+ await server.close();
924
+ process.exit(0);
925
+ });
926
+
927
+ process.on('SIGTERM', async () => {
928
+ await server.close();
929
+ process.exit(0);
930
+ });
931
+ }
932
+
933
+ main().catch((error) => {
934
+ console.error('Fatal error in MCP server:', error);
935
+ process.exit(1);
936
+ });