zephyr-scale-mcp-server 0.4.7 → 0.5.1
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 +2 -0
- package/build/tool-handlers.js +81 -4
- package/build/tool-schemas.js +29 -2
- package/package.json +1 -1
- package/src/index.ts +2 -0
- package/src/tool-handlers.ts +91 -4
- package/src/tool-schemas.ts +29 -2
- package/src/types.ts +7 -0
package/build/index.js
CHANGED
|
@@ -69,6 +69,8 @@ class ZephyrServer {
|
|
|
69
69
|
return await this.toolHandlers.searchTestRuns(args);
|
|
70
70
|
case 'add_test_cases_to_run':
|
|
71
71
|
return await this.toolHandlers.addTestCasesToRun(args);
|
|
72
|
+
case 'list_executions_by_cycle':
|
|
73
|
+
return await this.toolHandlers.listExecutionsByCycle(args);
|
|
72
74
|
default:
|
|
73
75
|
throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${request.params.name}`);
|
|
74
76
|
}
|
package/build/tool-handlers.js
CHANGED
|
@@ -808,9 +808,9 @@ export class ZephyrToolHandlers {
|
|
|
808
808
|
}
|
|
809
809
|
}
|
|
810
810
|
async searchTestRuns(args) {
|
|
811
|
-
const { project_key, folder, max_results = 200, fields } = args;
|
|
812
|
-
if (!project_key && !folder) {
|
|
813
|
-
throw new McpError(ErrorCode.InvalidParams, 'At least one of project_key or
|
|
811
|
+
const { project_key, folder, folder_id, max_results = 200, fields } = args;
|
|
812
|
+
if (!project_key && !folder && !folder_id) {
|
|
813
|
+
throw new McpError(ErrorCode.InvalidParams, 'At least one of project_key, folder, or folder_id must be provided.');
|
|
814
814
|
}
|
|
815
815
|
if (this.jiraConfig.type === 'cloud') {
|
|
816
816
|
try {
|
|
@@ -818,7 +818,11 @@ export class ZephyrToolHandlers {
|
|
|
818
818
|
const params = { maxResults: max_results };
|
|
819
819
|
if (project_key)
|
|
820
820
|
params.projectKey = project_key;
|
|
821
|
-
|
|
821
|
+
// folder_id takes precedence over folder path
|
|
822
|
+
if (folder_id) {
|
|
823
|
+
params.folderId = folder_id;
|
|
824
|
+
}
|
|
825
|
+
else if (folder && project_key) {
|
|
822
826
|
const folderId = await resolveFolderIdByPath(this.axiosInstance, project_key, folder, 'TEST_CYCLE');
|
|
823
827
|
if (folderId !== null)
|
|
824
828
|
params.folderId = folderId;
|
|
@@ -907,6 +911,79 @@ export class ZephyrToolHandlers {
|
|
|
907
911
|
throw new McpError(ErrorCode.InternalError, `Failed to add test cases: ${this.formatError(error)}`);
|
|
908
912
|
}
|
|
909
913
|
}
|
|
914
|
+
async listExecutionsByCycle(args) {
|
|
915
|
+
const { test_cycle_key, project_key, max_results = 100 } = args;
|
|
916
|
+
if (this.jiraConfig.type === 'cloud') {
|
|
917
|
+
try {
|
|
918
|
+
// Cloud v2: GET /testexecutions?projectKey=X&testCycle=Y
|
|
919
|
+
const params = {
|
|
920
|
+
projectKey: project_key,
|
|
921
|
+
testCycle: test_cycle_key,
|
|
922
|
+
maxResults: max_results,
|
|
923
|
+
};
|
|
924
|
+
const response = await this.axiosInstance.get('/testexecutions', { params });
|
|
925
|
+
const executions = Array.isArray(response.data)
|
|
926
|
+
? response.data
|
|
927
|
+
: response.data?.values ?? [];
|
|
928
|
+
const summary = executions.map((ex) => ({
|
|
929
|
+
key: ex.key,
|
|
930
|
+
testCaseKey: ex.testCase?.self?.match(/testcases\/(.+?)\/versions/)?.[1] || ex.testCase?.id,
|
|
931
|
+
status: ex.testExecutionStatus?.id,
|
|
932
|
+
statusName: ex.testExecutionStatus?.name,
|
|
933
|
+
executedById: ex.executedById,
|
|
934
|
+
assignedToId: ex.assignedToId,
|
|
935
|
+
actualEndDate: ex.actualEndDate,
|
|
936
|
+
automated: ex.automated,
|
|
937
|
+
comment: ex.comment,
|
|
938
|
+
}));
|
|
939
|
+
// Count statuses
|
|
940
|
+
const statusCounts = {};
|
|
941
|
+
for (const ex of executions) {
|
|
942
|
+
const statusId = ex.testExecutionStatus?.id?.toString() || 'unknown';
|
|
943
|
+
statusCounts[statusId] = (statusCounts[statusId] || 0) + 1;
|
|
944
|
+
}
|
|
945
|
+
return {
|
|
946
|
+
content: [{
|
|
947
|
+
type: 'text',
|
|
948
|
+
text: `✅ Found ${executions.length} execution(s) for cycle ${test_cycle_key}:\n${JSON.stringify({
|
|
949
|
+
cycleKey: test_cycle_key,
|
|
950
|
+
totalExecutions: executions.length,
|
|
951
|
+
statusCounts,
|
|
952
|
+
executions: summary,
|
|
953
|
+
}, null, 2)}`,
|
|
954
|
+
}],
|
|
955
|
+
};
|
|
956
|
+
}
|
|
957
|
+
catch (error) {
|
|
958
|
+
throw new McpError(ErrorCode.InternalError, `Failed to list executions: ${this.formatError(error)}`);
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
// Data Center: GET /rest/atm/1.0/testrun/{key}/testresults
|
|
962
|
+
try {
|
|
963
|
+
const response = await this.axiosInstance.get(`${this.jiraConfig.apiEndpoints.testrun}/${test_cycle_key}/testresults`);
|
|
964
|
+
const results = Array.isArray(response.data) ? response.data : [];
|
|
965
|
+
return {
|
|
966
|
+
content: [{
|
|
967
|
+
type: 'text',
|
|
968
|
+
text: `✅ Found ${results.length} execution(s) for cycle ${test_cycle_key}:\n${JSON.stringify({
|
|
969
|
+
cycleKey: test_cycle_key,
|
|
970
|
+
totalExecutions: results.length,
|
|
971
|
+
executions: results.map((r) => ({
|
|
972
|
+
id: r.id,
|
|
973
|
+
testCaseKey: r.testCaseKey,
|
|
974
|
+
status: r.status,
|
|
975
|
+
executedBy: r.executedBy,
|
|
976
|
+
executionDate: r.executionDate,
|
|
977
|
+
automated: r.automated,
|
|
978
|
+
})),
|
|
979
|
+
}, null, 2)}`,
|
|
980
|
+
}],
|
|
981
|
+
};
|
|
982
|
+
}
|
|
983
|
+
catch (error) {
|
|
984
|
+
throw new McpError(ErrorCode.InternalError, `Failed to list executions: ${this.formatError(error)}`);
|
|
985
|
+
}
|
|
986
|
+
}
|
|
910
987
|
async resolveJiraIssueId(issueKey) {
|
|
911
988
|
// The Zephyr API key is NOT valid for the Jira REST API — Jira Cloud requires
|
|
912
989
|
// Basic Auth: base64(email:api_token) via JIRA_USERNAME + JIRA_API_TOKEN env vars.
|
package/build/tool-schemas.js
CHANGED
|
@@ -351,7 +351,7 @@ export const toolSchemas = [
|
|
|
351
351
|
},
|
|
352
352
|
{
|
|
353
353
|
name: 'search_test_runs',
|
|
354
|
-
description: 'Search for test runs using a query. Supports filtering by projectKey and/or folder path.',
|
|
354
|
+
description: 'Search for test runs using a query. Supports filtering by projectKey and/or folder path. On Cloud, only returns cycles in the exact folder (not sub-folders). Use folder_id for direct numeric ID lookup (skips path resolution).',
|
|
355
355
|
inputSchema: {
|
|
356
356
|
type: 'object',
|
|
357
357
|
properties: {
|
|
@@ -361,7 +361,11 @@ export const toolSchemas = [
|
|
|
361
361
|
},
|
|
362
362
|
folder: {
|
|
363
363
|
type: 'string',
|
|
364
|
-
description: 'Folder path to filter test runs by (e.g., "/MyFolder/SubFolder")',
|
|
364
|
+
description: 'Folder path to filter test runs by (e.g., "/MyFolder/SubFolder"). Resolved to a numeric folderId on Cloud.',
|
|
365
|
+
},
|
|
366
|
+
folder_id: {
|
|
367
|
+
type: 'number',
|
|
368
|
+
description: 'Numeric folder ID to filter test runs by (optional). If provided, takes precedence over folder path and skips path resolution. Use this when you already know the folder ID.',
|
|
365
369
|
},
|
|
366
370
|
max_results: {
|
|
367
371
|
type: 'number',
|
|
@@ -375,6 +379,29 @@ export const toolSchemas = [
|
|
|
375
379
|
},
|
|
376
380
|
},
|
|
377
381
|
},
|
|
382
|
+
{
|
|
383
|
+
name: 'list_executions_by_cycle',
|
|
384
|
+
description: 'List all test executions for a specific test cycle. Returns execution status, executedBy, actualEndDate for each test case in the cycle. Cloud only.',
|
|
385
|
+
inputSchema: {
|
|
386
|
+
type: 'object',
|
|
387
|
+
properties: {
|
|
388
|
+
test_cycle_key: {
|
|
389
|
+
type: 'string',
|
|
390
|
+
description: 'Test cycle key (e.g., DDCN-R371)',
|
|
391
|
+
},
|
|
392
|
+
project_key: {
|
|
393
|
+
type: 'string',
|
|
394
|
+
description: 'Project key (required for Cloud)',
|
|
395
|
+
},
|
|
396
|
+
max_results: {
|
|
397
|
+
type: 'number',
|
|
398
|
+
description: 'Maximum number of results to return (optional, default 100)',
|
|
399
|
+
default: 100,
|
|
400
|
+
},
|
|
401
|
+
},
|
|
402
|
+
required: ['test_cycle_key', 'project_key'],
|
|
403
|
+
},
|
|
404
|
+
},
|
|
378
405
|
{
|
|
379
406
|
name: 'delete_test_run',
|
|
380
407
|
description: 'Delete a specific test run (Data Center only — not supported on Cloud v2)',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zephyr-scale-mcp-server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.1",
|
|
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
|
@@ -89,6 +89,8 @@ class ZephyrServer {
|
|
|
89
89
|
return await this.toolHandlers.searchTestRuns(args as any);
|
|
90
90
|
case 'add_test_cases_to_run':
|
|
91
91
|
return await this.toolHandlers.addTestCasesToRun(args as any);
|
|
92
|
+
case 'list_executions_by_cycle':
|
|
93
|
+
return await this.toolHandlers.listExecutionsByCycle(args as any);
|
|
92
94
|
default:
|
|
93
95
|
throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${request.params.name}`);
|
|
94
96
|
}
|
package/src/tool-handlers.ts
CHANGED
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
AddTestCasesToRunArgs,
|
|
11
11
|
SearchTestRunsArgs,
|
|
12
12
|
GetTestExecutionArgs,
|
|
13
|
+
ListExecutionsByCycleArgs,
|
|
13
14
|
JiraConfig
|
|
14
15
|
} from './types.js';
|
|
15
16
|
import { convertToGherkin, resolveFolderIdByPath } from './utils.js';
|
|
@@ -879,10 +880,10 @@ export class ZephyrToolHandlers {
|
|
|
879
880
|
}
|
|
880
881
|
|
|
881
882
|
async searchTestRuns(args: SearchTestRunsArgs) {
|
|
882
|
-
const { project_key, folder, max_results = 200, fields } = args;
|
|
883
|
+
const { project_key, folder, folder_id, max_results = 200, fields } = args;
|
|
883
884
|
|
|
884
|
-
if (!project_key && !folder) {
|
|
885
|
-
throw new McpError(ErrorCode.InvalidParams, 'At least one of project_key or
|
|
885
|
+
if (!project_key && !folder && !folder_id) {
|
|
886
|
+
throw new McpError(ErrorCode.InvalidParams, 'At least one of project_key, folder, or folder_id must be provided.');
|
|
886
887
|
}
|
|
887
888
|
|
|
888
889
|
if (this.jiraConfig.type === 'cloud') {
|
|
@@ -891,7 +892,10 @@ export class ZephyrToolHandlers {
|
|
|
891
892
|
const params: Record<string, any> = { maxResults: max_results };
|
|
892
893
|
if (project_key) params.projectKey = project_key;
|
|
893
894
|
|
|
894
|
-
|
|
895
|
+
// folder_id takes precedence over folder path
|
|
896
|
+
if (folder_id) {
|
|
897
|
+
params.folderId = folder_id;
|
|
898
|
+
} else if (folder && project_key) {
|
|
895
899
|
const folderId = await resolveFolderIdByPath(
|
|
896
900
|
this.axiosInstance, project_key, folder, 'TEST_CYCLE'
|
|
897
901
|
);
|
|
@@ -989,6 +993,89 @@ export class ZephyrToolHandlers {
|
|
|
989
993
|
}
|
|
990
994
|
}
|
|
991
995
|
|
|
996
|
+
async listExecutionsByCycle(args: ListExecutionsByCycleArgs) {
|
|
997
|
+
const { test_cycle_key, project_key, max_results = 100 } = args;
|
|
998
|
+
|
|
999
|
+
if (this.jiraConfig.type === 'cloud') {
|
|
1000
|
+
try {
|
|
1001
|
+
// Cloud v2: GET /testexecutions?projectKey=X&testCycle=Y
|
|
1002
|
+
const params: Record<string, any> = {
|
|
1003
|
+
projectKey: project_key,
|
|
1004
|
+
testCycle: test_cycle_key,
|
|
1005
|
+
maxResults: max_results,
|
|
1006
|
+
};
|
|
1007
|
+
|
|
1008
|
+
const response = await this.axiosInstance.get('/testexecutions', { params });
|
|
1009
|
+
|
|
1010
|
+
const executions = Array.isArray(response.data)
|
|
1011
|
+
? response.data
|
|
1012
|
+
: response.data?.values ?? [];
|
|
1013
|
+
|
|
1014
|
+
const summary = executions.map((ex: any) => ({
|
|
1015
|
+
key: ex.key,
|
|
1016
|
+
testCaseKey: ex.testCase?.self?.match(/testcases\/(.+?)\/versions/)?.[1] || ex.testCase?.id,
|
|
1017
|
+
status: ex.testExecutionStatus?.id,
|
|
1018
|
+
statusName: ex.testExecutionStatus?.name,
|
|
1019
|
+
executedById: ex.executedById,
|
|
1020
|
+
assignedToId: ex.assignedToId,
|
|
1021
|
+
actualEndDate: ex.actualEndDate,
|
|
1022
|
+
automated: ex.automated,
|
|
1023
|
+
comment: ex.comment,
|
|
1024
|
+
}));
|
|
1025
|
+
|
|
1026
|
+
// Count statuses
|
|
1027
|
+
const statusCounts: Record<string, number> = {};
|
|
1028
|
+
for (const ex of executions) {
|
|
1029
|
+
const statusId = ex.testExecutionStatus?.id?.toString() || 'unknown';
|
|
1030
|
+
statusCounts[statusId] = (statusCounts[statusId] || 0) + 1;
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
return {
|
|
1034
|
+
content: [{
|
|
1035
|
+
type: 'text',
|
|
1036
|
+
text: `✅ Found ${executions.length} execution(s) for cycle ${test_cycle_key}:\n${JSON.stringify({
|
|
1037
|
+
cycleKey: test_cycle_key,
|
|
1038
|
+
totalExecutions: executions.length,
|
|
1039
|
+
statusCounts,
|
|
1040
|
+
executions: summary,
|
|
1041
|
+
}, null, 2)}`,
|
|
1042
|
+
}],
|
|
1043
|
+
};
|
|
1044
|
+
} catch (error) {
|
|
1045
|
+
throw new McpError(ErrorCode.InternalError, `Failed to list executions: ${this.formatError(error)}`);
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
// Data Center: GET /rest/atm/1.0/testrun/{key}/testresults
|
|
1050
|
+
try {
|
|
1051
|
+
const response = await this.axiosInstance.get(
|
|
1052
|
+
`${this.jiraConfig.apiEndpoints.testrun}/${test_cycle_key}/testresults`
|
|
1053
|
+
);
|
|
1054
|
+
|
|
1055
|
+
const results = Array.isArray(response.data) ? response.data : [];
|
|
1056
|
+
|
|
1057
|
+
return {
|
|
1058
|
+
content: [{
|
|
1059
|
+
type: 'text',
|
|
1060
|
+
text: `✅ Found ${results.length} execution(s) for cycle ${test_cycle_key}:\n${JSON.stringify({
|
|
1061
|
+
cycleKey: test_cycle_key,
|
|
1062
|
+
totalExecutions: results.length,
|
|
1063
|
+
executions: results.map((r: any) => ({
|
|
1064
|
+
id: r.id,
|
|
1065
|
+
testCaseKey: r.testCaseKey,
|
|
1066
|
+
status: r.status,
|
|
1067
|
+
executedBy: r.executedBy,
|
|
1068
|
+
executionDate: r.executionDate,
|
|
1069
|
+
automated: r.automated,
|
|
1070
|
+
})),
|
|
1071
|
+
}, null, 2)}`,
|
|
1072
|
+
}],
|
|
1073
|
+
};
|
|
1074
|
+
} catch (error) {
|
|
1075
|
+
throw new McpError(ErrorCode.InternalError, `Failed to list executions: ${this.formatError(error)}`);
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
|
|
992
1079
|
private async resolveJiraIssueId(issueKey: string): Promise<number> {
|
|
993
1080
|
// The Zephyr API key is NOT valid for the Jira REST API — Jira Cloud requires
|
|
994
1081
|
// Basic Auth: base64(email:api_token) via JIRA_USERNAME + JIRA_API_TOKEN env vars.
|
package/src/tool-schemas.ts
CHANGED
|
@@ -351,7 +351,7 @@ export const toolSchemas = [
|
|
|
351
351
|
},
|
|
352
352
|
{
|
|
353
353
|
name: 'search_test_runs',
|
|
354
|
-
description: 'Search for test runs using a query. Supports filtering by projectKey and/or folder path.',
|
|
354
|
+
description: 'Search for test runs using a query. Supports filtering by projectKey and/or folder path. On Cloud, only returns cycles in the exact folder (not sub-folders). Use folder_id for direct numeric ID lookup (skips path resolution).',
|
|
355
355
|
inputSchema: {
|
|
356
356
|
type: 'object',
|
|
357
357
|
properties: {
|
|
@@ -361,7 +361,11 @@ export const toolSchemas = [
|
|
|
361
361
|
},
|
|
362
362
|
folder: {
|
|
363
363
|
type: 'string',
|
|
364
|
-
description: 'Folder path to filter test runs by (e.g., "/MyFolder/SubFolder")',
|
|
364
|
+
description: 'Folder path to filter test runs by (e.g., "/MyFolder/SubFolder"). Resolved to a numeric folderId on Cloud.',
|
|
365
|
+
},
|
|
366
|
+
folder_id: {
|
|
367
|
+
type: 'number',
|
|
368
|
+
description: 'Numeric folder ID to filter test runs by (optional). If provided, takes precedence over folder path and skips path resolution. Use this when you already know the folder ID.',
|
|
365
369
|
},
|
|
366
370
|
max_results: {
|
|
367
371
|
type: 'number',
|
|
@@ -375,6 +379,29 @@ export const toolSchemas = [
|
|
|
375
379
|
},
|
|
376
380
|
},
|
|
377
381
|
},
|
|
382
|
+
{
|
|
383
|
+
name: 'list_executions_by_cycle',
|
|
384
|
+
description: 'List all test executions for a specific test cycle. Returns execution status, executedBy, actualEndDate for each test case in the cycle. Cloud only.',
|
|
385
|
+
inputSchema: {
|
|
386
|
+
type: 'object',
|
|
387
|
+
properties: {
|
|
388
|
+
test_cycle_key: {
|
|
389
|
+
type: 'string',
|
|
390
|
+
description: 'Test cycle key (e.g., DDCN-R371)',
|
|
391
|
+
},
|
|
392
|
+
project_key: {
|
|
393
|
+
type: 'string',
|
|
394
|
+
description: 'Project key (required for Cloud)',
|
|
395
|
+
},
|
|
396
|
+
max_results: {
|
|
397
|
+
type: 'number',
|
|
398
|
+
description: 'Maximum number of results to return (optional, default 100)',
|
|
399
|
+
default: 100,
|
|
400
|
+
},
|
|
401
|
+
},
|
|
402
|
+
required: ['test_cycle_key', 'project_key'],
|
|
403
|
+
},
|
|
404
|
+
},
|
|
378
405
|
{
|
|
379
406
|
name: 'delete_test_run',
|
|
380
407
|
description: 'Delete a specific test run (Data Center only — not supported on Cloud v2)',
|
package/src/types.ts
CHANGED
|
@@ -91,10 +91,17 @@ export interface GetTestExecutionArgs {
|
|
|
91
91
|
export interface SearchTestRunsArgs {
|
|
92
92
|
project_key?: string;
|
|
93
93
|
folder?: string;
|
|
94
|
+
folder_id?: number;
|
|
94
95
|
max_results?: number;
|
|
95
96
|
fields?: string;
|
|
96
97
|
}
|
|
97
98
|
|
|
99
|
+
export interface ListExecutionsByCycleArgs {
|
|
100
|
+
test_cycle_key: string;
|
|
101
|
+
project_key: string;
|
|
102
|
+
max_results?: number;
|
|
103
|
+
}
|
|
104
|
+
|
|
98
105
|
export type JiraType = 'cloud' | 'datacenter';
|
|
99
106
|
|
|
100
107
|
export interface ApiEndpoints {
|