zephyr-scale-mcp-server 0.4.4 → 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.
@@ -212,14 +212,65 @@ export class ZephyrToolHandlers {
212
212
  const { test_case_key, bdd_content, name } = args;
213
213
  const converted = convertToGherkin(bdd_content);
214
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;
215
220
  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
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
+ }
227
+ try {
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
218
269
  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
270
+ if (!tc) {
271
+ const getResponse = await this.axiosInstance.get(`${this.jiraConfig.apiEndpoints.testcase}/${test_case_key}`);
272
+ tc = getResponse.data;
273
+ }
223
274
  const putPayload = {
224
275
  id: tc.id,
225
276
  key: test_case_key,
@@ -228,7 +279,6 @@ export class ZephyrToolHandlers {
228
279
  priority: tc.priority,
229
280
  project: tc.project,
230
281
  };
231
- // Preserve all optional fields to avoid the API clearing them
232
282
  for (const field of ['objective', 'precondition', 'estimatedTime', 'component', 'owner', 'folder']) {
233
283
  if (tc[field] !== undefined && tc[field] !== null)
234
284
  putPayload[field] = tc[field];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zephyr-scale-mcp-server",
3
- "version": "0.4.4",
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",
@@ -229,18 +229,68 @@ export class ZephyrToolHandlers {
229
229
  const converted = convertToGherkin(bdd_content);
230
230
  const finalText = converted && converted.trim().length > 0 ? converted : bdd_content;
231
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;
232
237
  try {
233
- await this.axiosInstance.post(
234
- `${this.jiraConfig.apiEndpoints.testcase}/${test_case_key}/testscript`,
235
- { type: 'bdd', text: finalText }
236
- );
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
+ }
237
255
 
238
- // 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
239
289
  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
290
+ if (!tc) {
291
+ const getResponse = await this.axiosInstance.get(`${this.jiraConfig.apiEndpoints.testcase}/${test_case_key}`);
292
+ tc = getResponse.data;
293
+ }
244
294
  const putPayload: any = {
245
295
  id: tc.id,
246
296
  key: test_case_key,
@@ -249,7 +299,6 @@ export class ZephyrToolHandlers {
249
299
  priority: tc.priority,
250
300
  project: tc.project,
251
301
  };
252
- // Preserve all optional fields to avoid the API clearing them
253
302
  for (const field of ['objective', 'precondition', 'estimatedTime', 'component', 'owner', 'folder']) {
254
303
  if (tc[field] !== undefined && tc[field] !== null) putPayload[field] = tc[field];
255
304
  }