zephyr-scale-mcp-server 0.2.8 → 0.3.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.
package/build/index.js CHANGED
@@ -14,7 +14,7 @@ class ZephyrServer {
14
14
  const jiraConfig = createJiraConfig();
15
15
  this.server = new Server({
16
16
  name: 'zephyr-server',
17
- version: '0.2.0',
17
+ version: '0.3.0',
18
18
  }, {
19
19
  capabilities: {
20
20
  tools: {},
@@ -63,6 +63,8 @@ class ZephyrServer {
63
63
  return await this.toolHandlers.getTestExecution(args);
64
64
  case 'search_test_cases_by_folder':
65
65
  return await this.toolHandlers.searchTestCasesByFolder(args);
66
+ case 'search_test_runs':
67
+ return await this.toolHandlers.searchTestRuns(args);
66
68
  case 'add_test_cases_to_run':
67
69
  return await this.toolHandlers.addTestCasesToRun(args);
68
70
  default:
@@ -274,7 +274,7 @@ export class ZephyrToolHandlers {
274
274
  }
275
275
  }
276
276
  async createTestRun(args) {
277
- const { project_key, name, test_case_keys, test_plan_key, folder, planned_start_date, planned_end_date, description, owner, environment, custom_fields } = args;
277
+ const { project_key, name, test_case_keys, test_plan_key, folder, planned_start_date, planned_end_date, description, owner, environment, issue_key, issue_links, custom_fields } = args;
278
278
  // Build the basic payload
279
279
  const payload = {
280
280
  projectKey: project_key,
@@ -298,6 +298,10 @@ export class ZephyrToolHandlers {
298
298
  payload.owner = owner;
299
299
  if (environment)
300
300
  payload.environment = environment;
301
+ if (issue_key)
302
+ payload.issueKey = issue_key;
303
+ if (issue_links && issue_links.length > 0)
304
+ payload.issueLinks = issue_links;
301
305
  if (custom_fields)
302
306
  payload.customFields = custom_fields;
303
307
  if (test_plan_key)
@@ -486,6 +490,71 @@ export class ZephyrToolHandlers {
486
490
  throw new McpError(ErrorCode.InternalError, `Failed to search test cases by folder: ${errorMessage}`);
487
491
  }
488
492
  }
493
+ async searchTestRuns(args) {
494
+ const { project_key, folder, max_results = 200, fields } = args;
495
+ // Build query string per Zephyr Scale API docs
496
+ const queryParts = [];
497
+ if (project_key)
498
+ queryParts.push(`projectKey = "${project_key}"`);
499
+ if (folder)
500
+ queryParts.push(`folder = "${folder}"`);
501
+ if (queryParts.length === 0) {
502
+ throw new McpError(ErrorCode.InvalidParams, 'At least one of project_key or folder must be provided.');
503
+ }
504
+ const query = queryParts.join(' AND ');
505
+ const params = { query, maxResults: max_results };
506
+ if (fields)
507
+ params.fields = fields;
508
+ const searchEndpoint = this.jiraConfig.type === 'cloud'
509
+ ? '/testruns/search'
510
+ : '/rest/atm/1.0/testrun/search';
511
+ try {
512
+ const response = await this.axiosInstance.get(searchEndpoint, { params });
513
+ let testRuns = [];
514
+ if (Array.isArray(response.data)) {
515
+ testRuns = response.data;
516
+ }
517
+ else if (response.data.values && Array.isArray(response.data.values)) {
518
+ testRuns = response.data.values;
519
+ }
520
+ else if (response.data.results && Array.isArray(response.data.results)) {
521
+ testRuns = response.data.results;
522
+ }
523
+ return {
524
+ content: [
525
+ {
526
+ type: 'text',
527
+ text: `✅ Found ${testRuns.length} test run(s) matching query "${query}":\n${JSON.stringify({
528
+ query,
529
+ totalCount: testRuns.length,
530
+ testRuns: testRuns.map((tr) => ({
531
+ key: tr.key,
532
+ name: tr.name,
533
+ status: tr.status,
534
+ folder: tr.folder,
535
+ testCaseCount: tr.testCaseCount,
536
+ issueKey: tr.issueKey,
537
+ }))
538
+ }, null, 2)}`,
539
+ },
540
+ ],
541
+ };
542
+ }
543
+ catch (error) {
544
+ let errorMessage = 'Unknown error';
545
+ if (error instanceof Error && 'response' in error) {
546
+ const axiosError = error;
547
+ errorMessage = `Status: ${axiosError.response?.status}, Data: ${JSON.stringify(axiosError.response?.data)}`;
548
+ }
549
+ else if (error instanceof Error) {
550
+ errorMessage = error.message;
551
+ }
552
+ else {
553
+ errorMessage = String(error);
554
+ }
555
+ throw new McpError(ErrorCode.InternalError, `Failed to search test runs: ${errorMessage}`);
556
+ }
557
+ }
489
558
  async addTestCasesToRun(args) {
490
559
  const { test_run_key, test_case_keys } = args;
491
560
  try {
@@ -273,6 +273,15 @@ export const toolSchemas = [
273
273
  type: 'string',
274
274
  description: 'Test environment (optional)',
275
275
  },
276
+ issue_key: {
277
+ type: 'string',
278
+ description: 'Single issue key to link to the test run (optional) - will be mapped to issueKey in API',
279
+ },
280
+ issue_links: {
281
+ type: 'array',
282
+ description: 'Array of issue links (optional) - will be mapped to issueLinks in API',
283
+ items: { type: 'string' },
284
+ },
276
285
  custom_fields: {
277
286
  type: 'object',
278
287
  description: 'Custom fields object (optional)',
@@ -338,6 +347,32 @@ export const toolSchemas = [
338
347
  required: ['project_key', 'folder_path'],
339
348
  },
340
349
  },
350
+ {
351
+ name: 'search_test_runs',
352
+ description: 'Search for test runs using a query. Supports filtering by projectKey and/or folder path.',
353
+ inputSchema: {
354
+ type: 'object',
355
+ properties: {
356
+ project_key: {
357
+ type: 'string',
358
+ description: 'Project key to filter by (e.g., "PROJ"). Can be a single key or omitted if using folder only.',
359
+ },
360
+ folder: {
361
+ type: 'string',
362
+ description: 'Folder path to filter test runs by (e.g., "/MyFolder/SubFolder")',
363
+ },
364
+ max_results: {
365
+ type: 'number',
366
+ description: 'Maximum number of results to return (optional, default 200)',
367
+ default: 200,
368
+ },
369
+ fields: {
370
+ type: 'string',
371
+ description: 'Comma-separated list of fields to include in the response (optional, e.g., "key,name,status,folder"). If not set, all fields are returned.',
372
+ },
373
+ },
374
+ },
375
+ },
341
376
  {
342
377
  name: 'add_test_cases_to_run',
343
378
  description: 'Add test cases to an existing test run',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zephyr-scale-mcp-server",
3
- "version": "0.2.8",
3
+ "version": "0.3.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",
package/src/index.ts CHANGED
@@ -25,7 +25,7 @@ class ZephyrServer {
25
25
  this.server = new Server(
26
26
  {
27
27
  name: 'zephyr-server',
28
- version: '0.2.0',
28
+ version: '0.3.0',
29
29
  },
30
30
  {
31
31
  capabilities: {
@@ -83,6 +83,8 @@ class ZephyrServer {
83
83
  return await this.toolHandlers.getTestExecution(args);
84
84
  case 'search_test_cases_by_folder':
85
85
  return await this.toolHandlers.searchTestCasesByFolder(args as any);
86
+ case 'search_test_runs':
87
+ return await this.toolHandlers.searchTestRuns(args as any);
86
88
  case 'add_test_cases_to_run':
87
89
  return await this.toolHandlers.addTestCasesToRun(args as any);
88
90
  default:
@@ -7,6 +7,7 @@ import {
7
7
  TestRunArgs,
8
8
  SearchTestCasesArgs,
9
9
  AddTestCasesToRunArgs,
10
+ SearchTestRunsArgs,
10
11
  JiraConfig
11
12
  } from './types.js';
12
13
  import { convertToGherkin, customPriorityMapping, priorityMapping } from './utils.js';
@@ -312,6 +313,8 @@ export class ZephyrToolHandlers {
312
313
  description,
313
314
  owner,
314
315
  environment,
316
+ issue_key,
317
+ issue_links,
315
318
  custom_fields
316
319
  } = args;
317
320
 
@@ -333,6 +336,8 @@ export class ZephyrToolHandlers {
333
336
  if (description) payload.description = description;
334
337
  if (owner) payload.owner = owner;
335
338
  if (environment) payload.environment = environment;
339
+ if (issue_key) payload.issueKey = issue_key;
340
+ if (issue_links && issue_links.length > 0) payload.issueLinks = issue_links;
336
341
  if (custom_fields) payload.customFields = custom_fields;
337
342
  if (test_plan_key) payload.testPlanKey = test_plan_key;
338
343
 
@@ -538,6 +543,71 @@ export class ZephyrToolHandlers {
538
543
  }
539
544
  }
540
545
 
546
+ async searchTestRuns(args: SearchTestRunsArgs) {
547
+ const { project_key, folder, max_results = 200, fields } = args;
548
+
549
+ // Build query string per Zephyr Scale API docs
550
+ const queryParts: string[] = [];
551
+ if (project_key) queryParts.push(`projectKey = "${project_key}"`);
552
+ if (folder) queryParts.push(`folder = "${folder}"`);
553
+
554
+ if (queryParts.length === 0) {
555
+ throw new McpError(ErrorCode.InvalidParams, 'At least one of project_key or folder must be provided.');
556
+ }
557
+
558
+ const query = queryParts.join(' AND ');
559
+ const params: Record<string, any> = { query, maxResults: max_results };
560
+ if (fields) params.fields = fields;
561
+
562
+ const searchEndpoint = this.jiraConfig.type === 'cloud'
563
+ ? '/testruns/search'
564
+ : '/rest/atm/1.0/testrun/search';
565
+
566
+ try {
567
+ const response = await this.axiosInstance.get(searchEndpoint, { params });
568
+
569
+ let testRuns: any[] = [];
570
+ if (Array.isArray(response.data)) {
571
+ testRuns = response.data;
572
+ } else if (response.data.values && Array.isArray(response.data.values)) {
573
+ testRuns = response.data.values;
574
+ } else if (response.data.results && Array.isArray(response.data.results)) {
575
+ testRuns = response.data.results;
576
+ }
577
+
578
+ return {
579
+ content: [
580
+ {
581
+ type: 'text',
582
+ text: `✅ Found ${testRuns.length} test run(s) matching query "${query}":\n${JSON.stringify({
583
+ query,
584
+ totalCount: testRuns.length,
585
+ testRuns: testRuns.map((tr: any) => ({
586
+ key: tr.key,
587
+ name: tr.name,
588
+ status: tr.status,
589
+ folder: tr.folder,
590
+ testCaseCount: tr.testCaseCount,
591
+ issueKey: tr.issueKey,
592
+ }))
593
+ }, null, 2)}`,
594
+ },
595
+ ],
596
+ };
597
+ } catch (error) {
598
+ let errorMessage = 'Unknown error';
599
+ if (error instanceof Error && 'response' in error) {
600
+ const axiosError = error as any;
601
+ errorMessage = `Status: ${axiosError.response?.status}, Data: ${JSON.stringify(axiosError.response?.data)}`;
602
+ } else if (error instanceof Error) {
603
+ errorMessage = error.message;
604
+ } else {
605
+ errorMessage = String(error);
606
+ }
607
+ throw new McpError(ErrorCode.InternalError, `Failed to search test runs: ${errorMessage}`);
608
+ }
609
+ }
610
+
541
611
  async addTestCasesToRun(args: AddTestCasesToRunArgs) {
542
612
  const { test_run_key, test_case_keys } = args;
543
613
 
@@ -273,6 +273,15 @@ export const toolSchemas = [
273
273
  type: 'string',
274
274
  description: 'Test environment (optional)',
275
275
  },
276
+ issue_key: {
277
+ type: 'string',
278
+ description: 'Single issue key to link to the test run (optional) - will be mapped to issueKey in API',
279
+ },
280
+ issue_links: {
281
+ type: 'array',
282
+ description: 'Array of issue links (optional) - will be mapped to issueLinks in API',
283
+ items: { type: 'string' },
284
+ },
276
285
  custom_fields: {
277
286
  type: 'object',
278
287
  description: 'Custom fields object (optional)',
@@ -338,6 +347,32 @@ export const toolSchemas = [
338
347
  required: ['project_key', 'folder_path'],
339
348
  },
340
349
  },
350
+ {
351
+ name: 'search_test_runs',
352
+ description: 'Search for test runs using a query. Supports filtering by projectKey and/or folder path.',
353
+ inputSchema: {
354
+ type: 'object',
355
+ properties: {
356
+ project_key: {
357
+ type: 'string',
358
+ description: 'Project key to filter by (e.g., "PROJ"). Can be a single key or omitted if using folder only.',
359
+ },
360
+ folder: {
361
+ type: 'string',
362
+ description: 'Folder path to filter test runs by (e.g., "/MyFolder/SubFolder")',
363
+ },
364
+ max_results: {
365
+ type: 'number',
366
+ description: 'Maximum number of results to return (optional, default 200)',
367
+ default: 200,
368
+ },
369
+ fields: {
370
+ type: 'string',
371
+ description: 'Comma-separated list of fields to include in the response (optional, e.g., "key,name,status,folder"). If not set, all fields are returned.',
372
+ },
373
+ },
374
+ },
375
+ },
341
376
  {
342
377
  name: 'add_test_cases_to_run',
343
378
  description: 'Add test cases to an existing test run',
package/src/types.ts CHANGED
@@ -63,6 +63,8 @@ export interface TestRunArgs {
63
63
  description?: string;
64
64
  owner?: string;
65
65
  environment?: string;
66
+ issue_key?: string;
67
+ issue_links?: string[];
66
68
  custom_fields?: Record<string, any>;
67
69
  }
68
70
 
@@ -77,6 +79,13 @@ export interface AddTestCasesToRunArgs {
77
79
  test_case_keys: string[];
78
80
  }
79
81
 
82
+ export interface SearchTestRunsArgs {
83
+ project_key?: string;
84
+ folder?: string;
85
+ max_results?: number;
86
+ fields?: string;
87
+ }
88
+
80
89
  export type JiraType = 'cloud' | 'datacenter';
81
90
 
82
91
  export interface ApiEndpoints {