@uarlouski/testrail-mcp-server 1.3.1 → 1.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.
package/README.md CHANGED
@@ -1,41 +1,103 @@
1
- # TestRail MCP Server
1
+ <p align="center">
2
+ <h1 align="center">TestRail MCP Server</h1>
3
+ <p align="center">
4
+ A Model Context Protocol (MCP) server that connects AI assistants to TestRail test management — search, create, update, and execute test cases using natural language.
5
+ </p>
6
+ </p>
7
+
8
+ <p align="center">
9
+ <a href="https://www.npmjs.com/package/@uarlouski/testrail-mcp-server"><img src="https://badge.fury.io/js/@uarlouski%2Ftestrail-mcp-server.svg" alt="npm version"></a>
10
+ <a href="https://www.npmjs.com/package/@uarlouski/testrail-mcp-server"><img src="https://img.shields.io/npm/dm/@uarlouski/testrail-mcp-server.svg" alt="npm downloads"></a>
11
+ <a href="https://github.com/uarlouski/testrail-mcp-server/actions/workflows/ci.yml"><img src="https://github.com/uarlouski/testrail-mcp-server/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
12
+ <a href="https://opensource.org/licenses/Apache-2.0"><img src="https://img.shields.io/badge/License-Apache_2.0-blue.svg" alt="License"></a>
13
+ <a href="https://www.typescriptlang.org/"><img src="https://img.shields.io/badge/TypeScript-5.9-blue.svg" alt="TypeScript"></a>
14
+ <a href="https://github.com/uarlouski/testrail-mcp-server"><img src="https://img.shields.io/github/stars/uarlouski/testrail-mcp-server.svg?style=social&label=Star" alt="GitHub stars"></a>
15
+ </p>
2
16
 
