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.
- package/build/tool-handlers.js +68 -8
- package/package.json +2 -2
- package/src/tool-handlers.ts +73 -11
package/build/tool-handlers.js
CHANGED
|
@@ -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(
|
|
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.
|
|
217
|
-
|
|
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
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
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.
|
|
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": "
|
|
8
|
+
"zephyr-scale-mcp": "build/index.js"
|
|
9
9
|
},
|
|
10
10
|
"files": [
|
|
11
11
|
"build/",
|
package/src/tool-handlers.ts
CHANGED
|
@@ -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(
|
|
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.
|
|
234
|
-
|
|
235
|
-
|
|
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
|
-
|
|
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
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
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
|
}
|