zephyr-scale-mcp-server 0.2.9 → 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.9',
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:
@@ -490,6 +490,71 @@ export class ZephyrToolHandlers {
490
490
  throw new McpError(ErrorCode.InternalError, `Failed to search test cases by folder: ${errorMessage}`);
491
491
  }
492
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
+ }
493
558
  async addTestCasesToRun(args) {
494
559
  const { test_run_key, test_case_keys } = args;
495
560
  try {
@@ -347,6 +347,32 @@ export const toolSchemas = [
347
347
  required: ['project_key', 'folder_path'],
348
348
  },
349
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
+ },
350
376
  {
351
377
  name: 'add_test_cases_to_run',
352
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.9",
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.9',
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';
@@ -542,6 +543,71 @@ export class ZephyrToolHandlers {
542
543
  }
543
544
  }
544
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
+
545
611
  async addTestCasesToRun(args: AddTestCasesToRunArgs) {
546
612
  const { test_run_key, test_case_keys } = args;
547
613
 
@@ -347,6 +347,32 @@ export const toolSchemas = [
347
347
  required: ['project_key', 'folder_path'],
348
348
  },
349
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
+ },
350
376
  {
351
377
  name: 'add_test_cases_to_run',
352
378
  description: 'Add test cases to an existing test run',
package/src/types.ts CHANGED
@@ -79,6 +79,13 @@ export interface AddTestCasesToRunArgs {
79
79
  test_case_keys: string[];
80
80
  }
81
81
 
82
+ export interface SearchTestRunsArgs {
83
+ project_key?: string;
84
+ folder?: string;
85
+ max_results?: number;
86
+ fields?: string;
87
+ }
88
+
82
89
  export type JiraType = 'cloud' | 'datacenter';
83
90
 
84
91
  export interface ApiEndpoints {