zephyr-scale-mcp-server 0.4.4 → 0.4.6

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.
@@ -12,8 +12,18 @@ export class ZephyrToolHandlers {
12
12
  const { test_case_key } = args;
13
13
  try {
14
14
  const response = await this.axiosInstance.get(`${this.jiraConfig.apiEndpoints.testcase}/${test_case_key}`);
15
+ const testCase = response.data;
16
+ // Fetch the test script content and embed it inline.
17
+ // The base GET /testcases/{key} only returns a link to the script, not the content.
18
+ try {
19
+ const scriptResponse = await this.axiosInstance.get(`${this.jiraConfig.apiEndpoints.testcase}/${test_case_key}/testscript`);
20
+ testCase.testScript = scriptResponse.data;
21
+ }
22
+ catch {
23
+ // Script fetch failed (e.g. no script yet) — leave testScript as the link object
24
+ }
15
25
  return {
16
- content: [{ type: 'text', text: JSON.stringify(response.data, null, 2) }],
26
+ content: [{ type: 'text', text: JSON.stringify(testCase, null, 2) }],
17
27
  };
18
28
  }
19
29
  catch (error) {
@@ -212,14 +222,65 @@ export class ZephyrToolHandlers {
212
222
  const { test_case_key, bdd_content, name } = args;
213
223
  const converted = convertToGherkin(bdd_content);
214
224
  const finalText = converted && converted.trim().length > 0 ? converted : bdd_content;
225
+ // Fetch the test case first to get the numeric ID.
226
+ // After a DC→Cloud migration, the project key prefix in the test case key (e.g. "CNIDS")
227
+ // may no longer match an active Cloud project, causing key-based write endpoints to return 404.
228
+ // Falling back to the numeric ID bypasses that project-key validation.
229
+ let tc = null;
215
230
  try {
216
- await this.axiosInstance.post(`${this.jiraConfig.apiEndpoints.testcase}/${test_case_key}/testscript`, { type: 'bdd', text: finalText });
217
- // Only fetch and PUT metadata when the caller also wants to rename the test case
231
+ const getResponse = await this.axiosInstance.get(`${this.jiraConfig.apiEndpoints.testcase}/${test_case_key}`);
232
+ tc = getResponse.data;
233
+ }
234
+ catch {
235
+ // GET failed — proceed with key only; write will surface the real error
236
+ }
237
+ try {
238
+ // Primary path: POST to dedicated testscript endpoint using the key
239
+ let scriptUpdateError = null;
240
+ try {
241
+ await this.axiosInstance.post(`${this.jiraConfig.apiEndpoints.testcase}/${test_case_key}/testscript`, { type: 'bdd', text: finalText });
242
+ }
243
+ catch (err) {
244
+ scriptUpdateError = err;
245
+ }
246
+ // Fallback: if testscript POST failed (e.g. migrated project key), try PUT on the full
247
+ // test case record with the testScript field embedded — some Cloud instances accept this
248
+ // for migrated test cases where the project is deactivated.
249
+ if (scriptUpdateError) {
250
+ if (!tc) {
251
+ throw scriptUpdateError; // no test case data to build PUT payload, surface original error
252
+ }
253
+ const putPayload = {
254
+ id: tc.id,
255
+ key: test_case_key,
256
+ name: (typeof name === 'string' && name.trim().length > 0) ? name : tc.name,
257
+ status: tc.status,
258
+ priority: tc.priority,
259
+ project: tc.project,
260
+ testScript: { type: 'bdd', text: finalText },
261
+ };
262
+ for (const field of ['objective', 'precondition', 'estimatedTime', 'component', 'owner', 'folder']) {
263
+ if (tc[field] !== undefined && tc[field] !== null)
264
+ putPayload[field] = tc[field];
265
+ }
266
+ if (Array.isArray(tc.labels) && tc.labels.length > 0)
267
+ putPayload.labels = tc.labels;
268
+ if (tc.customFields && Object.keys(tc.customFields).length > 0)
269
+ putPayload.customFields = tc.customFields;
270
+ await this.axiosInstance.put(`${this.jiraConfig.apiEndpoints.testcase}/${test_case_key}`, putPayload);
271
+ return {
272
+ content: [{
273
+ type: 'text',
274
+ text: `✅ Updated ${test_case_key} with BDD content successfully (Cloud v2, via PUT fallback for migrated project)`,
275
+ }],
276
+ };
277
+ }
278
+ // Primary path succeeded — optionally rename
218
279
  if (typeof name === 'string' && name.trim().length > 0) {
219
- const getResponse = await this.axiosInstance.get(`${this.jiraConfig.apiEndpoints.testcase}/${test_case_key}`);
220
- const tc = getResponse.data;
221
- // UpdateTestCaseInput requires: id, key, name, priority, project, status
222
- // All optional fields must also be re-sent or the API will clear them
280
+ if (!tc) {
281
+ const getResponse = await this.axiosInstance.get(`${this.jiraConfig.apiEndpoints.testcase}/${test_case_key}`);
282
+ tc = getResponse.data;
283
+ }
223
284
  const putPayload = {
224
285
  id: tc.id,
225
286
  key: test_case_key,
@@ -228,7 +289,6 @@ export class ZephyrToolHandlers {
228
289
  priority: tc.priority,
229
290
  project: tc.project,
230
291
  };
231
- // Preserve all optional fields to avoid the API clearing them
232
292
  for (const field of ['objective', 'precondition', 'estimatedTime', 'component', 'owner', 'folder']) {
233
293
  if (tc[field] !== undefined && tc[field] !== null)
234
294
  putPayload[field] = tc[field];
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "zephyr-scale-mcp-server",
3
- "version": "0.4.4",
3
+ "version": "0.4.6",
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",
7
7
  "bin": {
8
- "zephyr-scale-mcp": "./build/index.js"
8
+ "zephyr-scale-mcp": "build/index.js"
9
9
  },
10
10
  "files": [
11
11
  "build/",
@@ -24,8 +24,21 @@ export class ZephyrToolHandlers {
24
24
  const { test_case_key } = args;
25
25
  try {
26
26
  const response = await this.axiosInstance.get(`${this.jiraConfig.apiEndpoints.testcase}/${test_case_key}`);
27
+ const testCase = response.data;
28
+
29
+ // Fetch the test script content and embed it inline.
30
+ // The base GET /testcases/{key} only returns a link to the script, not the content.
31
+ try {
32
+ const scriptResponse = await this.axiosInstance.get(
33
+ `${this.jiraConfig.apiEndpoints.testcase}/${test_case_key}/testscript`
34
+ );
35
+ testCase.testScript = scriptResponse.data;
36
+ } catch {
37
+ // Script fetch failed (e.g. no script yet) — leave testScript as the link object
38
+ }
39
+
27
40
  return {
28
- content: [{ type: 'text', text: JSON.stringify(response.data, null, 2) }],
41
+ content: [{ type: 'text', text: JSON.stringify(testCase, null, 2) }],
29
42
  };
30
43
  } catch (error) {
31
44
  throw new McpError(ErrorCode.InternalError, `Failed to get test case: ${this.formatError(error)}`);
@@ -229,18 +242,68 @@ export class ZephyrToolHandlers {
229
242
  const converted = convertToGherkin(bdd_content);
230
243
  const finalText = converted && converted.trim().length > 0 ? converted : bdd_content;
231
244
 
245
+ // Fetch the test case first to get the numeric ID.
246
+ // After a DC→Cloud migration, the project key prefix in the test case key (e.g. "CNIDS")
247
+ // may no longer match an active Cloud project, causing key-based write endpoints to return 404.
248
+ // Falling back to the numeric ID bypasses that project-key validation.
249
+ let tc: any = null;
232
250
  try {
233
- await this.axiosInstance.post(
234
- `${this.jiraConfig.apiEndpoints.testcase}/${test_case_key}/testscript`,
235
- { type: 'bdd', text: finalText }
236
- );
251
+ const getResponse = await this.axiosInstance.get(`${this.jiraConfig.apiEndpoints.testcase}/${test_case_key}`);
252
+ tc = getResponse.data;
253
+ } catch {
254
+ // GET failed — proceed with key only; write will surface the real error
255
+ }
237
256
 
238
- // Only fetch and PUT metadata when the caller also wants to rename the test case
257
+ try {
258
+ // Primary path: POST to dedicated testscript endpoint using the key
259
+ let scriptUpdateError: any = null;
260
+ try {
261
+ await this.axiosInstance.post(
262
+ `${this.jiraConfig.apiEndpoints.testcase}/${test_case_key}/testscript`,
263
+ { type: 'bdd', text: finalText }
264
+ );
265
+ } catch (err: any) {
266
+ scriptUpdateError = err;
267
+ }
268
+
269
+ // Fallback: if testscript POST failed (e.g. migrated project key), try PUT on the full
270
+ // test case record with the testScript field embedded — some Cloud instances accept this
271
+ // for migrated test cases where the project is deactivated.
272
+ if (scriptUpdateError) {
273
+ if (!tc) {
274
+ throw scriptUpdateError; // no test case data to build PUT payload, surface original error
275
+ }
276
+ const putPayload: any = {
277
+ id: tc.id,
278
+ key: test_case_key,
279
+ name: (typeof name === 'string' && name.trim().length > 0) ? name : tc.name,
280
+ status: tc.status,
281
+ priority: tc.priority,
282
+ project: tc.project,
283
+ testScript: { type: 'bdd', text: finalText },
284
+ };
285
+ for (const field of ['objective', 'precondition', 'estimatedTime', 'component', 'owner', 'folder']) {
286
+ if (tc[field] !== undefined && tc[field] !== null) putPayload[field] = tc[field];
287
+ }
288
+ if (Array.isArray(tc.labels) && tc.labels.length > 0) putPayload.labels = tc.labels;
289
+ if (tc.customFields && Object.keys(tc.customFields).length > 0) putPayload.customFields = tc.customFields;
290
+
291
+ await this.axiosInstance.put(`${this.jiraConfig.apiEndpoints.testcase}/${test_case_key}`, putPayload);
292
+
293
+ return {
294
+ content: [{
295
+ type: 'text',
296
+ text: `✅ Updated ${test_case_key} with BDD content successfully (Cloud v2, via PUT fallback for migrated project)`,
297
+ }],
298
+ };
299
+ }
300
+
301
+ // Primary path succeeded — optionally rename
239
302
  if (typeof name === 'string' && name.trim().length > 0) {
240
- const getResponse = await this.axiosInstance.get(`${this.jiraConfig.apiEndpoints.testcase}/${test_case_key}`);
241
- const tc = getResponse.data;
242
- // UpdateTestCaseInput requires: id, key, name, priority, project, status
243
- // All optional fields must also be re-sent or the API will clear them
303
+ if (!tc) {
304
+ const getResponse = await this.axiosInstance.get(`${this.jiraConfig.apiEndpoints.testcase}/${test_case_key}`);
305
+ tc = getResponse.data;
306
+ }
244
307
  const putPayload: any = {
245
308
  id: tc.id,
246
309
  key: test_case_key,
@@ -249,7 +312,6 @@ export class ZephyrToolHandlers {
249
312
  priority: tc.priority,
250
313
  project: tc.project,
251
314
  };
252
- // Preserve all optional fields to avoid the API clearing them
253
315
  for (const field of ['objective', 'precondition', 'estimatedTime', 'component', 'owner', 'folder']) {
254
316
  if (tc[field] !== undefined && tc[field] !== null) putPayload[field] = tc[field];
255
317
  }