zephyr-scale-mcp-server 0.2.8 → 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.
- package/build/index.js +3 -1
- package/build/tool-handlers.js +70 -1
- package/build/tool-schemas.js +35 -0
- package/package.json +1 -1
- package/src/index.ts +3 -1
- package/src/tool-handlers.ts +70 -0
- package/src/tool-schemas.ts +35 -0
- package/src/types.ts +9 -0
package/build/index.js
CHANGED
|
@@ -14,7 +14,7 @@ class ZephyrServer {
|
|
|
14
14
|
const jiraConfig = createJiraConfig();
|
|
15
15
|
this.server = new Server({
|
|
16
16
|
name: 'zephyr-server',
|
|
17
|
-
version: '0.
|
|
17
|
+
version: '0.3.0',
|
|
18
18
|
}, {
|
|
19
19
|
capabilities: {
|
|
20
20
|
tools: {},
|
|
@@ -63,6 +63,8 @@ class ZephyrServer {
|
|
|
63
63
|
return await this.toolHandlers.getTestExecution(args);
|
|
64
64
|
case 'search_test_cases_by_folder':
|
|
65
65
|
return await this.toolHandlers.searchTestCasesByFolder(args);
|
|
66
|
+
case 'search_test_runs':
|
|
67
|
+
return await this.toolHandlers.searchTestRuns(args);
|
|
66
68
|
case 'add_test_cases_to_run':
|
|
67
69
|
return await this.toolHandlers.addTestCasesToRun(args);
|
|
68
70
|
default:
|
package/build/tool-handlers.js
CHANGED
|
@@ -274,7 +274,7 @@ export class ZephyrToolHandlers {
|
|
|
274
274
|
}
|
|
275
275
|
}
|
|
276
276
|
async createTestRun(args) {
|
|
277
|
-
const { project_key, name, test_case_keys, test_plan_key, folder, planned_start_date, planned_end_date, description, owner, environment, custom_fields } = args;
|
|
277
|
+
const { project_key, name, test_case_keys, test_plan_key, folder, planned_start_date, planned_end_date, description, owner, environment, issue_key, issue_links, custom_fields } = args;
|
|
278
278
|
// Build the basic payload
|
|
279
279
|
const payload = {
|
|
280
280
|
projectKey: project_key,
|
|
@@ -298,6 +298,10 @@ export class ZephyrToolHandlers {
|
|
|
298
298
|
payload.owner = owner;
|
|
299
299
|
if (environment)
|
|
300
300
|
payload.environment = environment;
|
|
301
|
+
if (issue_key)
|
|
302
|
+
payload.issueKey = issue_key;
|
|
303
|
+
if (issue_links && issue_links.length > 0)
|
|
304
|
+
payload.issueLinks = issue_links;
|
|
301
305
|
if (custom_fields)
|
|
302
306
|
payload.customFields = custom_fields;
|
|
303
307
|
if (test_plan_key)
|
|
@@ -486,6 +490,71 @@ export class ZephyrToolHandlers {
|
|
|
486
490
|
throw new McpError(ErrorCode.InternalError, `Failed to search test cases by folder: ${errorMessage}`);
|
|
487
491
|
}
|
|
488
492
|
}
|
|
493
|
+
async searchTestRuns(args) {
|
|
494
|
+
const { project_key, folder, max_results = 200, fields } = args;
|
|
495
|
+
// Build query string per Zephyr Scale API docs
|
|
496
|
+
const queryParts = [];
|
|
497
|
+
if (project_key)
|
|
498
|
+
queryParts.push(`projectKey = "${project_key}"`);
|
|
499
|
+
if (folder)
|
|
500
|
+
queryParts.push(`folder = "${folder}"`);
|
|
501
|
+
if (queryParts.length === 0) {
|
|
502
|
+
throw new McpError(ErrorCode.InvalidParams, 'At least one of project_key or folder must be provided.');
|
|
503
|
+
}
|
|
504
|
+
const query = queryParts.join(' AND ');
|
|
505
|
+
const params = { query, maxResults: max_results };
|
|
506
|
+
if (fields)
|
|
507
|
+
params.fields = fields;
|
|
508
|
+
const searchEndpoint = this.jiraConfig.type === 'cloud'
|
|
509
|
+
? '/testruns/search'
|
|
510
|
+
: '/rest/atm/1.0/testrun/search';
|
|
511
|
+
try {
|
|
512
|
+
const response = await this.axiosInstance.get(searchEndpoint, { params });
|
|
513
|
+
let testRuns = [];
|
|
514
|
+
if (Array.isArray(response.data)) {
|
|
515
|
+
testRuns = response.data;
|
|
516
|
+
}
|
|
517
|
+
else if (response.data.values && Array.isArray(response.data.values)) {
|
|
518
|
+
testRuns = response.data.values;
|
|
519
|
+
}
|
|
520
|
+
else if (response.data.results && Array.isArray(response.data.results)) {
|
|
521
|
+
testRuns = response.data.results;
|
|
522
|
+
}
|
|
523
|
+
return {
|
|
524
|
+
content: [
|
|
525
|
+
{
|
|
526
|
+
type: 'text',
|
|
527
|
+
text: `✅ Found ${testRuns.length} test run(s) matching query "${query}":\n${JSON.stringify({
|
|
528
|
+
query,
|
|
529
|
+
totalCount: testRuns.length,
|
|
530
|
+
testRuns: testRuns.map((tr) => ({
|
|
531
|
+
key: tr.key,
|
|
532
|
+
name: tr.name,
|
|
533
|
+
status: tr.status,
|
|
534
|
+
folder: tr.folder,
|
|
535
|
+
testCaseCount: tr.testCaseCount,
|
|
536
|
+
issueKey: tr.issueKey,
|
|
537
|
+
}))
|
|
538
|
+
}, null, 2)}`,
|
|
539
|
+
},
|
|
540
|
+
],
|
|
541
|
+
};
|
|
542
|
+
}
|
|
543
|
+
catch (error) {
|
|
544
|
+
let errorMessage = 'Unknown error';
|
|
545
|
+
if (error instanceof Error && 'response' in error) {
|
|
546
|
+
const axiosError = error;
|
|
547
|
+
errorMessage = `Status: ${axiosError.response?.status}, Data: ${JSON.stringify(axiosError.response?.data)}`;
|
|
548
|
+
}
|
|
549
|
+
else if (error instanceof Error) {
|
|
550
|
+
errorMessage = error.message;
|
|
551
|
+
}
|
|
552
|
+
else {
|
|
553
|
+
errorMessage = String(error);
|
|
554
|
+
}
|
|
555
|
+
throw new McpError(ErrorCode.InternalError, `Failed to search test runs: ${errorMessage}`);
|
|
556
|
+
}
|
|
557
|
+
}
|
|
489
558
|
async addTestCasesToRun(args) {
|
|
490
559
|
const { test_run_key, test_case_keys } = args;
|
|
491
560
|
try {
|
package/build/tool-schemas.js
CHANGED
|
@@ -273,6 +273,15 @@ export const toolSchemas = [
|
|
|
273
273
|
type: 'string',
|
|
274
274
|
description: 'Test environment (optional)',
|
|
275
275
|
},
|
|
276
|
+
issue_key: {
|
|
277
|
+
type: 'string',
|
|
278
|
+
description: 'Single issue key to link to the test run (optional) - will be mapped to issueKey in API',
|
|
279
|
+
},
|
|
280
|
+
issue_links: {
|
|
281
|
+
type: 'array',
|
|
282
|
+
description: 'Array of issue links (optional) - will be mapped to issueLinks in API',
|
|
283
|
+
items: { type: 'string' },
|
|
284
|
+
},
|
|
276
285
|
custom_fields: {
|
|
277
286
|
type: 'object',
|
|
278
287
|
description: 'Custom fields object (optional)',
|
|
@@ -338,6 +347,32 @@ export const toolSchemas = [
|
|
|
338
347
|
required: ['project_key', 'folder_path'],
|
|
339
348
|
},
|
|
340
349
|
},
|
|
350
|
+
{
|
|
351
|
+
name: 'search_test_runs',
|
|
352
|
+
description: 'Search for test runs using a query. Supports filtering by projectKey and/or folder path.',
|
|
353
|
+
inputSchema: {
|
|
354
|
+
type: 'object',
|
|
355
|
+
properties: {
|
|
356
|
+
project_key: {
|
|
357
|
+
type: 'string',
|
|
358
|
+
description: 'Project key to filter by (e.g., "PROJ"). Can be a single key or omitted if using folder only.',
|
|
359
|
+
},
|
|
360
|
+
folder: {
|
|
361
|
+
type: 'string',
|
|
362
|
+
description: 'Folder path to filter test runs by (e.g., "/MyFolder/SubFolder")',
|
|
363
|
+
},
|
|
364
|
+
max_results: {
|
|
365
|
+
type: 'number',
|
|
366
|
+
description: 'Maximum number of results to return (optional, default 200)',
|
|
367
|
+
default: 200,
|
|
368
|
+
},
|
|
369
|
+
fields: {
|
|
370
|
+
type: 'string',
|
|
371
|
+
description: 'Comma-separated list of fields to include in the response (optional, e.g., "key,name,status,folder"). If not set, all fields are returned.',
|
|
372
|
+
},
|
|
373
|
+
},
|
|
374
|
+
},
|
|
375
|
+
},
|
|
341
376
|
{
|
|
342
377
|
name: 'add_test_cases_to_run',
|
|
343
378
|
description: 'Add test cases to an existing test run',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zephyr-scale-mcp-server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Model Context Protocol (MCP) server for Zephyr Scale test case management with comprehensive STEP_BY_STEP, PLAIN_TEXT, and BDD support",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./build/index.js",
|
package/src/index.ts
CHANGED
|
@@ -25,7 +25,7 @@ class ZephyrServer {
|
|
|
25
25
|
this.server = new Server(
|
|
26
26
|
{
|
|
27
27
|
name: 'zephyr-server',
|
|
28
|
-
version: '0.
|
|
28
|
+
version: '0.3.0',
|
|
29
29
|
},
|
|
30
30
|
{
|
|
31
31
|
capabilities: {
|
|
@@ -83,6 +83,8 @@ class ZephyrServer {
|
|
|
83
83
|
return await this.toolHandlers.getTestExecution(args);
|
|
84
84
|
case 'search_test_cases_by_folder':
|
|
85
85
|
return await this.toolHandlers.searchTestCasesByFolder(args as any);
|
|
86
|
+
case 'search_test_runs':
|
|
87
|
+
return await this.toolHandlers.searchTestRuns(args as any);
|
|
86
88
|
case 'add_test_cases_to_run':
|
|
87
89
|
return await this.toolHandlers.addTestCasesToRun(args as any);
|
|
88
90
|
default:
|
package/src/tool-handlers.ts
CHANGED
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
TestRunArgs,
|
|
8
8
|
SearchTestCasesArgs,
|
|
9
9
|
AddTestCasesToRunArgs,
|
|
10
|
+
SearchTestRunsArgs,
|
|
10
11
|
JiraConfig
|
|
11
12
|
} from './types.js';
|
|
12
13
|
import { convertToGherkin, customPriorityMapping, priorityMapping } from './utils.js';
|
|
@@ -312,6 +313,8 @@ export class ZephyrToolHandlers {
|
|
|
312
313
|
description,
|
|
313
314
|
owner,
|
|
314
315
|
environment,
|
|
316
|
+
issue_key,
|
|
317
|
+
issue_links,
|
|
315
318
|
custom_fields
|
|
316
319
|
} = args;
|
|
317
320
|
|
|
@@ -333,6 +336,8 @@ export class ZephyrToolHandlers {
|
|
|
333
336
|
if (description) payload.description = description;
|
|
334
337
|
if (owner) payload.owner = owner;
|
|
335
338
|
if (environment) payload.environment = environment;
|
|
339
|
+
if (issue_key) payload.issueKey = issue_key;
|
|
340
|
+
if (issue_links && issue_links.length > 0) payload.issueLinks = issue_links;
|
|
336
341
|
if (custom_fields) payload.customFields = custom_fields;
|
|
337
342
|
if (test_plan_key) payload.testPlanKey = test_plan_key;
|
|
338
343
|
|
|
@@ -538,6 +543,71 @@ export class ZephyrToolHandlers {
|
|
|
538
543
|
}
|
|
539
544
|
}
|
|
540
545
|
|
|
546
|
+
async searchTestRuns(args: SearchTestRunsArgs) {
|
|
547
|
+
const { project_key, folder, max_results = 200, fields } = args;
|
|
548
|
+
|
|
549
|
+
// Build query string per Zephyr Scale API docs
|
|
550
|
+
const queryParts: string[] = [];
|
|
551
|
+
if (project_key) queryParts.push(`projectKey = "${project_key}"`);
|
|
552
|
+
if (folder) queryParts.push(`folder = "${folder}"`);
|
|
553
|
+
|
|
554
|
+
if (queryParts.length === 0) {
|
|
555
|
+
throw new McpError(ErrorCode.InvalidParams, 'At least one of project_key or folder must be provided.');
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
const query = queryParts.join(' AND ');
|
|
559
|
+
const params: Record<string, any> = { query, maxResults: max_results };
|
|
560
|
+
if (fields) params.fields = fields;
|
|
561
|
+
|
|
562
|
+
const searchEndpoint = this.jiraConfig.type === 'cloud'
|
|
563
|
+
? '/testruns/search'
|
|
564
|
+
: '/rest/atm/1.0/testrun/search';
|
|
565
|
+
|
|
566
|
+
try {
|
|
567
|
+
const response = await this.axiosInstance.get(searchEndpoint, { params });
|
|
568
|
+
|
|
569
|
+
let testRuns: any[] = [];
|
|
570
|
+
if (Array.isArray(response.data)) {
|
|
571
|
+
testRuns = response.data;
|
|
572
|
+
} else if (response.data.values && Array.isArray(response.data.values)) {
|
|
573
|
+
testRuns = response.data.values;
|
|
574
|
+
} else if (response.data.results && Array.isArray(response.data.results)) {
|
|
575
|
+
testRuns = response.data.results;
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
return {
|
|
579
|
+
content: [
|
|
580
|
+
{
|
|
581
|
+
type: 'text',
|
|
582
|
+
text: `✅ Found ${testRuns.length} test run(s) matching query "${query}":\n${JSON.stringify({
|
|
583
|
+
query,
|
|
584
|
+
totalCount: testRuns.length,
|
|
585
|
+
testRuns: testRuns.map((tr: any) => ({
|
|
586
|
+
key: tr.key,
|
|
587
|
+
name: tr.name,
|
|
588
|
+
status: tr.status,
|
|
589
|
+
folder: tr.folder,
|
|
590
|
+
testCaseCount: tr.testCaseCount,
|
|
591
|
+
issueKey: tr.issueKey,
|
|
592
|
+
}))
|
|
593
|
+
}, null, 2)}`,
|
|
594
|
+
},
|
|
595
|
+
],
|
|
596
|
+
};
|
|
597
|
+
} catch (error) {
|
|
598
|
+
let errorMessage = 'Unknown error';
|
|
599
|
+
if (error instanceof Error && 'response' in error) {
|
|
600
|
+
const axiosError = error as any;
|
|
601
|
+
errorMessage = `Status: ${axiosError.response?.status}, Data: ${JSON.stringify(axiosError.response?.data)}`;
|
|
602
|
+
} else if (error instanceof Error) {
|
|
603
|
+
errorMessage = error.message;
|
|
604
|
+
} else {
|
|
605
|
+
errorMessage = String(error);
|
|
606
|
+
}
|
|
607
|
+
throw new McpError(ErrorCode.InternalError, `Failed to search test runs: ${errorMessage}`);
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
|
|
541
611
|
async addTestCasesToRun(args: AddTestCasesToRunArgs) {
|
|
542
612
|
const { test_run_key, test_case_keys } = args;
|
|
543
613
|
|
package/src/tool-schemas.ts
CHANGED
|
@@ -273,6 +273,15 @@ export const toolSchemas = [
|
|
|
273
273
|
type: 'string',
|
|
274
274
|
description: 'Test environment (optional)',
|
|
275
275
|
},
|
|
276
|
+
issue_key: {
|
|
277
|
+
type: 'string',
|
|
278
|
+
description: 'Single issue key to link to the test run (optional) - will be mapped to issueKey in API',
|
|
279
|
+
},
|
|
280
|
+
issue_links: {
|
|
281
|
+
type: 'array',
|
|
282
|
+
description: 'Array of issue links (optional) - will be mapped to issueLinks in API',
|
|
283
|
+
items: { type: 'string' },
|
|
284
|
+
},
|
|
276
285
|
custom_fields: {
|
|
277
286
|
type: 'object',
|
|
278
287
|
description: 'Custom fields object (optional)',
|
|
@@ -338,6 +347,32 @@ export const toolSchemas = [
|
|
|
338
347
|
required: ['project_key', 'folder_path'],
|
|
339
348
|
},
|
|
340
349
|
},
|
|
350
|
+
{
|
|
351
|
+
name: 'search_test_runs',
|
|
352
|
+
description: 'Search for test runs using a query. Supports filtering by projectKey and/or folder path.',
|
|
353
|
+
inputSchema: {
|
|
354
|
+
type: 'object',
|
|
355
|
+
properties: {
|
|
356
|
+
project_key: {
|
|
357
|
+
type: 'string',
|
|
358
|
+
description: 'Project key to filter by (e.g., "PROJ"). Can be a single key or omitted if using folder only.',
|
|
359
|
+
},
|
|
360
|
+
folder: {
|
|
361
|
+
type: 'string',
|
|
362
|
+
description: 'Folder path to filter test runs by (e.g., "/MyFolder/SubFolder")',
|
|
363
|
+
},
|
|
364
|
+
max_results: {
|
|
365
|
+
type: 'number',
|
|
366
|
+
description: 'Maximum number of results to return (optional, default 200)',
|
|
367
|
+
default: 200,
|
|
368
|
+
},
|
|
369
|
+
fields: {
|
|
370
|
+
type: 'string',
|
|
371
|
+
description: 'Comma-separated list of fields to include in the response (optional, e.g., "key,name,status,folder"). If not set, all fields are returned.',
|
|
372
|
+
},
|
|
373
|
+
},
|
|
374
|
+
},
|
|
375
|
+
},
|
|
341
376
|
{
|
|
342
377
|
name: 'add_test_cases_to_run',
|
|
343
378
|
description: 'Add test cases to an existing test run',
|
package/src/types.ts
CHANGED
|
@@ -63,6 +63,8 @@ export interface TestRunArgs {
|
|
|
63
63
|
description?: string;
|
|
64
64
|
owner?: string;
|
|
65
65
|
environment?: string;
|
|
66
|
+
issue_key?: string;
|
|
67
|
+
issue_links?: string[];
|
|
66
68
|
custom_fields?: Record<string, any>;
|
|
67
69
|
}
|
|
68
70
|
|
|
@@ -77,6 +79,13 @@ export interface AddTestCasesToRunArgs {
|
|
|
77
79
|
test_case_keys: string[];
|
|
78
80
|
}
|
|
79
81
|
|
|
82
|
+
export interface SearchTestRunsArgs {
|
|
83
|
+
project_key?: string;
|
|
84
|
+
folder?: string;
|
|
85
|
+
max_results?: number;
|
|
86
|
+
fields?: string;
|
|
87
|
+
}
|
|
88
|
+
|
|
80
89
|
export type JiraType = 'cloud' | 'datacenter';
|
|
81
90
|
|
|
82
91
|
export interface ApiEndpoints {
|