zephyr-scale-mcp-server 0.4.3 → 0.4.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.
@@ -108,13 +108,19 @@ export class ZephyrToolHandlers {
108
108
  if (!test_script)
109
109
  return;
110
110
  if (test_script.type === 'STEP_BY_STEP' && test_script.steps && test_script.steps.length > 0) {
111
- const items = test_script.steps.map((step) => ({
112
- inline: {
113
- description: step.description || '',
114
- testData: step.testData || null,
115
- expectedResult: step.expectedResult || null,
116
- },
117
- }));
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
+ });
118
124
  await this.axiosInstance.post(`${this.jiraConfig.apiEndpoints.testcase}/${testKey}/teststeps`, { mode: 'OVERWRITE', items });
119
125
  }
120
126
  else if (test_script.type === 'BDD' && test_script.text) {
@@ -206,22 +212,82 @@ export class ZephyrToolHandlers {
206
212
  const { test_case_key, bdd_content, name } = args;
207
213
  const converted = convertToGherkin(bdd_content);
208
214
  const finalText = converted && converted.trim().length > 0 ? converted : bdd_content;
215
+ // Fetch the test case first to get the numeric ID.
216
+ // After a DC→Cloud migration, the project key prefix in the test case key (e.g. "CNIDS")
217
+ // may no longer match an active Cloud project, causing key-based write endpoints to return 404.
218
+ // Falling back to the numeric ID bypasses that project-key validation.
219
+ let tc = null;
220
+ try {
221
+ const getResponse = await this.axiosInstance.get(`${this.jiraConfig.apiEndpoints.testcase}/${test_case_key}`);
222
+ tc = getResponse.data;
223
+ }
224
+ catch {
225
+ // GET failed — proceed with key only; write will surface the real error
226
+ }
209
227
  try {
210
- await this.axiosInstance.post(`${this.jiraConfig.apiEndpoints.testcase}/${test_case_key}/testscript`, { type: 'bdd', text: finalText });
211
- // Only fetch and PUT metadata when the caller also wants to rename the test case
228
+ // Primary path: POST to dedicated testscript endpoint using the key
229
+ let scriptUpdateError = null;
230
+ try {
231
+ await this.axiosInstance.post(`${this.jiraConfig.apiEndpoints.testcase}/${test_case_key}/testscript`, { type: 'bdd', text: finalText });
232
+ }
233
+ catch (err) {
234
+ scriptUpdateError = err;
235
+ }
236
+ // Fallback: if testscript POST failed (e.g. migrated project key), try PUT on the full
237
+ // test case record with the testScript field embedded — some Cloud instances accept this
238
+ // for migrated test cases where the project is deactivated.
239
+ if (scriptUpdateError) {
240
+ if (!tc) {
241
+ throw scriptUpdateError; // no test case data to build PUT payload, surface original error
242
+ }
243
+ const putPayload = {
244
+ id: tc.id,
245
+ key: test_case_key,
246
+ name: (typeof name === 'string' && name.trim().length > 0) ? name : tc.name,
247
+ status: tc.status,
248
+ priority: tc.priority,
249
+ project: tc.project,
250
+ testScript: { type: 'bdd', text: finalText },
251
+ };
252
+ for (const field of ['objective', 'precondition', 'estimatedTime', 'component', 'owner', 'folder']) {
253
+ if (tc[field] !== undefined && tc[field] !== null)
254
+ putPayload[field] = tc[field];
255
+ }
256
+ if (Array.isArray(tc.labels) && tc.labels.length > 0)
257
+ putPayload.labels = tc.labels;
258
+ if (tc.customFields && Object.keys(tc.customFields).length > 0)
259
+ putPayload.customFields = tc.customFields;
260
+ await this.axiosInstance.put(`${this.jiraConfig.apiEndpoints.testcase}/${test_case_key}`, putPayload);
261
+ return {
262
+ content: [{
263
+ type: 'text',
264
+ text: `✅ Updated ${test_case_key} with BDD content successfully (Cloud v2, via PUT fallback for migrated project)`,
265
+ }],
266
+ };
267
+ }
268
+ // Primary path succeeded — optionally rename
212
269
  if (typeof name === 'string' && name.trim().length > 0) {
213
- const getResponse = await this.axiosInstance.get(`${this.jiraConfig.apiEndpoints.testcase}/${test_case_key}`);
214
- const tc = getResponse.data;
215
- // UpdateTestCaseInput requires: id, key, name, priority, project, status
216
- // tc.project is a ProjectLink { id, self } — pass it back as-is
217
- await this.axiosInstance.put(`${this.jiraConfig.apiEndpoints.testcase}/${test_case_key}`, {
270
+ if (!tc) {
271
+ const getResponse = await this.axiosInstance.get(`${this.jiraConfig.apiEndpoints.testcase}/${test_case_key}`);
272
+ tc = getResponse.data;
273
+ }
274
+ const putPayload = {
218
275
  id: tc.id,
219
276
  key: test_case_key,
220
277
  name,
221
278
  status: tc.status,
222
279
  priority: tc.priority,
223
280
  project: tc.project,
224
- });
281
+ };
282
+ for (const field of ['objective', 'precondition', 'estimatedTime', 'component', 'owner', 'folder']) {
283
+ if (tc[field] !== undefined && tc[field] !== null)
284
+ putPayload[field] = tc[field];
285
+ }
286
+ if (Array.isArray(tc.labels) && tc.labels.length > 0)
287
+ putPayload.labels = tc.labels;
288
+ if (tc.customFields && Object.keys(tc.customFields).length > 0)
289
+ putPayload.customFields = tc.customFields;
290
+ await this.axiosInstance.put(`${this.jiraConfig.apiEndpoints.testcase}/${test_case_key}`, putPayload);
225
291
  }
226
292
  return {
227
293
  content: [{
@@ -454,9 +520,10 @@ export class ZephyrToolHandlers {
454
520
  return this.createTestRunDC(args);
455
521
  }
456
522
  async createTestRunCloud(args) {
457
- const { project_key, name, test_case_keys, folder, planned_start_date, planned_end_date, description, owner, environment, custom_fields, } = args;
523
+ 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;
458
524
  // Cloud v2 TestCycleInput: projectKey, name, description, plannedStartDate,
459
- // 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
460
527
  const payload = { projectKey: project_key, name };
461
528
  if (description)
462
529
  payload.description = description;
@@ -469,6 +536,9 @@ export class ZephyrToolHandlers {
469
536
  // Cloud v2 TestCycleInput supports ownerId (Jira Account ID)
470
537
  if (owner)
471
538
  payload.ownerId = owner;
539
+ // Link to a Jira project version/release (integer ID)
540
+ if (jira_project_version)
541
+ payload.jiraProjectVersion = jira_project_version;
472
542
  if (folder) {
473
543
  const folderId = await resolveFolderIdByPath(this.axiosInstance, project_key, folder, 'TEST_CYCLE');
474
544
  if (folderId !== null)
@@ -483,14 +553,43 @@ export class ZephyrToolHandlers {
483
553
  // Step 2: add test cases via test executions (Cloud v2 has no /testcycles/{key}/testcases)
484
554
  if (test_case_keys && test_case_keys.length > 0) {
485
555
  for (const testCaseKey of test_case_keys) {
486
- await this.axiosInstance.post('/testexecutions', {
556
+ const execPayload = {
487
557
  projectKey: project_key,
488
558
  testCaseKey,
489
559
  testCycleKey: cycleKey,
490
560
  statusName: 'Not Executed',
491
- });
561
+ };
562
+ // environment is set at execution level on Cloud, not cycle level
563
+ if (environment)
564
+ execPayload.environmentName = environment;
565
+ await this.axiosInstance.post('/testexecutions', execPayload);
492
566
  }
493
567
  }
568
+ // Step 3: link Jira issues via POST /testcycles/{key}/links/issues
569
+ // Merge issue_key (single) and issue_links (array) into one list
570
+ const allIssueLinks = [
571
+ ...(issue_key ? [issue_key] : []),
572
+ ...(issue_links ?? []),
573
+ ];
574
+ const linkWarnings = [];
575
+ if (allIssueLinks.length > 0) {
576
+ for (const ik of allIssueLinks) {
577
+ try {
578
+ const issueId = await this.resolveJiraIssueId(ik);
579
+ await this.axiosInstance.post(`${this.jiraConfig.apiEndpoints.testrun}/${cycleKey}/links/issues`, { issueId });
580
+ }
581
+ catch (e) {
582
+ linkWarnings.push(`${ik}: ${this.formatError(e)}`);
583
+ }
584
+ }
585
+ }
586
+ const missingCreds = !process.env.JIRA_USERNAME || !process.env.JIRA_API_TOKEN;
587
+ const credHint = missingCreds && linkWarnings.length > 0
588
+ ? '\n💡 Tip: Set JIRA_USERNAME and JIRA_API_TOKEN env vars to enable issue linking on Cloud.'
589
+ : '';
590
+ const warningText = linkWarnings.length > 0
591
+ ? `\n⚠️ Some issue links failed:\n${linkWarnings.map(w => ` - ${w}`).join('\n')}${credHint}`
592
+ : '';
494
593
  return {
495
594
  content: [{
496
595
  type: 'text',
@@ -498,7 +597,8 @@ export class ZephyrToolHandlers {
498
597
  key: cycleKey,
499
598
  name,
500
599
  testCaseCount: test_case_keys?.length || 0,
501
- }, null, 2)}`,
600
+ linkedIssues: allIssueLinks.length - linkWarnings.length,
601
+ }, null, 2)}${warningText}`,
502
602
  }],
503
603
  };
504
604
  }
@@ -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',
@@ -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-C123)',
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 run (optional) - will be mapped to issueKey in API',
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 links (optional) - will be mapped to issueLinks in API',
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-C152", "PROJ-C161"])',
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-C161)',
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",
3
+ "version": "0.4.5",
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",
@@ -124,13 +124,19 @@ export class ZephyrToolHandlers {
124
124
  if (!test_script) return;
125
125
 
126
126
  if (test_script.type === 'STEP_BY_STEP' && test_script.steps && test_script.steps.length > 0) {
127
- const items = test_script.steps.map((step: any) => ({
128
- inline: {
129
- description: step.description || '',
130
- testData: step.testData || null,
131
- expectedResult: step.expectedResult || null,
132
- },
133
- }));
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
+ });
134
140
  await this.axiosInstance.post(
135
141
  `${this.jiraConfig.apiEndpoints.testcase}/${testKey}/teststeps`,
136
142
  { mode: 'OVERWRITE', items }
@@ -223,26 +229,82 @@ export class ZephyrToolHandlers {
223
229
  const converted = convertToGherkin(bdd_content);
224
230
  const finalText = converted && converted.trim().length > 0 ? converted : bdd_content;
225
231
 
232
+ // Fetch the test case first to get the numeric ID.
233
+ // After a DC→Cloud migration, the project key prefix in the test case key (e.g. "CNIDS")
234
+ // may no longer match an active Cloud project, causing key-based write endpoints to return 404.
235
+ // Falling back to the numeric ID bypasses that project-key validation.
236
+ let tc: any = null;
226
237
  try {
227
- await this.axiosInstance.post(
228
- `${this.jiraConfig.apiEndpoints.testcase}/${test_case_key}/testscript`,
229
- { type: 'bdd', text: finalText }
230
- );
238
+ const getResponse = await this.axiosInstance.get(`${this.jiraConfig.apiEndpoints.testcase}/${test_case_key}`);
239
+ tc = getResponse.data;
240
+ } catch {
241
+ // GET failed — proceed with key only; write will surface the real error
242
+ }
243
+
244
+ try {
245
+ // Primary path: POST to dedicated testscript endpoint using the key
246
+ let scriptUpdateError: any = null;
247
+ try {
248
+ await this.axiosInstance.post(
249
+ `${this.jiraConfig.apiEndpoints.testcase}/${test_case_key}/testscript`,
250
+ { type: 'bdd', text: finalText }
251
+ );
252
+ } catch (err: any) {
253
+ scriptUpdateError = err;
254
+ }
231
255
 
232
- // Only fetch and PUT metadata when the caller also wants to rename the test case
256
+ // Fallback: if testscript POST failed (e.g. migrated project key), try PUT on the full
257
+ // test case record with the testScript field embedded — some Cloud instances accept this
258
+ // for migrated test cases where the project is deactivated.
259
+ if (scriptUpdateError) {
260
+ if (!tc) {
261
+ throw scriptUpdateError; // no test case data to build PUT payload, surface original error
262
+ }
263
+ const putPayload: any = {
264
+ id: tc.id,
265
+ key: test_case_key,
266
+ name: (typeof name === 'string' && name.trim().length > 0) ? name : tc.name,
267
+ status: tc.status,
268
+ priority: tc.priority,
269
+ project: tc.project,
270
+ testScript: { type: 'bdd', text: finalText },
271
+ };
272
+ for (const field of ['objective', 'precondition', 'estimatedTime', 'component', 'owner', 'folder']) {
273
+ if (tc[field] !== undefined && tc[field] !== null) putPayload[field] = tc[field];
274
+ }
275
+ if (Array.isArray(tc.labels) && tc.labels.length > 0) putPayload.labels = tc.labels;
276
+ if (tc.customFields && Object.keys(tc.customFields).length > 0) putPayload.customFields = tc.customFields;
277
+
278
+ await this.axiosInstance.put(`${this.jiraConfig.apiEndpoints.testcase}/${test_case_key}`, putPayload);
279
+
280
+ return {
281
+ content: [{
282
+ type: 'text',
283
+ text: `✅ Updated ${test_case_key} with BDD content successfully (Cloud v2, via PUT fallback for migrated project)`,
284
+ }],
285
+ };
286
+ }
287
+
288
+ // Primary path succeeded — optionally rename
233
289
  if (typeof name === 'string' && name.trim().length > 0) {
234
- const getResponse = await this.axiosInstance.get(`${this.jiraConfig.apiEndpoints.testcase}/${test_case_key}`);
235
- const tc = getResponse.data;
236
- // UpdateTestCaseInput requires: id, key, name, priority, project, status
237
- // tc.project is a ProjectLink { id, self } — pass it back as-is
238
- await this.axiosInstance.put(`${this.jiraConfig.apiEndpoints.testcase}/${test_case_key}`, {
290
+ if (!tc) {
291
+ const getResponse = await this.axiosInstance.get(`${this.jiraConfig.apiEndpoints.testcase}/${test_case_key}`);
292
+ tc = getResponse.data;
293
+ }
294
+ const putPayload: any = {
239
295
  id: tc.id,
240
296
  key: test_case_key,
241
297
  name,
242
298
  status: tc.status,
243
299
  priority: tc.priority,
244
300
  project: tc.project,
245
- });
301
+ };
302
+ for (const field of ['objective', 'precondition', 'estimatedTime', 'component', 'owner', 'folder']) {
303
+ if (tc[field] !== undefined && tc[field] !== null) putPayload[field] = tc[field];
304
+ }
305
+ if (Array.isArray(tc.labels) && tc.labels.length > 0) putPayload.labels = tc.labels;
306
+ if (tc.customFields && Object.keys(tc.customFields).length > 0) putPayload.customFields = tc.customFields;
307
+ await this.axiosInstance.put(`${this.jiraConfig.apiEndpoints.testcase}/${test_case_key}`, putPayload);
246
308
  }
247
309
 
248
310
  return {
@@ -504,11 +566,13 @@ export class ZephyrToolHandlers {
504
566
  const {
505
567
  project_key, name, test_case_keys, folder,
506
568
  planned_start_date, planned_end_date, description,
507
- owner, environment, custom_fields,
569
+ owner, environment, custom_fields, issue_links, issue_key,
570
+ jira_project_version,
508
571
  } = args;
509
572
 
510
573
  // Cloud v2 TestCycleInput: projectKey, name, description, plannedStartDate,
511
- // plannedEndDate, statusName, folderId, ownerId, customFields
574
+ // plannedEndDate, statusName, folderId, ownerId, jiraProjectVersion, customFields
575
+ // Note: environment is NOT a TestCycleInput field on Cloud — it belongs on TestExecutionInput
512
576
  const payload: any = { projectKey: project_key, name };
513
577
 
514
578
  if (description) payload.description = description;
@@ -517,6 +581,8 @@ export class ZephyrToolHandlers {
517
581
  if (custom_fields) payload.customFields = custom_fields;
518
582
  // Cloud v2 TestCycleInput supports ownerId (Jira Account ID)
519
583
  if (owner) payload.ownerId = owner;
584
+ // Link to a Jira project version/release (integer ID)
585
+ if (jira_project_version) payload.jiraProjectVersion = jira_project_version;
520
586
  if (folder) {
521
587
  const folderId = await resolveFolderIdByPath(
522
588
  this.axiosInstance, project_key, folder, 'TEST_CYCLE'
@@ -535,15 +601,47 @@ export class ZephyrToolHandlers {
535
601
  // Step 2: add test cases via test executions (Cloud v2 has no /testcycles/{key}/testcases)
536
602
  if (test_case_keys && test_case_keys.length > 0) {
537
603
  for (const testCaseKey of test_case_keys) {
538
- await this.axiosInstance.post('/testexecutions', {
604
+ const execPayload: any = {
539
605
  projectKey: project_key,
540
606
  testCaseKey,
541
607
  testCycleKey: cycleKey,
542
608
  statusName: 'Not Executed',
543
- });
609
+ };
610
+ // environment is set at execution level on Cloud, not cycle level
611
+ if (environment) execPayload.environmentName = environment;
612
+ await this.axiosInstance.post('/testexecutions', execPayload);
544
613
  }
545
614
  }
546
615
 
616
+ // Step 3: link Jira issues via POST /testcycles/{key}/links/issues
617
+ // Merge issue_key (single) and issue_links (array) into one list
618
+ const allIssueLinks = [
619
+ ...(issue_key ? [issue_key] : []),
620
+ ...(issue_links ?? []),
621
+ ];
622
+ const linkWarnings: string[] = [];
623
+ if (allIssueLinks.length > 0) {
624
+ for (const ik of allIssueLinks) {
625
+ try {
626
+ const issueId = await this.resolveJiraIssueId(ik);
627
+ await this.axiosInstance.post(
628
+ `${this.jiraConfig.apiEndpoints.testrun}/${cycleKey}/links/issues`,
629
+ { issueId }
630
+ );
631
+ } catch (e) {
632
+ linkWarnings.push(`${ik}: ${this.formatError(e)}`);
633
+ }
634
+ }
635
+ }
636
+
637
+ const missingCreds = !process.env.JIRA_USERNAME || !process.env.JIRA_API_TOKEN;
638
+ const credHint = missingCreds && linkWarnings.length > 0
639
+ ? '\n💡 Tip: Set JIRA_USERNAME and JIRA_API_TOKEN env vars to enable issue linking on Cloud.'
640
+ : '';
641
+ const warningText = linkWarnings.length > 0
642
+ ? `\n⚠️ Some issue links failed:\n${linkWarnings.map(w => ` - ${w}`).join('\n')}${credHint}`
643
+ : '';
644
+
547
645
  return {
548
646
  content: [{
549
647
  type: 'text',
@@ -551,7 +649,8 @@ export class ZephyrToolHandlers {
551
649
  key: cycleKey,
552
650
  name,
553
651
  testCaseCount: test_case_keys?.length || 0,
554
- }, null, 2)}`,
652
+ linkedIssues: allIssueLinks.length - linkWarnings.length,
653
+ }, null, 2)}${warningText}`,
555
654
  }],
556
655
  };
557
656
  } catch (error) {
@@ -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',
@@ -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-C123)',
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 run (optional) - will be mapped to issueKey in API',
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 links (optional) - will be mapped to issueLinks in API',
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-C152", "PROJ-C161"])',
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-C161)',
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
@@ -64,7 +64,8 @@ export interface TestRunArgs {
64
64
  planned_end_date?: string;
65
65
  description?: string;
66
66
  owner?: string;
67
- 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)
68
69
  issue_key?: string;
69
70
  issue_links?: string[];
70
71
  custom_fields?: Record<string, any>;