zephyr-scale-mcp-server 0.4.2 → 0.4.4
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/README.md +43 -21
- package/build/tool-handlers.js +107 -26
- package/build/tool-schemas.js +18 -16
- package/package.json +1 -1
- package/src/tool-handlers.ts +106 -26
- package/src/tool-schemas.ts +18 -16
- package/src/types.ts +7 -4
package/README.md
CHANGED
|
@@ -27,7 +27,29 @@ Configure your MCP client with the following structure.
|
|
|
27
27
|
"args": ["zephyr-scale-mcp-server@latest"],
|
|
28
28
|
"env": {
|
|
29
29
|
"ZEPHYR_BASE_URL": "https://your-company.atlassian.net",
|
|
30
|
-
"ZEPHYR_API_KEY": "your-zephyr-api-key"
|
|
30
|
+
"ZEPHYR_API_KEY": "your-zephyr-api-key",
|
|
31
|
+
"JIRA_USERNAME": "your-email@company.com",
|
|
32
|
+
"JIRA_API_TOKEN": "your-jira-api-token"
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
> **Note**: `JIRA_USERNAME` and `JIRA_API_TOKEN` are optional but required if you want to use the `issue_links` field when creating test cases. Without them, issue linking will fail with a 401 warning (the test case is still created). Generate a Jira API token at [id.atlassian.com/manage-profile/security/api-tokens](https://id.atlassian.com/manage-profile/security/api-tokens).
|
|
39
|
+
|
|
40
|
+
**Jira Cloud (EU region):**
|
|
41
|
+
```json
|
|
42
|
+
{
|
|
43
|
+
"mcpServers": {
|
|
44
|
+
"zephyr-server": {
|
|
45
|
+
"command": "npx",
|
|
46
|
+
"args": ["zephyr-scale-mcp-server@latest"],
|
|
47
|
+
"env": {
|
|
48
|
+
"ZEPHYR_BASE_URL": "https://your-company.atlassian.net",
|
|
49
|
+
"ZEPHYR_API_KEY": "your-zephyr-api-key",
|
|
50
|
+
"JIRA_USERNAME": "your-email@company.com",
|
|
51
|
+
"JIRA_API_TOKEN": "your-jira-api-token",
|
|
52
|
+
"ZEPHYR_API_BASE_URL": "https://eu.api.zephyrscale.smartbear.com/v2"
|
|
31
53
|
}
|
|
32
54
|
}
|
|
33
55
|
}
|
|
@@ -99,18 +121,19 @@ The server provides access to various resources through URI schemes:
|
|
|
99
121
|
|
|
100
122
|
## Usage Examples
|
|
101
123
|
|
|
102
|
-
### Create a BDD Test Case
|
|
124
|
+
### Create a BDD Test Case with Issue Links
|
|
103
125
|
```json
|
|
104
126
|
{
|
|
105
127
|
"project_key": "PROJ",
|
|
106
128
|
"name": "User Authentication",
|
|
107
129
|
"test_script": {
|
|
108
130
|
"type": "BDD",
|
|
109
|
-
"text": "
|
|
110
|
-
}
|
|
131
|
+
"text": "Given a user with valid credentials\nWhen the user attempts to log in\nThen the user should be authenticated successfully"
|
|
132
|
+
},
|
|
133
|
+
"issue_links": ["PROJ-123", "PROJ-456"]
|
|
111
134
|
}
|
|
112
135
|
```
|
|
113
|
-
**Note**:
|
|
136
|
+
**Note**: `issue_links` requires `JIRA_USERNAME` and `JIRA_API_TOKEN` to be set (Cloud only). Link failures are reported as warnings — the test case is still created.
|
|
114
137
|
|
|
115
138
|
### Use a Live Test Case as a Template
|
|
116
139
|
1. Fetch an existing test case: `zephyr://testcase/PROJ-T123`
|
|
@@ -139,26 +162,25 @@ The server provides access to various resources through URI schemes:
|
|
|
139
162
|
## Authentication
|
|
140
163
|
|
|
141
164
|
### Jira Cloud Configuration
|
|
142
|
-
- `ZEPHYR_BASE_URL`: `https://your-company.atlassian.net`
|
|
143
|
-
- `ZEPHYR_API_KEY`: Your Zephyr Scale API key (JWT). Generate it in Jira by clicking your profile picture (bottom left) → **Zephyr API keys**.
|
|
144
165
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
166
|
+
| Variable | Required | Description |
|
|
167
|
+
|---|---|---|
|
|
168
|
+
| `ZEPHYR_BASE_URL` | ✅ | Your Jira Cloud URL, e.g. `https://your-company.atlassian.net` |
|
|
169
|
+
| `ZEPHYR_API_KEY` | ✅ | Zephyr Scale API key (JWT). Generate in Jira: profile picture (bottom left) → **Zephyr API keys** |
|
|
170
|
+
| `JIRA_USERNAME` | ⚠️ Optional* | Your Jira account email address |
|
|
171
|
+
| `JIRA_API_TOKEN` | ⚠️ Optional* | Jira API token. Generate at [id.atlassian.com/manage-profile/security/api-tokens](https://id.atlassian.com/manage-profile/security/api-tokens) |
|
|
172
|
+
| `ZEPHYR_API_BASE_URL` | Optional | Override the Zephyr API base URL (e.g. for EU: `https://eu.api.zephyrscale.smartbear.com/v2`). Defaults to US endpoint. |
|
|
173
|
+
| `JIRA_TYPE` | Optional | Force `"cloud"` or `"datacenter"` — overrides auto-detection |
|
|
148
174
|
|
|
149
|
-
|
|
150
|
-
```json
|
|
151
|
-
"env": {
|
|
152
|
-
"ZEPHYR_BASE_URL": "https://your-company.atlassian.net",
|
|
153
|
-
"ZEPHYR_API_KEY": "your-zephyr-api-token",
|
|
154
|
-
"ZEPHYR_API_BASE_URL": "https://eu.api.zephyrscale.smartbear.com/v2"
|
|
155
|
-
}
|
|
156
|
-
```
|
|
157
|
-
If `ZEPHYR_API_BASE_URL` is not set, the US URL is used. Omit this variable for US instances.
|
|
175
|
+
> **\* `JIRA_USERNAME` + `JIRA_API_TOKEN`**: Required only for the `issue_links` feature on Cloud. The Zephyr API key cannot authenticate against the Jira REST API, so a separate Jira credential is needed to resolve issue keys to numeric IDs. Without these, `issue_links` will fail with a 401 warning — the test case is still created successfully.
|
|
158
176
|
|
|
159
177
|
### Jira Data Center Configuration
|
|
160
|
-
|
|
161
|
-
|
|
178
|
+
|
|
179
|
+
| Variable | Required | Description |
|
|
180
|
+
|---|---|---|
|
|
181
|
+
| `ZEPHYR_BASE_URL` | ✅ | Your Jira server URL, e.g. `https://your-jira-server.com` |
|
|
182
|
+
| `ZEPHYR_API_KEY` | ✅ | Zephyr Scale API token from your Jira profile settings |
|
|
183
|
+
| `JIRA_TYPE` | Optional | Set to `"datacenter"` to override auto-detection |
|
|
162
184
|
|
|
163
185
|
### Automatic Detection
|
|
164
186
|
The server automatically detects your Jira type based on `ZEPHYR_BASE_URL` — URLs containing `.atlassian.net` are treated as Cloud, everything else as Data Center. Override with `JIRA_TYPE="cloud"` or `JIRA_TYPE="datacenter"`.
|
package/build/tool-handlers.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import axios from 'axios';
|
|
1
2
|
import { McpError, ErrorCode } from '@modelcontextprotocol/sdk/types.js';
|
|
2
3
|
import { convertToGherkin, resolveFolderIdByPath } from './utils.js';
|
|
3
4
|
export class ZephyrToolHandlers {
|
|
@@ -34,7 +35,7 @@ export class ZephyrToolHandlers {
|
|
|
34
35
|
return this.createTestCaseDC(args);
|
|
35
36
|
}
|
|
36
37
|
async createTestCaseCloud(args) {
|
|
37
|
-
const { project_key, name, test_script, folder, priority, precondition, objective, estimated_time, labels, custom_fields, issue_links, } = args;
|
|
38
|
+
const { project_key, name, test_script, folder, priority, precondition, objective, estimated_time, labels, custom_fields, issue_links, owner_id, component_id, } = args;
|
|
38
39
|
const payload = { projectKey: project_key, name };
|
|
39
40
|
// Cloud v2 uses statusName/priorityName (strings), folderId (integer)
|
|
40
41
|
payload.statusName = 'Draft';
|
|
@@ -50,6 +51,11 @@ export class ZephyrToolHandlers {
|
|
|
50
51
|
payload.labels = labels;
|
|
51
52
|
if (custom_fields)
|
|
52
53
|
payload.customFields = custom_fields;
|
|
54
|
+
// Cloud v2 uses ownerId (Jira Account ID) and componentId (integer)
|
|
55
|
+
if (owner_id)
|
|
56
|
+
payload.ownerId = owner_id;
|
|
57
|
+
if (component_id)
|
|
58
|
+
payload.componentId = component_id;
|
|
53
59
|
// Resolve folder path → folderId
|
|
54
60
|
if (folder) {
|
|
55
61
|
const folderId = await resolveFolderIdByPath(this.axiosInstance, project_key, folder, 'TEST_CASE');
|
|
@@ -80,8 +86,12 @@ export class ZephyrToolHandlers {
|
|
|
80
86
|
}
|
|
81
87
|
}
|
|
82
88
|
}
|
|
89
|
+
const missingCreds = !process.env.JIRA_USERNAME || !process.env.JIRA_API_TOKEN;
|
|
90
|
+
const credHint = missingCreds
|
|
91
|
+
? '\n💡 Tip: Set JIRA_USERNAME and JIRA_API_TOKEN env vars to enable issue linking on Cloud.'
|
|
92
|
+
: '';
|
|
83
93
|
const warningText = linkWarnings.length > 0
|
|
84
|
-
? `\n⚠️ Some issue links failed:\n${linkWarnings.map(w => ` - ${w}`).join('\n')}`
|
|
94
|
+
? `\n⚠️ Some issue links failed:\n${linkWarnings.map(w => ` - ${w}`).join('\n')}${credHint}`
|
|
85
95
|
: '';
|
|
86
96
|
return {
|
|
87
97
|
content: [{
|
|
@@ -98,13 +108,19 @@ export class ZephyrToolHandlers {
|
|
|
98
108
|
if (!test_script)
|
|
99
109
|
return;
|
|
100
110
|
if (test_script.type === 'STEP_BY_STEP' && test_script.steps && test_script.steps.length > 0) {
|
|
101
|
-
const items = test_script.steps.map((step) =>
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
111
|
+
const items = test_script.steps.map((step) => {
|
|
112
|
+
// If step is a call-to-test (testCaseKey), use the testCase variant
|
|
113
|
+
if (step.testCaseKey) {
|
|
114
|
+
return { testCase: { testCaseKey: step.testCaseKey } };
|
|
115
|
+
}
|
|
116
|
+
return {
|
|
117
|
+
inline: {
|
|
118
|
+
description: step.description || '',
|
|
119
|
+
testData: step.testData || null,
|
|
120
|
+
expectedResult: step.expectedResult || null,
|
|
121
|
+
},
|
|
122
|
+
};
|
|
123
|
+
});
|
|
108
124
|
await this.axiosInstance.post(`${this.jiraConfig.apiEndpoints.testcase}/${testKey}/teststeps`, { mode: 'OVERWRITE', items });
|
|
109
125
|
}
|
|
110
126
|
else if (test_script.type === 'BDD' && test_script.text) {
|
|
@@ -202,13 +218,26 @@ export class ZephyrToolHandlers {
|
|
|
202
218
|
if (typeof name === 'string' && name.trim().length > 0) {
|
|
203
219
|
const getResponse = await this.axiosInstance.get(`${this.jiraConfig.apiEndpoints.testcase}/${test_case_key}`);
|
|
204
220
|
const tc = getResponse.data;
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
221
|
+
// UpdateTestCaseInput requires: id, key, name, priority, project, status
|
|
222
|
+
// All optional fields must also be re-sent or the API will clear them
|
|
223
|
+
const putPayload = {
|
|
224
|
+
id: tc.id,
|
|
225
|
+
key: test_case_key,
|
|
208
226
|
name,
|
|
209
227
|
status: tc.status,
|
|
210
228
|
priority: tc.priority,
|
|
211
|
-
|
|
229
|
+
project: tc.project,
|
|
230
|
+
};
|
|
231
|
+
// Preserve all optional fields to avoid the API clearing them
|
|
232
|
+
for (const field of ['objective', 'precondition', 'estimatedTime', 'component', 'owner', 'folder']) {
|
|
233
|
+
if (tc[field] !== undefined && tc[field] !== null)
|
|
234
|
+
putPayload[field] = tc[field];
|
|
235
|
+
}
|
|
236
|
+
if (Array.isArray(tc.labels) && tc.labels.length > 0)
|
|
237
|
+
putPayload.labels = tc.labels;
|
|
238
|
+
if (tc.customFields && Object.keys(tc.customFields).length > 0)
|
|
239
|
+
putPayload.customFields = tc.customFields;
|
|
240
|
+
await this.axiosInstance.put(`${this.jiraConfig.apiEndpoints.testcase}/${test_case_key}`, putPayload);
|
|
212
241
|
}
|
|
213
242
|
return {
|
|
214
243
|
content: [{
|
|
@@ -289,8 +318,7 @@ export class ZephyrToolHandlers {
|
|
|
289
318
|
async createFolderCloud(args) {
|
|
290
319
|
const { project_key, name: folderPath, folder_type = 'TEST_CASE' } = args;
|
|
291
320
|
// Cloud v2 uses folderType (not type) and parentId integer
|
|
292
|
-
|
|
293
|
-
const cloudFolderType = folder_type === 'TEST_RUN' ? 'TEST_CYCLE' : folder_type;
|
|
321
|
+
const cloudFolderType = folder_type;
|
|
294
322
|
const segments = folderPath.replace(/^\/+|\/+$/g, '').split('/').filter(Boolean);
|
|
295
323
|
if (segments.length === 0) {
|
|
296
324
|
throw new McpError(ErrorCode.InvalidParams, 'folder name/path cannot be empty');
|
|
@@ -442,9 +470,10 @@ export class ZephyrToolHandlers {
|
|
|
442
470
|
return this.createTestRunDC(args);
|
|
443
471
|
}
|
|
444
472
|
async createTestRunCloud(args) {
|
|
445
|
-
const { project_key, name, test_case_keys, folder, planned_start_date, planned_end_date, description, owner, environment, custom_fields, } = args;
|
|
473
|
+
const { project_key, name, test_case_keys, folder, planned_start_date, planned_end_date, description, owner, environment, custom_fields, issue_links, issue_key, jira_project_version, } = args;
|
|
446
474
|
// Cloud v2 TestCycleInput: projectKey, name, description, plannedStartDate,
|
|
447
|
-
// plannedEndDate, statusName, folderId, ownerId, customFields
|
|
475
|
+
// plannedEndDate, statusName, folderId, ownerId, jiraProjectVersion, customFields
|
|
476
|
+
// Note: environment is NOT a TestCycleInput field on Cloud — it belongs on TestExecutionInput
|
|
448
477
|
const payload = { projectKey: project_key, name };
|
|
449
478
|
if (description)
|
|
450
479
|
payload.description = description;
|
|
@@ -454,7 +483,12 @@ export class ZephyrToolHandlers {
|
|
|
454
483
|
payload.plannedEndDate = planned_end_date;
|
|
455
484
|
if (custom_fields)
|
|
456
485
|
payload.customFields = custom_fields;
|
|
457
|
-
//
|
|
486
|
+
// Cloud v2 TestCycleInput supports ownerId (Jira Account ID)
|
|
487
|
+
if (owner)
|
|
488
|
+
payload.ownerId = owner;
|
|
489
|
+
// Link to a Jira project version/release (integer ID)
|
|
490
|
+
if (jira_project_version)
|
|
491
|
+
payload.jiraProjectVersion = jira_project_version;
|
|
458
492
|
if (folder) {
|
|
459
493
|
const folderId = await resolveFolderIdByPath(this.axiosInstance, project_key, folder, 'TEST_CYCLE');
|
|
460
494
|
if (folderId !== null)
|
|
@@ -469,14 +503,43 @@ export class ZephyrToolHandlers {
|
|
|
469
503
|
// Step 2: add test cases via test executions (Cloud v2 has no /testcycles/{key}/testcases)
|
|
470
504
|
if (test_case_keys && test_case_keys.length > 0) {
|
|
471
505
|
for (const testCaseKey of test_case_keys) {
|
|
472
|
-
|
|
506
|
+
const execPayload = {
|
|
473
507
|
projectKey: project_key,
|
|
474
508
|
testCaseKey,
|
|
475
509
|
testCycleKey: cycleKey,
|
|
476
510
|
statusName: 'Not Executed',
|
|
477
|
-
}
|
|
511
|
+
};
|
|
512
|
+
// environment is set at execution level on Cloud, not cycle level
|
|
513
|
+
if (environment)
|
|
514
|
+
execPayload.environmentName = environment;
|
|
515
|
+
await this.axiosInstance.post('/testexecutions', execPayload);
|
|
478
516
|
}
|
|
479
517
|
}
|
|
518
|
+
// Step 3: link Jira issues via POST /testcycles/{key}/links/issues
|
|
519
|
+
// Merge issue_key (single) and issue_links (array) into one list
|
|
520
|
+
const allIssueLinks = [
|
|
521
|
+
...(issue_key ? [issue_key] : []),
|
|
522
|
+
...(issue_links ?? []),
|
|
523
|
+
];
|
|
524
|
+
const linkWarnings = [];
|
|
525
|
+
if (allIssueLinks.length > 0) {
|
|
526
|
+
for (const ik of allIssueLinks) {
|
|
527
|
+
try {
|
|
528
|
+
const issueId = await this.resolveJiraIssueId(ik);
|
|
529
|
+
await this.axiosInstance.post(`${this.jiraConfig.apiEndpoints.testrun}/${cycleKey}/links/issues`, { issueId });
|
|
530
|
+
}
|
|
531
|
+
catch (e) {
|
|
532
|
+
linkWarnings.push(`${ik}: ${this.formatError(e)}`);
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
const missingCreds = !process.env.JIRA_USERNAME || !process.env.JIRA_API_TOKEN;
|
|
537
|
+
const credHint = missingCreds && linkWarnings.length > 0
|
|
538
|
+
? '\n💡 Tip: Set JIRA_USERNAME and JIRA_API_TOKEN env vars to enable issue linking on Cloud.'
|
|
539
|
+
: '';
|
|
540
|
+
const warningText = linkWarnings.length > 0
|
|
541
|
+
? `\n⚠️ Some issue links failed:\n${linkWarnings.map(w => ` - ${w}`).join('\n')}${credHint}`
|
|
542
|
+
: '';
|
|
480
543
|
return {
|
|
481
544
|
content: [{
|
|
482
545
|
type: 'text',
|
|
@@ -484,7 +547,8 @@ export class ZephyrToolHandlers {
|
|
|
484
547
|
key: cycleKey,
|
|
485
548
|
name,
|
|
486
549
|
testCaseCount: test_case_keys?.length || 0,
|
|
487
|
-
|
|
550
|
+
linkedIssues: allIssueLinks.length - linkWarnings.length,
|
|
551
|
+
}, null, 2)}${warningText}`,
|
|
488
552
|
}],
|
|
489
553
|
};
|
|
490
554
|
}
|
|
@@ -694,7 +758,7 @@ export class ZephyrToolHandlers {
|
|
|
694
758
|
text: `✅ Found ${testRuns.length} test run(s):\n${JSON.stringify({
|
|
695
759
|
totalCount: testRuns.length,
|
|
696
760
|
testRuns: testRuns.map((tr) => ({
|
|
697
|
-
key: tr.key, name: tr.name, status: tr.status?.
|
|
761
|
+
key: tr.key, name: tr.name, status: tr.status?.id, folder: tr.folder?.name,
|
|
698
762
|
})),
|
|
699
763
|
}, null, 2)}`,
|
|
700
764
|
}],
|
|
@@ -769,11 +833,28 @@ export class ZephyrToolHandlers {
|
|
|
769
833
|
}
|
|
770
834
|
}
|
|
771
835
|
async resolveJiraIssueId(issueKey) {
|
|
772
|
-
// The Zephyr API key is
|
|
773
|
-
//
|
|
774
|
-
|
|
836
|
+
// The Zephyr API key is NOT valid for the Jira REST API — Jira Cloud requires
|
|
837
|
+
// Basic Auth: base64(email:api_token) via JIRA_USERNAME + JIRA_API_TOKEN env vars.
|
|
838
|
+
const username = process.env.JIRA_USERNAME;
|
|
839
|
+
const apiToken = process.env.JIRA_API_TOKEN;
|
|
775
840
|
const url = `${this.jiraConfig.jiraBaseUrl}/rest/api/3/issue/${issueKey}?fields=id`;
|
|
776
|
-
|
|
841
|
+
let response;
|
|
842
|
+
if (username && apiToken) {
|
|
843
|
+
// Jira Cloud Basic Auth
|
|
844
|
+
response = await axios.get(url, {
|
|
845
|
+
headers: { 'Accept': 'application/json' },
|
|
846
|
+
auth: { username, password: apiToken },
|
|
847
|
+
});
|
|
848
|
+
}
|
|
849
|
+
else {
|
|
850
|
+
// Fallback: try Bearer token (works for Data Center with PAT)
|
|
851
|
+
response = await axios.get(url, {
|
|
852
|
+
headers: {
|
|
853
|
+
'Accept': 'application/json',
|
|
854
|
+
'Authorization': `Bearer ${process.env.ZEPHYR_API_KEY}`,
|
|
855
|
+
},
|
|
856
|
+
});
|
|
857
|
+
}
|
|
777
858
|
const id = parseInt(response.data.id, 10);
|
|
778
859
|
if (!id || isNaN(id)) {
|
|
779
860
|
throw new Error(`Could not resolve numeric ID for Jira issue "${issueKey}"`);
|
package/build/tool-schemas.js
CHANGED
|
@@ -74,14 +74,12 @@ export const toolSchemas = [
|
|
|
74
74
|
},
|
|
75
75
|
status: {
|
|
76
76
|
type: 'string',
|
|
77
|
-
description: 'Test case status (optional)',
|
|
78
|
-
enum: ['Draft', 'Approved', 'Deprecated'],
|
|
77
|
+
description: 'Test case status (optional, default: "Draft"). Value must match a status name configured in your Zephyr project (e.g. "Draft", "Approved", "Deprecated"). Note: always overridden to "Draft" on creation.',
|
|
79
78
|
default: 'Draft',
|
|
80
79
|
},
|
|
81
80
|
priority: {
|
|
82
81
|
type: 'string',
|
|
83
|
-
description: 'Test case priority (optional)',
|
|
84
|
-
enum: ['High', 'Normal', 'Low'],
|
|
82
|
+
description: 'Test case priority (optional). Value must match a priority name configured in your Zephyr project (e.g. "High", "Normal", "Low", "Critical"). Use zephyr://testcase/EXISTING-KEY to check your project\'s valid values.',
|
|
85
83
|
},
|
|
86
84
|
precondition: {
|
|
87
85
|
type: 'string',
|
|
@@ -91,13 +89,13 @@ export const toolSchemas = [
|
|
|
91
89
|
type: 'string',
|
|
92
90
|
description: 'Test objective (optional)',
|
|
93
91
|
},
|
|
94
|
-
|
|
95
|
-
type: '
|
|
96
|
-
description: '
|
|
92
|
+
component_id: {
|
|
93
|
+
type: 'integer',
|
|
94
|
+
description: 'Jira component ID (optional, Cloud only — use the numeric component ID, not the name)',
|
|
97
95
|
},
|
|
98
|
-
|
|
96
|
+
owner_id: {
|
|
99
97
|
type: 'string',
|
|
100
|
-
description: 'Test case owner (optional)',
|
|
98
|
+
description: 'Test case owner Jira Account ID (optional, Cloud only — e.g. "5b10a2844c20165700ede21g")',
|
|
101
99
|
},
|
|
102
100
|
estimated_time: {
|
|
103
101
|
type: 'number',
|
|
@@ -191,7 +189,7 @@ export const toolSchemas = [
|
|
|
191
189
|
folder_type: {
|
|
192
190
|
type: 'string',
|
|
193
191
|
description: 'Type of folder',
|
|
194
|
-
enum: ['TEST_CASE', 'TEST_PLAN', '
|
|
192
|
+
enum: ['TEST_CASE', 'TEST_PLAN', 'TEST_CYCLE'],
|
|
195
193
|
default: 'TEST_CASE',
|
|
196
194
|
},
|
|
197
195
|
},
|
|
@@ -206,7 +204,7 @@ export const toolSchemas = [
|
|
|
206
204
|
properties: {
|
|
207
205
|
test_run_key: {
|
|
208
206
|
type: 'string',
|
|
209
|
-
description: 'Test run key (e.g., PROJ-
|
|
207
|
+
description: 'Test run key (e.g., PROJ-R123)',
|
|
210
208
|
},
|
|
211
209
|
},
|
|
212
210
|
required: ['test_run_key'],
|
|
@@ -271,17 +269,21 @@ export const toolSchemas = [
|
|
|
271
269
|
},
|
|
272
270
|
environment: {
|
|
273
271
|
type: 'string',
|
|
274
|
-
description: 'Test environment (optional)',
|
|
272
|
+
description: 'Test environment name (optional). On Cloud, applied to each test execution (environmentName). On Data Center, set at cycle level.',
|
|
275
273
|
},
|
|
276
274
|
issue_key: {
|
|
277
275
|
type: 'string',
|
|
278
|
-
description: 'Single issue key to link to the test
|
|
276
|
+
description: 'Single Jira issue key to link to the test cycle (e.g. "PROJ-123"). On Cloud, resolved to a numeric ID via Jira REST API — requires JIRA_USERNAME + JIRA_API_TOKEN env vars.',
|
|
279
277
|
},
|
|
280
278
|
issue_links: {
|
|
281
279
|
type: 'array',
|
|
282
|
-
description: 'Array of issue
|
|
280
|
+
description: 'Array of Jira issue keys to link to the test cycle (e.g. ["PROJ-123", "PROJ-456"]). On Cloud, each key is resolved to a numeric ID via Jira REST API — requires JIRA_USERNAME + JIRA_API_TOKEN env vars. Failures are reported as warnings and do not fail the tool call.',
|
|
283
281
|
items: { type: 'string' },
|
|
284
282
|
},
|
|
283
|
+
jira_project_version: {
|
|
284
|
+
type: 'integer',
|
|
285
|
+
description: 'Jira project version/release ID to link this test cycle to (optional, Cloud only — use the numeric version ID).',
|
|
286
|
+
},
|
|
285
287
|
custom_fields: {
|
|
286
288
|
type: 'object',
|
|
287
289
|
description: 'Custom fields object (optional)',
|
|
@@ -316,7 +318,7 @@ export const toolSchemas = [
|
|
|
316
318
|
},
|
|
317
319
|
test_run_keys: {
|
|
318
320
|
type: 'array',
|
|
319
|
-
description: 'Array of test run keys to search in (required for Data Center, optional for Cloud — e.g., ["PROJ-
|
|
321
|
+
description: 'Array of test run keys to search in (required for Data Center, optional for Cloud — e.g., ["PROJ-R152", "PROJ-R161"])',
|
|
320
322
|
items: { type: 'string' },
|
|
321
323
|
minItems: 1
|
|
322
324
|
},
|
|
@@ -395,7 +397,7 @@ export const toolSchemas = [
|
|
|
395
397
|
properties: {
|
|
396
398
|
test_run_key: {
|
|
397
399
|
type: 'string',
|
|
398
|
-
description: 'Test run key (e.g., PROJ-
|
|
400
|
+
description: 'Test run key (e.g., PROJ-R161)',
|
|
399
401
|
},
|
|
400
402
|
test_case_keys: {
|
|
401
403
|
type: 'array',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zephyr-scale-mcp-server",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.4",
|
|
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/tool-handlers.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { AxiosInstance } from 'axios';
|
|
2
|
+
import axios from 'axios';
|
|
2
3
|
import { McpError, ErrorCode } from '@modelcontextprotocol/sdk/types.js';
|
|
3
4
|
import {
|
|
4
5
|
TestCaseArgs,
|
|
@@ -47,6 +48,7 @@ export class ZephyrToolHandlers {
|
|
|
47
48
|
const {
|
|
48
49
|
project_key, name, test_script, folder, priority, precondition,
|
|
49
50
|
objective, estimated_time, labels, custom_fields, issue_links,
|
|
51
|
+
owner_id, component_id,
|
|
50
52
|
} = args;
|
|
51
53
|
|
|
52
54
|
const payload: any = { projectKey: project_key, name };
|
|
@@ -58,6 +60,9 @@ export class ZephyrToolHandlers {
|
|
|
58
60
|
if (estimated_time) payload.estimatedTime = estimated_time;
|
|
59
61
|
if (labels && labels.length > 0) payload.labels = labels;
|
|
60
62
|
if (custom_fields) payload.customFields = custom_fields;
|
|
63
|
+
// Cloud v2 uses ownerId (Jira Account ID) and componentId (integer)
|
|
64
|
+
if (owner_id) payload.ownerId = owner_id;
|
|
65
|
+
if (component_id) payload.componentId = component_id;
|
|
61
66
|
|
|
62
67
|
// Resolve folder path → folderId
|
|
63
68
|
if (folder) {
|
|
@@ -96,8 +101,12 @@ export class ZephyrToolHandlers {
|
|
|
96
101
|
}
|
|
97
102
|
}
|
|
98
103
|
|
|
104
|
+
const missingCreds = !process.env.JIRA_USERNAME || !process.env.JIRA_API_TOKEN;
|
|
105
|
+
const credHint = missingCreds
|
|
106
|
+
? '\n💡 Tip: Set JIRA_USERNAME and JIRA_API_TOKEN env vars to enable issue linking on Cloud.'
|
|
107
|
+
: '';
|
|
99
108
|
const warningText = linkWarnings.length > 0
|
|
100
|
-
? `\n⚠️ Some issue links failed:\n${linkWarnings.map(w => ` - ${w}`).join('\n')}`
|
|
109
|
+
? `\n⚠️ Some issue links failed:\n${linkWarnings.map(w => ` - ${w}`).join('\n')}${credHint}`
|
|
101
110
|
: '';
|
|
102
111
|
|
|
103
112
|
return {
|
|
@@ -115,13 +124,19 @@ export class ZephyrToolHandlers {
|
|
|
115
124
|
if (!test_script) return;
|
|
116
125
|
|
|
117
126
|
if (test_script.type === 'STEP_BY_STEP' && test_script.steps && test_script.steps.length > 0) {
|
|
118
|
-
const items = test_script.steps.map((step: any) =>
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
127
|
+
const items = test_script.steps.map((step: any) => {
|
|
128
|
+
// If step is a call-to-test (testCaseKey), use the testCase variant
|
|
129
|
+
if (step.testCaseKey) {
|
|
130
|
+
return { testCase: { testCaseKey: step.testCaseKey } };
|
|
131
|
+
}
|
|
132
|
+
return {
|
|
133
|
+
inline: {
|
|
134
|
+
description: step.description || '',
|
|
135
|
+
testData: step.testData || null,
|
|
136
|
+
expectedResult: step.expectedResult || null,
|
|
137
|
+
},
|
|
138
|
+
};
|
|
139
|
+
});
|
|
125
140
|
await this.axiosInstance.post(
|
|
126
141
|
`${this.jiraConfig.apiEndpoints.testcase}/${testKey}/teststeps`,
|
|
127
142
|
{ mode: 'OVERWRITE', items }
|
|
@@ -224,13 +239,23 @@ export class ZephyrToolHandlers {
|
|
|
224
239
|
if (typeof name === 'string' && name.trim().length > 0) {
|
|
225
240
|
const getResponse = await this.axiosInstance.get(`${this.jiraConfig.apiEndpoints.testcase}/${test_case_key}`);
|
|
226
241
|
const tc = getResponse.data;
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
242
|
+
// UpdateTestCaseInput requires: id, key, name, priority, project, status
|
|
243
|
+
// All optional fields must also be re-sent or the API will clear them
|
|
244
|
+
const putPayload: any = {
|
|
245
|
+
id: tc.id,
|
|
246
|
+
key: test_case_key,
|
|
230
247
|
name,
|
|
231
248
|
status: tc.status,
|
|
232
249
|
priority: tc.priority,
|
|
233
|
-
|
|
250
|
+
project: tc.project,
|
|
251
|
+
};
|
|
252
|
+
// Preserve all optional fields to avoid the API clearing them
|
|
253
|
+
for (const field of ['objective', 'precondition', 'estimatedTime', 'component', 'owner', 'folder']) {
|
|
254
|
+
if (tc[field] !== undefined && tc[field] !== null) putPayload[field] = tc[field];
|
|
255
|
+
}
|
|
256
|
+
if (Array.isArray(tc.labels) && tc.labels.length > 0) putPayload.labels = tc.labels;
|
|
257
|
+
if (tc.customFields && Object.keys(tc.customFields).length > 0) putPayload.customFields = tc.customFields;
|
|
258
|
+
await this.axiosInstance.put(`${this.jiraConfig.apiEndpoints.testcase}/${test_case_key}`, putPayload);
|
|
234
259
|
}
|
|
235
260
|
|
|
236
261
|
return {
|
|
@@ -318,8 +343,7 @@ export class ZephyrToolHandlers {
|
|
|
318
343
|
const { project_key, name: folderPath, folder_type = 'TEST_CASE' } = args;
|
|
319
344
|
|
|
320
345
|
// Cloud v2 uses folderType (not type) and parentId integer
|
|
321
|
-
|
|
322
|
-
const cloudFolderType = folder_type === 'TEST_RUN' ? 'TEST_CYCLE' : folder_type;
|
|
346
|
+
const cloudFolderType = folder_type;
|
|
323
347
|
|
|
324
348
|
const segments = folderPath.replace(/^\/+|\/+$/g, '').split('/').filter(Boolean);
|
|
325
349
|
if (segments.length === 0) {
|
|
@@ -493,19 +517,23 @@ export class ZephyrToolHandlers {
|
|
|
493
517
|
const {
|
|
494
518
|
project_key, name, test_case_keys, folder,
|
|
495
519
|
planned_start_date, planned_end_date, description,
|
|
496
|
-
owner, environment, custom_fields,
|
|
520
|
+
owner, environment, custom_fields, issue_links, issue_key,
|
|
521
|
+
jira_project_version,
|
|
497
522
|
} = args;
|
|
498
523
|
|
|
499
524
|
// Cloud v2 TestCycleInput: projectKey, name, description, plannedStartDate,
|
|
500
|
-
// plannedEndDate, statusName, folderId, ownerId, customFields
|
|
525
|
+
// plannedEndDate, statusName, folderId, ownerId, jiraProjectVersion, customFields
|
|
526
|
+
// Note: environment is NOT a TestCycleInput field on Cloud — it belongs on TestExecutionInput
|
|
501
527
|
const payload: any = { projectKey: project_key, name };
|
|
502
528
|
|
|
503
529
|
if (description) payload.description = description;
|
|
504
530
|
if (planned_start_date) payload.plannedStartDate = planned_start_date;
|
|
505
531
|
if (planned_end_date) payload.plannedEndDate = planned_end_date;
|
|
506
532
|
if (custom_fields) payload.customFields = custom_fields;
|
|
507
|
-
|
|
508
|
-
|
|
533
|
+
// Cloud v2 TestCycleInput supports ownerId (Jira Account ID)
|
|
534
|
+
if (owner) payload.ownerId = owner;
|
|
535
|
+
// Link to a Jira project version/release (integer ID)
|
|
536
|
+
if (jira_project_version) payload.jiraProjectVersion = jira_project_version;
|
|
509
537
|
if (folder) {
|
|
510
538
|
const folderId = await resolveFolderIdByPath(
|
|
511
539
|
this.axiosInstance, project_key, folder, 'TEST_CYCLE'
|
|
@@ -524,15 +552,47 @@ export class ZephyrToolHandlers {
|
|
|
524
552
|
// Step 2: add test cases via test executions (Cloud v2 has no /testcycles/{key}/testcases)
|
|
525
553
|
if (test_case_keys && test_case_keys.length > 0) {
|
|
526
554
|
for (const testCaseKey of test_case_keys) {
|
|
527
|
-
|
|
555
|
+
const execPayload: any = {
|
|
528
556
|
projectKey: project_key,
|
|
529
557
|
testCaseKey,
|
|
530
558
|
testCycleKey: cycleKey,
|
|
531
559
|
statusName: 'Not Executed',
|
|
532
|
-
}
|
|
560
|
+
};
|
|
561
|
+
// environment is set at execution level on Cloud, not cycle level
|
|
562
|
+
if (environment) execPayload.environmentName = environment;
|
|
563
|
+
await this.axiosInstance.post('/testexecutions', execPayload);
|
|
533
564
|
}
|
|
534
565
|
}
|
|
535
566
|
|
|
567
|
+
// Step 3: link Jira issues via POST /testcycles/{key}/links/issues
|
|
568
|
+
// Merge issue_key (single) and issue_links (array) into one list
|
|
569
|
+
const allIssueLinks = [
|
|
570
|
+
...(issue_key ? [issue_key] : []),
|
|
571
|
+
...(issue_links ?? []),
|
|
572
|
+
];
|
|
573
|
+
const linkWarnings: string[] = [];
|
|
574
|
+
if (allIssueLinks.length > 0) {
|
|
575
|
+
for (const ik of allIssueLinks) {
|
|
576
|
+
try {
|
|
577
|
+
const issueId = await this.resolveJiraIssueId(ik);
|
|
578
|
+
await this.axiosInstance.post(
|
|
579
|
+
`${this.jiraConfig.apiEndpoints.testrun}/${cycleKey}/links/issues`,
|
|
580
|
+
{ issueId }
|
|
581
|
+
);
|
|
582
|
+
} catch (e) {
|
|
583
|
+
linkWarnings.push(`${ik}: ${this.formatError(e)}`);
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
const missingCreds = !process.env.JIRA_USERNAME || !process.env.JIRA_API_TOKEN;
|
|
589
|
+
const credHint = missingCreds && linkWarnings.length > 0
|
|
590
|
+
? '\n💡 Tip: Set JIRA_USERNAME and JIRA_API_TOKEN env vars to enable issue linking on Cloud.'
|
|
591
|
+
: '';
|
|
592
|
+
const warningText = linkWarnings.length > 0
|
|
593
|
+
? `\n⚠️ Some issue links failed:\n${linkWarnings.map(w => ` - ${w}`).join('\n')}${credHint}`
|
|
594
|
+
: '';
|
|
595
|
+
|
|
536
596
|
return {
|
|
537
597
|
content: [{
|
|
538
598
|
type: 'text',
|
|
@@ -540,7 +600,8 @@ export class ZephyrToolHandlers {
|
|
|
540
600
|
key: cycleKey,
|
|
541
601
|
name,
|
|
542
602
|
testCaseCount: test_case_keys?.length || 0,
|
|
543
|
-
|
|
603
|
+
linkedIssues: allIssueLinks.length - linkWarnings.length,
|
|
604
|
+
}, null, 2)}${warningText}`,
|
|
544
605
|
}],
|
|
545
606
|
};
|
|
546
607
|
} catch (error) {
|
|
@@ -766,7 +827,7 @@ export class ZephyrToolHandlers {
|
|
|
766
827
|
text: `✅ Found ${testRuns.length} test run(s):\n${JSON.stringify({
|
|
767
828
|
totalCount: testRuns.length,
|
|
768
829
|
testRuns: testRuns.map((tr: any) => ({
|
|
769
|
-
key: tr.key, name: tr.name, status: tr.status?.
|
|
830
|
+
key: tr.key, name: tr.name, status: tr.status?.id, folder: tr.folder?.name,
|
|
770
831
|
})),
|
|
771
832
|
}, null, 2)}`,
|
|
772
833
|
}],
|
|
@@ -846,11 +907,30 @@ export class ZephyrToolHandlers {
|
|
|
846
907
|
}
|
|
847
908
|
|
|
848
909
|
private async resolveJiraIssueId(issueKey: string): Promise<number> {
|
|
849
|
-
// The Zephyr API key is
|
|
850
|
-
//
|
|
851
|
-
|
|
910
|
+
// The Zephyr API key is NOT valid for the Jira REST API — Jira Cloud requires
|
|
911
|
+
// Basic Auth: base64(email:api_token) via JIRA_USERNAME + JIRA_API_TOKEN env vars.
|
|
912
|
+
const username = process.env.JIRA_USERNAME;
|
|
913
|
+
const apiToken = process.env.JIRA_API_TOKEN;
|
|
914
|
+
|
|
852
915
|
const url = `${this.jiraConfig.jiraBaseUrl}/rest/api/3/issue/${issueKey}?fields=id`;
|
|
853
|
-
|
|
916
|
+
|
|
917
|
+
let response;
|
|
918
|
+
if (username && apiToken) {
|
|
919
|
+
// Jira Cloud Basic Auth
|
|
920
|
+
response = await axios.get(url, {
|
|
921
|
+
headers: { 'Accept': 'application/json' },
|
|
922
|
+
auth: { username, password: apiToken },
|
|
923
|
+
});
|
|
924
|
+
} else {
|
|
925
|
+
// Fallback: try Bearer token (works for Data Center with PAT)
|
|
926
|
+
response = await axios.get(url, {
|
|
927
|
+
headers: {
|
|
928
|
+
'Accept': 'application/json',
|
|
929
|
+
'Authorization': `Bearer ${process.env.ZEPHYR_API_KEY}`,
|
|
930
|
+
},
|
|
931
|
+
});
|
|
932
|
+
}
|
|
933
|
+
|
|
854
934
|
const id = parseInt(response.data.id, 10);
|
|
855
935
|
if (!id || isNaN(id)) {
|
|
856
936
|
throw new Error(`Could not resolve numeric ID for Jira issue "${issueKey}"`);
|
package/src/tool-schemas.ts
CHANGED
|
@@ -74,14 +74,12 @@ export const toolSchemas = [
|
|
|
74
74
|
},
|
|
75
75
|
status: {
|
|
76
76
|
type: 'string',
|
|
77
|
-
description: 'Test case status (optional)',
|
|
78
|
-
enum: ['Draft', 'Approved', 'Deprecated'],
|
|
77
|
+
description: 'Test case status (optional, default: "Draft"). Value must match a status name configured in your Zephyr project (e.g. "Draft", "Approved", "Deprecated"). Note: always overridden to "Draft" on creation.',
|
|
79
78
|
default: 'Draft',
|
|
80
79
|
},
|
|
81
80
|
priority: {
|
|
82
81
|
type: 'string',
|
|
83
|
-
description: 'Test case priority (optional)',
|
|
84
|
-
enum: ['High', 'Normal', 'Low'],
|
|
82
|
+
description: 'Test case priority (optional). Value must match a priority name configured in your Zephyr project (e.g. "High", "Normal", "Low", "Critical"). Use zephyr://testcase/EXISTING-KEY to check your project\'s valid values.',
|
|
85
83
|
},
|
|
86
84
|
precondition: {
|
|
87
85
|
type: 'string',
|
|
@@ -91,13 +89,13 @@ export const toolSchemas = [
|
|
|
91
89
|
type: 'string',
|
|
92
90
|
description: 'Test objective (optional)',
|
|
93
91
|
},
|
|
94
|
-
|
|
95
|
-
type: '
|
|
96
|
-
description: '
|
|
92
|
+
component_id: {
|
|
93
|
+
type: 'integer',
|
|
94
|
+
description: 'Jira component ID (optional, Cloud only — use the numeric component ID, not the name)',
|
|
97
95
|
},
|
|
98
|
-
|
|
96
|
+
owner_id: {
|
|
99
97
|
type: 'string',
|
|
100
|
-
description: 'Test case owner (optional)',
|
|
98
|
+
description: 'Test case owner Jira Account ID (optional, Cloud only — e.g. "5b10a2844c20165700ede21g")',
|
|
101
99
|
},
|
|
102
100
|
estimated_time: {
|
|
103
101
|
type: 'number',
|
|
@@ -191,7 +189,7 @@ export const toolSchemas = [
|
|
|
191
189
|
folder_type: {
|
|
192
190
|
type: 'string',
|
|
193
191
|
description: 'Type of folder',
|
|
194
|
-
enum: ['TEST_CASE', 'TEST_PLAN', '
|
|
192
|
+
enum: ['TEST_CASE', 'TEST_PLAN', 'TEST_CYCLE'],
|
|
195
193
|
default: 'TEST_CASE',
|
|
196
194
|
},
|
|
197
195
|
},
|
|
@@ -206,7 +204,7 @@ export const toolSchemas = [
|
|
|
206
204
|
properties: {
|
|
207
205
|
test_run_key: {
|
|
208
206
|
type: 'string',
|
|
209
|
-
description: 'Test run key (e.g., PROJ-
|
|
207
|
+
description: 'Test run key (e.g., PROJ-R123)',
|
|
210
208
|
},
|
|
211
209
|
},
|
|
212
210
|
required: ['test_run_key'],
|
|
@@ -271,17 +269,21 @@ export const toolSchemas = [
|
|
|
271
269
|
},
|
|
272
270
|
environment: {
|
|
273
271
|
type: 'string',
|
|
274
|
-
description: 'Test environment (optional)',
|
|
272
|
+
description: 'Test environment name (optional). On Cloud, applied to each test execution (environmentName). On Data Center, set at cycle level.',
|
|
275
273
|
},
|
|
276
274
|
issue_key: {
|
|
277
275
|
type: 'string',
|
|
278
|
-
description: 'Single issue key to link to the test
|
|
276
|
+
description: 'Single Jira issue key to link to the test cycle (e.g. "PROJ-123"). On Cloud, resolved to a numeric ID via Jira REST API — requires JIRA_USERNAME + JIRA_API_TOKEN env vars.',
|
|
279
277
|
},
|
|
280
278
|
issue_links: {
|
|
281
279
|
type: 'array',
|
|
282
|
-
description: 'Array of issue
|
|
280
|
+
description: 'Array of Jira issue keys to link to the test cycle (e.g. ["PROJ-123", "PROJ-456"]). On Cloud, each key is resolved to a numeric ID via Jira REST API — requires JIRA_USERNAME + JIRA_API_TOKEN env vars. Failures are reported as warnings and do not fail the tool call.',
|
|
283
281
|
items: { type: 'string' },
|
|
284
282
|
},
|
|
283
|
+
jira_project_version: {
|
|
284
|
+
type: 'integer',
|
|
285
|
+
description: 'Jira project version/release ID to link this test cycle to (optional, Cloud only — use the numeric version ID).',
|
|
286
|
+
},
|
|
285
287
|
custom_fields: {
|
|
286
288
|
type: 'object',
|
|
287
289
|
description: 'Custom fields object (optional)',
|
|
@@ -316,7 +318,7 @@ export const toolSchemas = [
|
|
|
316
318
|
},
|
|
317
319
|
test_run_keys: {
|
|
318
320
|
type: 'array',
|
|
319
|
-
description: 'Array of test run keys to search in (required for Data Center, optional for Cloud — e.g., ["PROJ-
|
|
321
|
+
description: 'Array of test run keys to search in (required for Data Center, optional for Cloud — e.g., ["PROJ-R152", "PROJ-R161"])',
|
|
320
322
|
items: { type: 'string' },
|
|
321
323
|
minItems: 1
|
|
322
324
|
},
|
|
@@ -395,7 +397,7 @@ export const toolSchemas = [
|
|
|
395
397
|
properties: {
|
|
396
398
|
test_run_key: {
|
|
397
399
|
type: 'string',
|
|
398
|
-
description: 'Test run key (e.g., PROJ-
|
|
400
|
+
description: 'Test run key (e.g., PROJ-R161)',
|
|
399
401
|
},
|
|
400
402
|
test_case_keys: {
|
|
401
403
|
type: 'array',
|
package/src/types.ts
CHANGED
|
@@ -31,8 +31,10 @@ export interface TestCaseArgs {
|
|
|
31
31
|
priority?: 'High' | 'Normal' | 'Low';
|
|
32
32
|
precondition?: string;
|
|
33
33
|
objective?: string;
|
|
34
|
-
component?: string;
|
|
35
|
-
owner?: string;
|
|
34
|
+
component?: string; // Data Center: component name
|
|
35
|
+
owner?: string; // Data Center: owner name
|
|
36
|
+
component_id?: number; // Cloud: Jira component ID (integer)
|
|
37
|
+
owner_id?: string; // Cloud: Jira Account ID
|
|
36
38
|
estimated_time?: number;
|
|
37
39
|
labels?: string[];
|
|
38
40
|
issue_links?: string[];
|
|
@@ -49,7 +51,7 @@ export interface UpdateBddArgs {
|
|
|
49
51
|
export interface FolderArgs {
|
|
50
52
|
project_key: string;
|
|
51
53
|
name: string; // Full folder path including parent folders (e.g., "/folder/subfolder")
|
|
52
|
-
folder_type?: 'TEST_CASE' | 'TEST_PLAN' | '
|
|
54
|
+
folder_type?: 'TEST_CASE' | 'TEST_PLAN' | 'TEST_CYCLE';
|
|
53
55
|
}
|
|
54
56
|
|
|
55
57
|
export interface TestRunArgs {
|
|
@@ -62,7 +64,8 @@ export interface TestRunArgs {
|
|
|
62
64
|
planned_end_date?: string;
|
|
63
65
|
description?: string;
|
|
64
66
|
owner?: string;
|
|
65
|
-
environment?: string;
|
|
67
|
+
environment?: string; // Cloud: mapped to environmentName on each TestExecutionInput; DC: cycle-level field
|
|
68
|
+
jira_project_version?: number; // Cloud only: Jira project version/release ID (integer)
|
|
66
69
|
issue_key?: string;
|
|
67
70
|
issue_links?: string[];
|
|
68
71
|
custom_fields?: Record<string, any>;
|