zephyr-scale-mcp-server 0.4.1 → 0.4.3

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 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": "Feature: User Login\n\nScenario: Valid user login\n Given a user with valid credentials\n When the user attempts to log in\n Then the user should be authenticated successfully"
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**: The server will automatically convert markdown-style BDD to proper Gherkin format.
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
- ### Jira Cloud regional API endpoint (optional)
146
- Zephyr Scale Cloud has regional API hosts. By default the server uses the **US** endpoint (`https://api.zephyrscale.smartbear.com/v2`). If your instance uses the **EU** API (e.g. for EU data residency), set:
147
- - **`ZEPHYR_API_BASE_URL`**: Full base URL for the Zephyr Scale Cloud API (no trailing slash).
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
- **Example EU:**
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
- - `ZEPHYR_BASE_URL`: `https://your-jira-server.com`
161
- - `ZEPHYR_API_KEY`: Your Zephyr Scale API token from your Jira profile settings.
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"`.
@@ -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, } = 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');
@@ -66,10 +72,31 @@ export class ZephyrToolHandlers {
66
72
  if (test_script) {
67
73
  await this.upsertTestScriptCloud(testKey, test_script);
68
74
  }
75
+ // Step 3: link Jira issues via POST /testcases/{key}/links/issues
76
+ // IssueLinkInput requires a numeric issueId — resolve each key via Jira REST API
77
+ const linkWarnings = [];
78
+ if (issue_links && issue_links.length > 0) {
79
+ for (const issueKey of issue_links) {
80
+ try {
81
+ const issueId = await this.resolveJiraIssueId(issueKey);
82
+ await this.axiosInstance.post(`${this.jiraConfig.apiEndpoints.testcase}/${testKey}/links/issues`, { issueId });
83
+ }
84
+ catch (e) {
85
+ linkWarnings.push(`${issueKey}: ${this.formatError(e)}`);
86
+ }
87
+ }
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
+ : '';
93
+ const warningText = linkWarnings.length > 0
94
+ ? `\n⚠️ Some issue links failed:\n${linkWarnings.map(w => ` - ${w}`).join('\n')}${credHint}`
95
+ : '';
69
96
  return {
70
97
  content: [{
71
98
  type: 'text',
72
- text: `✅ Test case created successfully: ${testKey}\n${JSON.stringify({ key: testKey, type: test_script?.type || 'none' }, null, 2)}`,
99
+ text: `✅ Test case created successfully: ${testKey}\n${JSON.stringify({ key: testKey, type: test_script?.type || 'none', linkedIssues: (issue_links ?? []).length - linkWarnings.length }, null, 2)}${warningText}`,
73
100
  }],
74
101
  };
75
102
  }
@@ -185,12 +212,15 @@ export class ZephyrToolHandlers {
185
212
  if (typeof name === 'string' && name.trim().length > 0) {
186
213
  const getResponse = await this.axiosInstance.get(`${this.jiraConfig.apiEndpoints.testcase}/${test_case_key}`);
187
214
  const tc = getResponse.data;
188
- const projectKey = tc.projectKey ?? test_case_key.replace(/-T\d+$/, '');
215
+ // UpdateTestCaseInput requires: id, key, name, priority, project, status
216
+ // tc.project is a ProjectLink { id, self } — pass it back as-is
189
217
  await this.axiosInstance.put(`${this.jiraConfig.apiEndpoints.testcase}/${test_case_key}`, {
190
- projectKey,
218
+ id: tc.id,
219
+ key: test_case_key,
191
220
  name,
192
221
  status: tc.status,
193
222
  priority: tc.priority,
223
+ project: tc.project,
194
224
  });
195
225
  }
196
226
  return {
@@ -272,8 +302,7 @@ export class ZephyrToolHandlers {
272
302
  async createFolderCloud(args) {
273
303
  const { project_key, name: folderPath, folder_type = 'TEST_CASE' } = args;
274
304
  // Cloud v2 uses folderType (not type) and parentId integer
275
- // Map TEST_RUN → TEST_CYCLE for Cloud v2
276
- const cloudFolderType = folder_type === 'TEST_RUN' ? 'TEST_CYCLE' : folder_type;
305
+ const cloudFolderType = folder_type;
277
306
  const segments = folderPath.replace(/^\/+|\/+$/g, '').split('/').filter(Boolean);
278
307
  if (segments.length === 0) {
279
308
  throw new McpError(ErrorCode.InvalidParams, 'folder name/path cannot be empty');
@@ -437,7 +466,9 @@ export class ZephyrToolHandlers {
437
466
  payload.plannedEndDate = planned_end_date;
438
467
  if (custom_fields)
439
468
  payload.customFields = custom_fields;
440
- // Resolve folder path folderId
469
+ // Cloud v2 TestCycleInput supports ownerId (Jira Account ID)
470
+ if (owner)
471
+ payload.ownerId = owner;
441
472
  if (folder) {
442
473
  const folderId = await resolveFolderIdByPath(this.axiosInstance, project_key, folder, 'TEST_CYCLE');
443
474
  if (folderId !== null)
@@ -677,7 +708,7 @@ export class ZephyrToolHandlers {
677
708
  text: `✅ Found ${testRuns.length} test run(s):\n${JSON.stringify({
678
709
  totalCount: testRuns.length,
679
710
  testRuns: testRuns.map((tr) => ({
680
- key: tr.key, name: tr.name, status: tr.status?.name, folder: tr.folder?.name,
711
+ key: tr.key, name: tr.name, status: tr.status?.id, folder: tr.folder?.name,
681
712
  })),
682
713
  }, null, 2)}`,
683
714
  }],
@@ -751,6 +782,35 @@ export class ZephyrToolHandlers {
751
782
  throw new McpError(ErrorCode.InternalError, `Failed to add test cases: ${this.formatError(error)}`);
752
783
  }
753
784
  }
785
+ async resolveJiraIssueId(issueKey) {
786
+ // The Zephyr API key is NOT valid for the Jira REST API — Jira Cloud requires
787
+ // Basic Auth: base64(email:api_token) via JIRA_USERNAME + JIRA_API_TOKEN env vars.
788
+ const username = process.env.JIRA_USERNAME;
789
+ const apiToken = process.env.JIRA_API_TOKEN;
790
+ const url = `${this.jiraConfig.jiraBaseUrl}/rest/api/3/issue/${issueKey}?fields=id`;
791
+ let response;
792
+ if (username && apiToken) {
793
+ // Jira Cloud Basic Auth
794
+ response = await axios.get(url, {
795
+ headers: { 'Accept': 'application/json' },
796
+ auth: { username, password: apiToken },
797
+ });
798
+ }
799
+ else {
800
+ // Fallback: try Bearer token (works for Data Center with PAT)
801
+ response = await axios.get(url, {
802
+ headers: {
803
+ 'Accept': 'application/json',
804
+ 'Authorization': `Bearer ${process.env.ZEPHYR_API_KEY}`,
805
+ },
806
+ });
807
+ }
808
+ const id = parseInt(response.data.id, 10);
809
+ if (!id || isNaN(id)) {
810
+ throw new Error(`Could not resolve numeric ID for Jira issue "${issueKey}"`);
811
+ }
812
+ return id;
813
+ }
754
814
  formatError(error) {
755
815
  if (error instanceof Error && 'response' in error) {
756
816
  const axiosError = error;
@@ -91,13 +91,13 @@ export const toolSchemas = [
91
91
  type: 'string',
92
92
  description: 'Test objective (optional)',
93
93
  },
94
- component: {
95
- type: 'string',
96
- description: 'Component name (optional)',
94
+ component_id: {
95
+ type: 'integer',
96
+ description: 'Jira component ID (optional, Cloud only — use the numeric component ID, not the name)',
97
97
  },
98
- owner: {
98
+ owner_id: {
99
99
  type: 'string',
100
- description: 'Test case owner (optional)',
100
+ description: 'Test case owner Jira Account ID (optional, Cloud only — e.g. "5b10a2844c20165700ede21g")',
101
101
  },
102
102
  estimated_time: {
103
103
  type: 'number',
@@ -110,7 +110,7 @@ export const toolSchemas = [
110
110
  },
111
111
  issue_links: {
112
112
  type: 'array',
113
- description: 'Array of issue links (optional) - will be mapped to issueLinks in API',
113
+ description: 'Array of Jira issue keys to link to this test case (e.g. ["PROJ-123", "PROJ-456"]). On Cloud, each key is resolved to a numeric Jira issue ID via the Jira REST API, then linked via POST /testcases/{key}/links/issues — failures are reported as warnings but do not fail the tool call. On Data Center, sent directly in the create payload.',
114
114
  items: { type: 'string' }
115
115
  },
116
116
  custom_fields: {
@@ -191,7 +191,7 @@ export const toolSchemas = [
191
191
  folder_type: {
192
192
  type: 'string',
193
193
  description: 'Type of folder',
194
- enum: ['TEST_CASE', 'TEST_PLAN', 'TEST_RUN'],
194
+ enum: ['TEST_CASE', 'TEST_PLAN', 'TEST_CYCLE'],
195
195
  default: 'TEST_CASE',
196
196
  },
197
197
  },
package/build/utils.js CHANGED
@@ -145,6 +145,7 @@ export function createJiraConfig() {
145
145
  return {
146
146
  type,
147
147
  baseUrl,
148
+ jiraBaseUrl,
148
149
  authHeaders,
149
150
  apiEndpoints,
150
151
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zephyr-scale-mcp-server",
3
- "version": "0.4.1",
3
+ "version": "0.4.3",
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",
@@ -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,
@@ -46,7 +47,8 @@ export class ZephyrToolHandlers {
46
47
  private async createTestCaseCloud(args: TestCaseArgs) {
47
48
  const {
48
49
  project_key, name, test_script, folder, priority, precondition,
49
- objective, estimated_time, labels, custom_fields,
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) {
@@ -79,10 +84,35 @@ export class ZephyrToolHandlers {
79
84
  await this.upsertTestScriptCloud(testKey, test_script);
80
85
  }
81
86
 
87
+ // Step 3: link Jira issues via POST /testcases/{key}/links/issues
88
+ // IssueLinkInput requires a numeric issueId — resolve each key via Jira REST API
89
+ const linkWarnings: string[] = [];
90
+ if (issue_links && issue_links.length > 0) {
91
+ for (const issueKey of issue_links) {
92
+ try {
93
+ const issueId = await this.resolveJiraIssueId(issueKey);
94
+ await this.axiosInstance.post(
95
+ `${this.jiraConfig.apiEndpoints.testcase}/${testKey}/links/issues`,
96
+ { issueId }
97
+ );
98
+ } catch (e) {
99
+ linkWarnings.push(`${issueKey}: ${this.formatError(e)}`);
100
+ }
101
+ }
102
+ }
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
+ : '';
108
+ const warningText = linkWarnings.length > 0
109
+ ? `\n⚠️ Some issue links failed:\n${linkWarnings.map(w => ` - ${w}`).join('\n')}${credHint}`
110
+ : '';
111
+
82
112
  return {
83
113
  content: [{
84
114
  type: 'text',
85
- text: `✅ Test case created successfully: ${testKey}\n${JSON.stringify({ key: testKey, type: test_script?.type || 'none' }, null, 2)}`,
115
+ text: `✅ Test case created successfully: ${testKey}\n${JSON.stringify({ key: testKey, type: test_script?.type || 'none', linkedIssues: (issue_links ?? []).length - linkWarnings.length }, null, 2)}${warningText}`,
86
116
  }],
87
117
  };
88
118
  } catch (error) {
@@ -203,12 +233,15 @@ export class ZephyrToolHandlers {
203
233
  if (typeof name === 'string' && name.trim().length > 0) {
204
234
  const getResponse = await this.axiosInstance.get(`${this.jiraConfig.apiEndpoints.testcase}/${test_case_key}`);
205
235
  const tc = getResponse.data;
206
- const projectKey = tc.projectKey ?? test_case_key.replace(/-T\d+$/, '');
236
+ // UpdateTestCaseInput requires: id, key, name, priority, project, status
237
+ // tc.project is a ProjectLink { id, self } — pass it back as-is
207
238
  await this.axiosInstance.put(`${this.jiraConfig.apiEndpoints.testcase}/${test_case_key}`, {
208
- projectKey,
239
+ id: tc.id,
240
+ key: test_case_key,
209
241
  name,
210
242
  status: tc.status,
211
243
  priority: tc.priority,
244
+ project: tc.project,
212
245
  });
213
246
  }
214
247
 
@@ -297,8 +330,7 @@ export class ZephyrToolHandlers {
297
330
  const { project_key, name: folderPath, folder_type = 'TEST_CASE' } = args;
298
331
 
299
332
  // Cloud v2 uses folderType (not type) and parentId integer
300
- // Map TEST_RUN → TEST_CYCLE for Cloud v2
301
- const cloudFolderType = folder_type === 'TEST_RUN' ? 'TEST_CYCLE' : folder_type;
333
+ const cloudFolderType = folder_type;
302
334
 
303
335
  const segments = folderPath.replace(/^\/+|\/+$/g, '').split('/').filter(Boolean);
304
336
  if (segments.length === 0) {
@@ -483,8 +515,8 @@ export class ZephyrToolHandlers {
483
515
  if (planned_start_date) payload.plannedStartDate = planned_start_date;
484
516
  if (planned_end_date) payload.plannedEndDate = planned_end_date;
485
517
  if (custom_fields) payload.customFields = custom_fields;
486
-
487
- // Resolve folder path → folderId
518
+ // Cloud v2 TestCycleInput supports ownerId (Jira Account ID)
519
+ if (owner) payload.ownerId = owner;
488
520
  if (folder) {
489
521
  const folderId = await resolveFolderIdByPath(
490
522
  this.axiosInstance, project_key, folder, 'TEST_CYCLE'
@@ -745,7 +777,7 @@ export class ZephyrToolHandlers {
745
777
  text: `✅ Found ${testRuns.length} test run(s):\n${JSON.stringify({
746
778
  totalCount: testRuns.length,
747
779
  testRuns: testRuns.map((tr: any) => ({
748
- key: tr.key, name: tr.name, status: tr.status?.name, folder: tr.folder?.name,
780
+ key: tr.key, name: tr.name, status: tr.status?.id, folder: tr.folder?.name,
749
781
  })),
750
782
  }, null, 2)}`,
751
783
  }],
@@ -824,6 +856,38 @@ export class ZephyrToolHandlers {
824
856
  }
825
857
  }
826
858
 
859
+ private async resolveJiraIssueId(issueKey: string): Promise<number> {
860
+ // The Zephyr API key is NOT valid for the Jira REST API — Jira Cloud requires
861
+ // Basic Auth: base64(email:api_token) via JIRA_USERNAME + JIRA_API_TOKEN env vars.
862
+ const username = process.env.JIRA_USERNAME;
863
+ const apiToken = process.env.JIRA_API_TOKEN;
864
+
865
+ const url = `${this.jiraConfig.jiraBaseUrl}/rest/api/3/issue/${issueKey}?fields=id`;
866
+
867
+ let response;
868
+ if (username && apiToken) {
869
+ // Jira Cloud Basic Auth
870
+ response = await axios.get(url, {
871
+ headers: { 'Accept': 'application/json' },
872
+ auth: { username, password: apiToken },
873
+ });
874
+ } else {
875
+ // Fallback: try Bearer token (works for Data Center with PAT)
876
+ response = await axios.get(url, {
877
+ headers: {
878
+ 'Accept': 'application/json',
879
+ 'Authorization': `Bearer ${process.env.ZEPHYR_API_KEY}`,
880
+ },
881
+ });
882
+ }
883
+
884
+ const id = parseInt(response.data.id, 10);
885
+ if (!id || isNaN(id)) {
886
+ throw new Error(`Could not resolve numeric ID for Jira issue "${issueKey}"`);
887
+ }
888
+ return id;
889
+ }
890
+
827
891
  private formatError(error: unknown): string {
828
892
  if (error instanceof Error && 'response' in error) {
829
893
  const axiosError = error as any;
@@ -91,13 +91,13 @@ export const toolSchemas = [
91
91
  type: 'string',
92
92
  description: 'Test objective (optional)',
93
93
  },
94
- component: {
95
- type: 'string',
96
- description: 'Component name (optional)',
94
+ component_id: {
95
+ type: 'integer',
96
+ description: 'Jira component ID (optional, Cloud only — use the numeric component ID, not the name)',
97
97
  },
98
- owner: {
98
+ owner_id: {
99
99
  type: 'string',
100
- description: 'Test case owner (optional)',
100
+ description: 'Test case owner Jira Account ID (optional, Cloud only — e.g. "5b10a2844c20165700ede21g")',
101
101
  },
102
102
  estimated_time: {
103
103
  type: 'number',
@@ -110,7 +110,7 @@ export const toolSchemas = [
110
110
  },
111
111
  issue_links: {
112
112
  type: 'array',
113
- description: 'Array of issue links (optional) - will be mapped to issueLinks in API',
113
+ description: 'Array of Jira issue keys to link to this test case (e.g. ["PROJ-123", "PROJ-456"]). On Cloud, each key is resolved to a numeric Jira issue ID via the Jira REST API, then linked via POST /testcases/{key}/links/issues — failures are reported as warnings but do not fail the tool call. On Data Center, sent directly in the create payload.',
114
114
  items: { type: 'string' }
115
115
  },
116
116
  custom_fields: {
@@ -191,7 +191,7 @@ export const toolSchemas = [
191
191
  folder_type: {
192
192
  type: 'string',
193
193
  description: 'Type of folder',
194
- enum: ['TEST_CASE', 'TEST_PLAN', 'TEST_RUN'],
194
+ enum: ['TEST_CASE', 'TEST_PLAN', 'TEST_CYCLE'],
195
195
  default: 'TEST_CASE',
196
196
  },
197
197
  },
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' | 'TEST_RUN';
54
+ folder_type?: 'TEST_CASE' | 'TEST_PLAN' | 'TEST_CYCLE';
53
55
  }
54
56
 
55
57
  export interface TestRunArgs {
@@ -104,6 +106,7 @@ export interface ApiEndpoints {
104
106
  export interface JiraConfig {
105
107
  type: JiraType;
106
108
  baseUrl: string;
109
+ jiraBaseUrl: string;
107
110
  authHeaders: Record<string, string>;
108
111
  apiEndpoints: ApiEndpoints;
109
112
  }
package/src/utils.ts CHANGED
@@ -170,6 +170,7 @@ export function createJiraConfig() {
170
170
  return {
171
171
  type,
172
172
  baseUrl,
173
+ jiraBaseUrl,
173
174
  authHeaders,
174
175
  apiEndpoints,
175
176
  };