3
- [![npm version](https://badge.fury.io/js/@uarlouski%2Ftestrail-mcp-server.svg)](https://www.npmjs.com/package/@uarlouski/testrail-mcp-server)
4
- [![npm downloads](https://img.shields.io/npm/dm/@uarlouski/testrail-mcp-server.svg)](https://www.npmjs.com/package/@uarlouski/testrail-mcp-server)
5
- [![CI](https://github.com/uarlouski/testrail-mcp-server/actions/workflows/ci.yml/badge.svg)](https://github.com/uarlouski/testrail-mcp-server/actions/workflows/ci.yml)
6
- [![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
7
- [![TypeScript](https://img.shields.io/badge/TypeScript-5.9.3-blue.svg)](https://www.typescriptlang.org/)
8
- [![GitHub stars](https://img.shields.io/github/stars/uarlouski/testrail-mcp-server.svg?style=social&label=Star)](https://github.com/uarlouski/testrail-mcp-server)
17
+ ---
18
+
19
+ ## Why TestRail MCP Server?
20
+
21
+ Managing test cases manually is tedious. With the TestRail MCP Server, your AI assistant (Claude, Cursor, Windsurf, or any MCP-compliant client) talks directly to TestRail. Ask it to find test cases, create new ones, kick off test runs, or report results — all through natural conversation.
22
+
23
+ **No context switching. No copy-pasting. Just ask.**
24
+
25
+ ## ✨ Features
9
26
 
27
+ | Category | Capabilities |
28
+ |----------|-------------|
29
+ | **🔍 Discovery** | Browse projects, suites, and sections to map your test organization |
30
+ | **📋 Case Management** | Fetch, create, update, and bulk-edit test cases with full custom field support |
31
+ | **▶️ Test Execution** | Create runs, record results, attach files, and track test statuses |
32
+ | **🧠 Context-Aware** | Exposes templates, field definitions, priorities, and case types so LLMs generate valid, well-structured test cases |
10
33
 
11
- A powerful **Model Context Protocol (MCP)** server for seamless integration between **Large Language Models (LLMs)** and **TestRail** test case management.
34
+ ## 🚀 Quick Start
12
35
 
13
- This MCP server enables AI assistants like **Claude**, **Cursor**, **Windsurf**, and other MCP-compliant clients to interact directly with your TestRail instance. Streamline your QA workflows by allowing your AI to search, retrieve, create, and update test cases, plans, and runs.
36
+ ### 1. Get Your TestRail API Key
14
37
 
15
- ## 🚀 Features
38
+ Navigate to **My Settings → API Keys** in your TestRail instance and generate a new key.
16
39
 
17
- - **AI-Powered Test Management**: Bridge the gap between AI and your QA process.
18
- - **Smart Discovery**: List projects, suites, and sections to help LLMs understand your test organization.
19
- - **Comprehensive Case Operations**: Fetch, create, and update test cases with full support for custom fields.
20
- - **Context-Aware**: Designed to provide LLMs with the right context (templates, field options) for accurate test generation.
40
+ ### 2. Configure Your MCP Client
21
41
 
22
- ## ⚙️ Usage
42
+ Add the server to your MCP client configuration. Below are examples for popular clients.
43
+
44
+ <details>
45
+ <summary><strong>Claude Desktop</strong></summary>
46
+
47
+ Edit your `claude_desktop_config.json`:
48
+
49
+ ```json
50
+ {
51
+ "mcpServers": {
52
+ "testrail": {
53
+ "command": "npx",
54
+ "args": ["-y", "@uarlouski/testrail-mcp-server@latest"],
55
+ "env": {
56
+ "TESTRAIL_INSTANCE_URL": "https://your-instance.testrail.io",
57
+ "TESTRAIL_USERNAME": "your@email.com",
58
+ "TESTRAIL_API_KEY": "your-api-key"
59
+ }
60
+ }
61
+ }
62
+ }
63
+ ```
64
+
65
+ </details>
66
+
67
+ <details>
68
+ <summary><strong>Cursor</strong></summary>
69
+
70
+ Open **Settings → MCP** and add a new server:
71
+
72
+ ```json
73
+ {
74
+ "mcpServers": {
75
+ "testrail": {
76
+ "command": "npx",
77
+ "args": ["-y", "@uarlouski/testrail-mcp-server@latest"],
78
+ "env": {
79
+ "TESTRAIL_INSTANCE_URL": "https://your-instance.testrail.io",
80
+ "TESTRAIL_USERNAME": "your@email.com",
81
+ "TESTRAIL_API_KEY": "your-api-key"
82
+ }
83
+ }
84
+ }
85
+ }
86
+ ```
23
87
 
24
- Configure the server in your MCP client settings (e.g., `claude_desktop_config.json` or Cursor settings).
88
+ </details>
25
89
 
26
- ### Claude Desktop / General MCP Clients
90
+ <details>
91
+ <summary><strong>Windsurf</strong></summary>
27
92
 
28
- Add the following to your configuration file:
93
+ Add to your Windsurf MCP configuration:
29
94
 
30
95
  ```json
31
96
  {
32
97
  "mcpServers": {
33
98
  "testrail": {
34
99
  "command": "npx",
35
- "args": [
36
- "-y",
37
- "@uarlouski/testrail-mcp-server@latest"
38
- ],
100
+ "args": ["-y", "@uarlouski/testrail-mcp-server@latest"],
39
101
  "env": {
40
102
  "TESTRAIL_INSTANCE_URL": "https://your-instance.testrail.io",
41
103
  "TESTRAIL_USERNAME": "your@email.com",
@@ -46,42 +108,110 @@ Add the following to your configuration file:
46
108
  }
47
109
  ```
48
110
 
49
- ### Environment Variables
111
+ </details>
112
+
113
+ <details>
114
+ <summary><strong>Other MCP Clients</strong></summary>
115
+
116
+ Any MCP-compliant client can use this server. The configuration pattern is the same — point it at the `npx` command with the required environment variables.
117
+
118
+ </details>
119
+
120
+ ### 3. Start Using It
121
+
122
+ Once configured, ask your AI assistant things like:
123
+
124
+ - *"List all projects in TestRail"*
125
+ - *"Show me the test cases in suite 1 of project 3"*
126
+ - *"Create a test case for login validation with steps"*
127
+ - *"Start a new test run with cases from section 5"*
128
+ - *"Mark test 42 as passed with a comment"*
129
+
130
+ ## ⚙️ Configuration
50
131
 
51
- | Variable | Description | Required |
52
- |----------|-------------|:--------:|
53
- | `TESTRAIL_INSTANCE_URL` | The URL of your TestRail instance (e.g., `https://example.testrail.io`) | Yes |
54
- | `TESTRAIL_USERNAME` | Your TestRail email address | Yes |
55
- | `TESTRAIL_API_KEY` | Your TestRail API key (generate in My Settings > API Keys) | Yes |
132
+ | Environment Variable | Description | Required |
133
+ |---------------------|-------------|:--------:|
134
+ | `TESTRAIL_INSTANCE_URL` | Your TestRail instance URL (e.g., `https://example.testrail.io`) | ✅ |
135
+ | `TESTRAIL_USERNAME` | Your TestRail email address | ✅ |
136
+ | `TESTRAIL_API_KEY` | Your TestRail API key ([how to get one](https://support.testrail.com/hc/en-us/articles/7077039051412-Accessing-the-TestRail-API)) | ✅ |
56
137
 
57
138
  ## 🛠️ Available Tools
58
139
 
59
- This MCP server exposes the following tools to the LLM:
140
+ ### Discovery
60
141
 
61
142
  | Tool | Description |
62
143
  |------|-------------|
63
- | `get_projects` | **Discovery**: List all available projects in the TestRail instance to establish context. |
64
- | `get_suites` | **Discovery**: Retrieve test suites for a given project (if not in single-suite mode). |
65
- | `get_sections` | **Structure**: navigate the folder/section hierarchy of a test suite. |
66
- | `get_cases` | **Search**: Query test cases with filtering (priority, template, type, etc.) and pagination. |
67
- | `get_case` | **Detail**: Fetch comprehensive details of a specific test case, including steps and custom fields. |
68
- | `create_case` | **Authoring**: Create new test cases. *Tip: The model should check templates and fields first to ensure validity.* |
69
- | `update_case` | **Maintenance**: Modify existing test case fields, title, or steps. |
70
- | `update_cases` | **Bulk Edit**: Update multiple cases simultaneously for efficient refactoring. |
71
- | `add_run` | **Test Execution**: Create a new test run in TestRail. |
72
- | `get_tests` | **Test Execution**: Get tests for a test run, optionally filtered by status. |
73
- | `add_results` | **Test Execution**: Add one or more test results to a test run. |
74
- | `add_attachment_to_run` | **Test Execution**: Add an attachment (file or zipped folder) to a test run. |
75
- | `get_statuses` | **Metadata**: Get all available test statuses (e.g. Passed, Failed, Blocked). |
76
- | `get_case_fields` | **Metadata**: Retrieve definitions of custom fields to understand available inputs. |
77
- | `get_templates` | **Metadata**: List available case templates to ensure correct case creation structure. |
78
- | `get_priorities` | **Metadata**: List available priority levels. |
79
- | `get_case_types` | **Metadata**: List available case types (e.g., Automated, Manual). |
144
+ | `get_projects` | List all available projects in your TestRail instance |
145
+ | `get_sections` | Navigate the folder/section hierarchy of a test suite |
146
+
147
+ ### Test Case Management
148
+
149
+ | Tool | Description |
150
+ |------|-------------|
151
+ | `get_cases` | Query test cases with filtering by priority, template, type, and more — supports pagination and recursive section fetching |
152
+ | `get_case` | Fetch complete details of a specific test case including steps and custom fields |
153
+ | `create_case` | Create a new test case with full custom field support |
154
+ | `update_case` | Modify an existing test case's fields, title, or steps |
155
+ | `update_cases` | Bulk-update multiple test cases simultaneously |
156
+
157
+ ### Test Execution
158
+
159
+ | Tool | Description |
160
+ |------|-------------|
161
+ | `add_run` | Create a new test run in TestRail |
162
+ | `get_tests` | Get tests for a run, optionally filtered by status |
163
+ | `add_results` | Submit one or more test results to a run |
164
+ | `add_attachment_to_run` | Attach a file or zipped folder to a test run |
165
+
166
+ ### Metadata
167
+
168
+ | Tool | Description |
169
+ |------|-------------|
170
+ | `get_statuses` | List all available test statuses (Passed, Failed, Blocked, etc.) |
171
+ | `get_case_fields` | Retrieve custom field definitions and dropdown options |
172
+ | `get_templates` | List available case templates for correct case structure |
173
+
174
+ ## 🏗️ Development
175
+
176
+ ### Prerequisites
177
+
178
+ - [Node.js](https://nodejs.org/) 18+
179
+ - npm
180
+
181
+ ### Setup
182
+
183
+ ```bash
184
+ git clone https://github.com/uarlouski/testrail-mcp-server.git
185
+ cd testrail-mcp-server
186
+ npm install
187
+ ```
188
+
189
+ ### Build
190
+
191
+ ```bash
192
+ npm run build
193
+ ```
194
+
195
+ ### Run Tests
196
+
197
+ ```bash
198
+ npm test
199
+ ```
200
+
201
+ ## 🤝 Contributing
202
+
203
+ Contributions are welcome! Please feel free to open an [issue](https://github.com/uarlouski/testrail-mcp-server/issues) or submit a pull request.
80
204
 
81
205
  ## 📜 License
82
206
 
83
- This project is licensed under the Apache 2.0 License - see the [LICENSE](LICENSE) file for details.
207
+ This project is licensed under the [Apache License 2.0](LICENSE).
84
208
 
85
209
  ---
86
210
 
87
- *Keywords: TestRail, MCP, Model Context Protocol, LLM, AI, Claude, Cursor, Windsurf, Test Management, QA, Automation, Testing, API*
211
+ <p align="center">
212
+ <b>TestRail MCP Server</b> · Built with the <a href="https://modelcontextprotocol.io">Model Context Protocol</a>
213
+ </p>
214
+
215
+ <p align="center">
216
+ <sub>Keywords: TestRail MCP Server, Model Context Protocol, TestRail API integration, AI test management, LLM testing tools, Claude TestRail, Cursor TestRail, Windsurf TestRail, test case automation, QA AI assistant, MCP server for testing, TestRail plugin, test run management, AI-powered QA</sub>
217
+ </p>
@@ -23,6 +23,23 @@ export class TestRailClient {
23
23
  async getCase(caseId) {
24
24
  return this.get(`${API_BASE_V2}/get_case/${caseId}`);
25
25
  }
26
+ async getCasesRecursively(projectId, sectionId, filter, excludedSectionNames) {
27
+ const sections = await this.getSections(projectId);
28
+ const sectionIds = this.getSubSectionIds(sections, sectionId, excludedSectionNames);
29
+ sectionIds.push(sectionId);
30
+ const promises = sectionIds.map(id => this.getCases(projectId, id, filter));
31
+ const results = await Promise.all(promises);
32
+ return results.flat();
33
+ }
34
+ getSubSectionIds(sections, parentId, excludedSectionNames) {
35
+ const children = sections.filter(s => s.parent_id === parentId);
36
+ const validChildren = children.filter(s => !excludedSectionNames?.includes(s.name));
37
+ let ids = validChildren.map(s => s.id);
38
+ for (const child of validChildren) {
39
+ ids = ids.concat(this.getSubSectionIds(sections, child.id, excludedSectionNames));
40
+ }
41
+ return ids;
42
+ }
26
43
  async getCases(projectId, sectionId, filter) {
27
44
  let url = `${API_BASE_V2}/get_cases/${projectId}`;
28
45
  if (sectionId) {
@@ -73,10 +90,10 @@ export class TestRailClient {
73
90
  return this.statusesPromise;
74
91
  }
75
92
  async getTemplates(projectId) {
76
- if (!this.templatesPromiseMap.has(projectId)) {
77
- this.templatesPromiseMap.set(projectId, this.get(`${API_BASE_V2}/get_templates/${projectId}`));
93
+ if (!this.templatesPromiseMap.has(projectId.toString())) {
94
+ this.templatesPromiseMap.set(projectId.toString(), this.get(`${API_BASE_V2}/get_templates/${projectId}`));
78
95
  }
79
- return this.templatesPromiseMap.get(projectId);
96
+ return this.templatesPromiseMap.get(projectId.toString());
80
97
  }
81
98
  async updateCase(caseId, fields) {
82
99
  return this.post(`${API_BASE_V2}/update_case/${caseId}`, fields);
@@ -155,13 +172,26 @@ export class TestRailClient {
155
172
  headers,
156
173
  body,
157
174
  };
158
- const response = await fetch(url, params);
159
- if (!response.ok) {
160
- let errorMessage = `TestRail: ${response.status} ${response.statusText}`;
161
- const errorText = await response.text();
162
- errorMessage += ` - ${errorText}`;
163
- throw new Error(errorMessage);
175
+ console.error(`[TestRailClient] Executing ${method} request to ${endpoint}`);
176
+ const startTime = Date.now();
177
+ try {
178
+ const response = await fetch(url, params);
179
+ const duration = Date.now() - startTime;
180
+ console.error(`[TestRailClient] Received ${response.status} ${response.statusText} from ${method} ${endpoint} in ${duration}ms`);
181
+ if (!response.ok) {
182
+ let errorMessage = `TestRail API Error: ${response.status} ${response.statusText}`;
183
+ const errorText = await response.text();
184
+ errorMessage += ` - ${errorText}`;
185
+ console.error(`[TestRailClient] Error Details: ${errorMessage}`);
186
+ throw new Error(errorMessage);
187
+ }
188
+ return await response.json();
189
+ }
190
+ catch (error) {
191
+ if (error instanceof Error && !error.message.startsWith('TestRail API Error')) {
192
+ console.error(`[TestRailClient] Request Failed: ${method} ${endpoint} - ${error.message}`);
193
+ }
194
+ throw error;
164
195
  }
165
- return await response.json();
166
196
  }
167
197
  }
package/dist/index.js CHANGED
@@ -27,7 +27,7 @@ if (!TESTRAIL_INSTANCE_URL || !TESTRAIL_USERNAME || !TESTRAIL_API_KEY) {
27
27
  }
28
28
  const server = new McpServer({
29
29
  name: "TestRail MCP Server",
30
- version: "1.3.1",
30
+ version: "1.5.0",
31
31
  });
32
32
  const client = new TestRailClient(TESTRAIL_INSTANCE_URL, TESTRAIL_USERNAME, TESTRAIL_API_KEY);
33
33
  const tools = [
@@ -5,7 +5,7 @@ import * as path from "path";
5
5
  import * as os from "os";
6
6
  import archiver from "archiver";
7
7
  const parameters = {
8
- run_id: z.string().describe("The ID of the test run to attach the file to"),
8
+ run_id: z.number().describe("The ID of the test run to attach the file to"),
9
9
  file_path: z.string().describe("The path to the file or directory to attach. Directories will be automatically zipped."),
10
10
  };
11
11
  export const addAttachmentToRunTool = {
@@ -40,7 +40,7 @@ export const addAttachmentToRunTool = {
40
40
  isTemporary = true;
41
41
  }
42
42
  try {
43
- const result = await client.addAttachmentToRun(Number(run_id), uploadPath, filename);
43
+ const result = await client.addAttachmentToRun(run_id, uploadPath, filename);
44
44
  return AttachmentSchema.parse(result);
45
45
  }
46
46
  finally {
@@ -1,17 +1,17 @@
1
1
  import { z } from "zod";
2
2
  import { RunSchema } from "../types/testrail.js";
3
3
  const parameters = {
4
- project_id: z.string().describe("The ID of the project. Use get_projects to find available projects"),
4
+ project_id: z.number().describe("The ID of the project. Use get_projects to find available projects"),
5
5
  name: z.string().optional().describe("The name of the test run"),
6
6
  description: z.string().optional().describe("The description of the test run"),
7
- case_ids: z.array(z.number()).describe("Array of case IDs to include in the run. After creating the run, use get_tests with the returned run_id to retrieve test IDs for result submission")
7
+ case_ids: z.array(z.number()).min(1).describe("Array of case IDs to include in the run. After creating the run, use get_tests with the returned run_id to retrieve test IDs for result submission")
8
8
  };
9
9
  export const addRunTool = {
10
10
  name: "add_run",
11
11
  description: "Create a new test run in TestRail",
12
12
  parameters,
13
13
  handler: async ({ project_id, ...fields }, client) => {
14
- const caseData = await client.getCase(String(fields.case_ids[0]));
14
+ const caseData = await client.getCase(fields.case_ids[0]);
15
15
  const params = { suite_id: caseData.suite_id, include_all: false, ...fields };
16
16
  const run = await client.addRun(project_id, params);
17
17
  return RunSchema.parse(run);
@@ -1,6 +1,6 @@
1
1
  import { z } from "zod";
2
2
  const parameters = {
3
- section_id: z.string().describe("The ID of the section where the case should be created. Use get_sections to find available sections"),
3
+ section_id: z.number().describe("The ID of the section where the case should be created. Use get_sections to find available sections"),
4
4
  title: z.string().describe("The title of the test case"),
5
5
  fields: z.record(z.string(), z.any()).optional().describe("Optional fields for the test case. First use get_templates to discover available templates, then get_case_fields to see available fields for your template. Example: {\"priority_id\": 2, \"template_id\": 1, \"custom_automation_priority\": 1}"),
6
6
  };
@@ -1,5 +1,6 @@
1
1
  import { z } from "zod";
2
2
  import { processCustomFields } from "../utils/mapper.js";
3
+ import { LabelSchema } from "../types/testrail.js";
3
4
  const parameters = {
4
5
  case_id: z.string().describe("The ID of the test case (e.g. '123' or 'C123')"),
5
6
  };
@@ -8,10 +9,11 @@ export const getCaseTool = {
8
9
  description: "Get detailed information about a test case including its custom fields",
9
10
  parameters,
10
11
  handler: async ({ case_id }, client) => {
11
- const id = case_id.toUpperCase().startsWith("C") ? case_id.substring(1) : case_id;
12
+ const idString = case_id.toUpperCase().startsWith("C") ? case_id.substring(1) : case_id;
13
+ const id = Number(idString);
12
14
  const testCase = await client.getCase(id);
13
15
  const [section, caseTypes, priorities, caseFields] = await Promise.all([
14
- client.getSection(testCase.section_id.toString()),
16
+ client.getSection(Number(testCase.section_id)),
15
17
  client.getCaseTypes(),
16
18
  client.getPriorities(),
17
19
  client.getCaseFields()
@@ -25,7 +27,7 @@ export const getCaseTool = {
25
27
  section: section.name,
26
28
  type: caseType ? caseType.name : "Unknown",
27
29
  priority: priority ? priority.name : "Unknown",
28
- labels: testCase.labels || [],
30
+ labels: (testCase.labels || []).map((label) => LabelSchema.parse(label)),
29
31
  references: testCase.refs,
30
32
  updated_on: testCase.updated_on,
31
33
  ...customFields
@@ -1,7 +1,11 @@
1
1
  import { z } from "zod";
2
2
  const parameters = {
3
- project_id: z.string().describe("The ID of the project. Use get_projects to find available projects"),
4
- section_id: z.string().optional().describe("Optional section ID to filter cases by section. Use get_sections to find available sections"),
3
+ project_id: z.number().describe("The ID of the project. Use get_projects to find available projects"),
4
+ section: z.object({
5
+ id: z.number(),
6
+ recursive: z.boolean().optional().default(false).describe("If true, fetches cases from the section and all its child sections"),
7
+ excludes: z.array(z.string()).optional().describe("List of section names to exclude from the recursive search"),
8
+ }).optional().describe("Section filter configuration. Use get_sections to find available sections"),
5
9
  filter: z.record(z.string(), z.string()).optional().describe("Optional API-side filters (more efficient for large datasets). Supported: priority_id, type_id, created_by, updated_by, milestone_id, refs, created_after, created_before, updated_after, updated_before. Use comma-separated values for IDs. Example: {\"priority_id\": \"1,2\", \"type_id\": \"3\"}"),
6
10
  where: z.record(z.string(), z.any()).optional().describe("Optional client-side filter for any field including custom fields (filters after fetching all cases). Supports exact value matching. Example: {\"custom_automation_status\": 1, \"priority_id\": 2}"),
7
11
  fields: z.array(z.string()).optional().describe("Additional fields to include in response beyond id, title, and suite_id. Use get_case_fields to see available fields. Example: [\"priority_id\", \"type_id\", \"custom_automation_status\"]"),
@@ -10,8 +14,14 @@ export const getCasesTool = {
10
14
  name: "get_cases",
11
15
  description: "Get all test cases for a project. Filter by section, API params (priority, type), or any field including custom fields via 'where'. Returns case IDs, titles, and any additional requested fields.",
12
16
  parameters,
13
- handler: async ({ project_id, section_id, filter, where, fields }, client) => {
14
- let cases = await client.getCases(project_id, section_id, filter);
17
+ handler: async ({ project_id, section, filter, where, fields }, client) => {
18
+ let cases;
19
+ if (section?.recursive) {
20
+ cases = await client.getCasesRecursively(project_id, section.id, filter, section.excludes);
21
+ }
22
+ else {
23
+ cases = await client.getCases(project_id, section?.id, filter);
24
+ }
15
25
  // Client-side filtering for custom fields and other unsupported API filters
16
26
  if (where) {
17
27
  cases = cases.filter(c => {
@@ -1,7 +1,7 @@
1
1
  import { z } from "zod";
2
2
  import { SectionSchema } from "../types/testrail.js";
3
3
  const parameters = {
4
- project_id: z.string().describe("The ID of the project. Use get_projects to find available projects"),
4
+ project_id: z.number().describe("The ID of the project. Use get_projects to find available projects"),
5
5
  };
6
6
  export const getSectionsTool = {
7
7
  name: "get_sections",
@@ -1,7 +1,7 @@
1
1
  import { z } from "zod";
2
2
  import { TemplateSchema } from "../types/testrail.js";
3
3
  const parameters = {
4
- project_id: z.string().describe("The ID of the project. Use get_projects to find available projects"),
4
+ project_id: z.number().describe("The ID of the project. Use get_projects to find available projects"),
5
5
  };
6
6
  export const getTemplatesTool = {
7
7
  name: "get_templates",
@@ -8,7 +8,8 @@ export const updateCaseTool = {
8
8
  description: "Update a test case in TestRail. Supports partial updates - only specify the fields you want to change",
9
9
  parameters,
10
10
  handler: async ({ case_id, fields }, client) => {
11
- const id = case_id.toUpperCase().startsWith("C") ? case_id.substring(1) : case_id;
11
+ const idString = case_id.toUpperCase().startsWith("C") ? case_id.substring(1) : case_id;
12
+ const id = Number(idString);
12
13
  const updatedCase = await client.updateCase(id, fields);
13
14
  return {
14
15
  success: true,
@@ -1,6 +1,6 @@
1
1
  import { z } from "zod";
2
2
  const parameters = {
3
- case_ids: z.array(z.number()).describe("Array of case IDs to update (e.g. [123, 456, 789])"),
3
+ case_ids: z.array(z.number()).min(1).describe("Array of case IDs to update (e.g. [123, 456, 789])"),
4
4
  fields: z.record(z.string(), z.any()).describe("Object containing fields to update for ALL specified cases. Use get_case_fields to see available fields. Example: {\"priority_id\": 2, \"type_id\": 1}"),
5
5
  };
6
6
  export const updateCasesTool = {
@@ -8,7 +8,7 @@ export const updateCasesTool = {
8
8
  description: "Bulk update multiple test cases with the same field values. More efficient than calling update_case multiple times",
9
9
  parameters,
10
10
  handler: async ({ case_ids, fields }, client) => {
11
- const caseData = await client.getCase(String(case_ids[0]));
11
+ const caseData = await client.getCase(case_ids[0]);
12
12
  const updatedCases = await client.updateCases(caseData.suite_id, case_ids, fields);
13
13
  return {
14
14
  success: true,
@@ -18,6 +18,9 @@ export const CaseFieldSchema = z.object({
18
18
  include_all: z.boolean(),
19
19
  configs: z.array(CaseFieldConfigSchema),
20
20
  });
21
+ export const LabelSchema = z.object({
22
+ title: z.string(),
23
+ });
21
24
  export const CaseSchema = z.object({
22
25
  id: z.number(),
23
26
  title: z.string(),
@@ -31,7 +34,7 @@ export const CaseSchema = z.object({
31
34
  updated_on: z.number(),
32
35
  estimate: z.string().nullable(),
33
36
  suite_id: z.number(),
34
- labels: z.array(z.any()),
37
+ labels: z.array(LabelSchema),
35
38
  }).loose();
36
39
  export const SectionSchema = z.object({
37
40
  id: z.number(),
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Recursively ыtrips inline style attributes from HTML tags contained in string values
2
+ * Recursively strips inline style attributes from HTML tags contained in string values
3
3
  * in an object or array. Useful for cleaning up TestRail markdown content before sending
4
4
  * to LLMs.
5
5
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uarlouski/testrail-mcp-server",
3
- "version": "1.3.1",
3
+ "version": "1.5.0",
4
4
  "description": "Model Context Protocol (MCP) server for TestRail integration, enabling LLMs to fetch test cases and metadata.",
5
5
  "main": "dist/index.js",
6
6
  "bin": {