zephyr-scale-mcp-server 0.4.6 → 0.5.0

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.
@@ -250,6 +250,18 @@ export class ZephyrToolHandlers {
250
250
  if (!tc) {
251
251
  throw scriptUpdateError; // no test case data to build PUT payload, surface original error
252
252
  }
253
+ // Fetch the existing test script to get its ID — required for the PUT to update in place
254
+ let existingScriptId;
255
+ try {
256
+ const scriptRes = await this.axiosInstance.get(`${this.jiraConfig.apiEndpoints.testcase}/${test_case_key}/testscript`);
257
+ existingScriptId = scriptRes.data?.id;
258
+ }
259
+ catch {
260
+ // no existing script — proceed without id
261
+ }
262
+ const testScriptPayload = { type: 'bdd', text: finalText };
263
+ if (existingScriptId)
264
+ testScriptPayload.id = existingScriptId;
253
265
  const putPayload = {
254
266
  id: tc.id,
255
267
  key: test_case_key,
@@ -257,7 +269,7 @@ export class ZephyrToolHandlers {
257
269
  status: tc.status,
258
270
  priority: tc.priority,
259
271
  project: tc.project,
260
- testScript: { type: 'bdd', text: finalText },
272
+ testScript: testScriptPayload,
261
273
  };
262
274
  for (const field of ['objective', 'precondition', 'estimatedTime', 'component', 'owner', 'folder']) {
263
275
  if (tc[field] !== undefined && tc[field] !== null)
@@ -268,12 +280,15 @@ export class ZephyrToolHandlers {
268
280
  if (tc.customFields && Object.keys(tc.customFields).length > 0)
269
281
  putPayload.customFields = tc.customFields;
270
282
  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
- };
283
+ // Note: the Zephyr Scale Cloud API's UpdateTestCaseInput does not include a testScript field,
284
+ // so the testScript embedded in the PUT payload is silently ignored.
285
+ // The only endpoint that updates script content (POST /testscript) requires the project to be active.
286
+ // For test cases migrated from Data Center to Cloud, the original project key may be deactivated,
287
+ // blocking script updates at the API level. Contact your Zephyr Scale admin to re-enable the project.
288
+ throw new McpError(ErrorCode.InternalError, `Cannot update test script for ${test_case_key}: the project "${test_case_key.replace(/-T\d+$/, '')}" is deactivated in Zephyr Scale Cloud. ` +
289
+ `This test case was likely migrated from Data Center and retains its original project key prefix. ` +
290
+ `POST /testcases/{key}/testscript requires an active project. ` +
291
+ `Please ask your Zephyr Scale admin to re-enable the project, or recreate this test case under an active Cloud project.`);
277
292
  }
278
293
  // Primary path succeeded — optionally rename
279
294
  if (typeof name === 'string' && name.trim().length > 0) {
@@ -793,9 +808,9 @@ export class ZephyrToolHandlers {
793
808
  }
794
809
  }
795
810
  async searchTestRuns(args) {
796
- const { project_key, folder, max_results = 200, fields } = args;
797
- if (!project_key && !folder) {
798
- throw new McpError(ErrorCode.InvalidParams, 'At least one of project_key or folder must be provided.');
811
+ const { project_key, folder, folder_id, max_results = 200, fields } = args;
812
+ if (!project_key && !folder && !folder_id) {
813
+ throw new McpError(ErrorCode.InvalidParams, 'At least one of project_key, folder, or folder_id must be provided.');
799
814
  }
800
815
  if (this.jiraConfig.type === 'cloud') {
801
816
  try {
@@ -803,7 +818,11 @@ export class ZephyrToolHandlers {
803
818
  const params = { maxResults: max_results };
804
819
  if (project_key)
805
820
  params.projectKey = project_key;
806
- if (folder && project_key) {
821
+ // folder_id takes precedence over folder path
822
+ if (folder_id) {
823
+ params.folderId = folder_id;
824
+ }
825
+ else if (folder && project_key) {
807
826
  const folderId = await resolveFolderIdByPath(this.axiosInstance, project_key, folder, 'TEST_CYCLE');
808
827
  if (folderId !== null)
809
828
  params.folderId = folderId;
@@ -351,7 +351,7 @@ export const toolSchemas = [
351
351
  },
352
352
  {
353
353
  name: 'search_test_runs',
354
- description: 'Search for test runs using a query. Supports filtering by projectKey and/or folder path.',
354
+ description: 'Search for test runs using a query. Supports filtering by projectKey and/or folder path. On Cloud, only returns cycles in the exact folder (not sub-folders). Use folder_id for direct numeric ID lookup (skips path resolution).',
355
355
  inputSchema: {
356
356
  type: 'object',
357
357
  properties: {
@@ -361,7 +361,11 @@ export const toolSchemas = [
361
361
  },
362
362
  folder: {
363
363
  type: 'string',
364
- description: 'Folder path to filter test runs by (e.g., "/MyFolder/SubFolder")',
364
+ description: 'Folder path to filter test runs by (e.g., "/MyFolder/SubFolder"). Resolved to a numeric folderId on Cloud.',
365
+ },
366
+ folder_id: {
367
+ type: 'number',
368
+ description: 'Numeric folder ID to filter test runs by (optional). If provided, takes precedence over folder path and skips path resolution. Use this when you already know the folder ID.',
365
369
  },
366
370
  max_results: {
367
371
  type: 'number',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zephyr-scale-mcp-server",
3
- "version": "0.4.6",
3
+ "version": "0.5.0",
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",
@@ -273,6 +273,21 @@ export class ZephyrToolHandlers {
273
273
  if (!tc) {
274
274
  throw scriptUpdateError; // no test case data to build PUT payload, surface original error
275
275
  }
276
+
277
+ // Fetch the existing test script to get its ID — required for the PUT to update in place
278
+ let existingScriptId: number | undefined;
279
+ try {
280
+ const scriptRes = await this.axiosInstance.get(
281
+ `${this.jiraConfig.apiEndpoints.testcase}/${test_case_key}/testscript`
282
+ );
283
+ existingScriptId = scriptRes.data?.id;
284
+ } catch {
285
+ // no existing script — proceed without id
286
+ }
287
+
288
+ const testScriptPayload: any = { type: 'bdd', text: finalText };
289
+ if (existingScriptId) testScriptPayload.id = existingScriptId;
290
+
276
291
  const putPayload: any = {
277
292
  id: tc.id,
278
293
  key: test_case_key,
@@ -280,7 +295,7 @@ export class ZephyrToolHandlers {
280
295
  status: tc.status,
281
296
  priority: tc.priority,
282
297
  project: tc.project,
283
- testScript: { type: 'bdd', text: finalText },
298
+ testScript: testScriptPayload,
284
299
  };
285
300
  for (const field of ['objective', 'precondition', 'estimatedTime', 'component', 'owner', 'folder']) {
286
301
  if (tc[field] !== undefined && tc[field] !== null) putPayload[field] = tc[field];
@@ -290,12 +305,18 @@ export class ZephyrToolHandlers {
290
305
 
291
306
  await this.axiosInstance.put(`${this.jiraConfig.apiEndpoints.testcase}/${test_case_key}`, putPayload);
292
307
 
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
- };
308
+ // Note: the Zephyr Scale Cloud API's UpdateTestCaseInput does not include a testScript field,
309
+ // so the testScript embedded in the PUT payload is silently ignored.
310
+ // The only endpoint that updates script content (POST /testscript) requires the project to be active.
311
+ // For test cases migrated from Data Center to Cloud, the original project key may be deactivated,
312
+ // blocking script updates at the API level. Contact your Zephyr Scale admin to re-enable the project.
313
+ throw new McpError(
314
+ ErrorCode.InternalError,
315
+ `Cannot update test script for ${test_case_key}: the project "${test_case_key.replace(/-T\d+$/, '')}" is deactivated in Zephyr Scale Cloud. ` +
316
+ `This test case was likely migrated from Data Center and retains its original project key prefix. ` +
317
+ `POST /testcases/{key}/testscript requires an active project. ` +
318
+ `Please ask your Zephyr Scale admin to re-enable the project, or recreate this test case under an active Cloud project.`
319
+ );
299
320
  }
300
321
 
301
322
  // Primary path succeeded — optionally rename
@@ -858,10 +879,10 @@ export class ZephyrToolHandlers {
858
879
  }
859
880
 
860
881
  async searchTestRuns(args: SearchTestRunsArgs) {
861
- const { project_key, folder, max_results = 200, fields } = args;
882
+ const { project_key, folder, folder_id, max_results = 200, fields } = args;
862
883
 
863
- if (!project_key && !folder) {
864
- throw new McpError(ErrorCode.InvalidParams, 'At least one of project_key or folder must be provided.');
884
+ if (!project_key && !folder && !folder_id) {
885
+ throw new McpError(ErrorCode.InvalidParams, 'At least one of project_key, folder, or folder_id must be provided.');
865
886
  }
866
887
 
867
888
  if (this.jiraConfig.type === 'cloud') {
@@ -870,7 +891,10 @@ export class ZephyrToolHandlers {
870
891
  const params: Record<string, any> = { maxResults: max_results };
871
892
  if (project_key) params.projectKey = project_key;
872
893
 
873
- if (folder && project_key) {
894
+ // folder_id takes precedence over folder path
895
+ if (folder_id) {
896
+ params.folderId = folder_id;
897
+ } else if (folder && project_key) {
874
898
  const folderId = await resolveFolderIdByPath(
875
899
  this.axiosInstance, project_key, folder, 'TEST_CYCLE'
876
900
  );
@@ -351,7 +351,7 @@ export const toolSchemas = [
351
351
  },
352
352
  {
353
353
  name: 'search_test_runs',
354
- description: 'Search for test runs using a query. Supports filtering by projectKey and/or folder path.',
354
+ description: 'Search for test runs using a query. Supports filtering by projectKey and/or folder path. On Cloud, only returns cycles in the exact folder (not sub-folders). Use folder_id for direct numeric ID lookup (skips path resolution).',
355
355
  inputSchema: {
356
356
  type: 'object',
357
357
  properties: {
@@ -361,7 +361,11 @@ export const toolSchemas = [
361
361
  },
362
362
  folder: {
363
363
  type: 'string',
364
- description: 'Folder path to filter test runs by (e.g., "/MyFolder/SubFolder")',
364
+ description: 'Folder path to filter test runs by (e.g., "/MyFolder/SubFolder"). Resolved to a numeric folderId on Cloud.',
365
+ },
366
+ folder_id: {
367
+ type: 'number',
368
+ description: 'Numeric folder ID to filter test runs by (optional). If provided, takes precedence over folder path and skips path resolution. Use this when you already know the folder ID.',
365
369
  },
366
370
  max_results: {
367
371
  type: 'number',
package/src/types.ts CHANGED
@@ -91,6 +91,7 @@ export interface GetTestExecutionArgs {
91
91
  export interface SearchTestRunsArgs {
92
92
  project_key?: string;
93
93
  folder?: string;
94
+ folder_id?: number;
94
95
  max_results?: number;
95
96
  fields?: string;
96
97
  }