@uarlouski/testrail-mcp-server 1.8.0 โ 1.9.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 +31 -18
- package/dist/client/testrail.js +28 -3
- package/dist/index.js +20 -5
- package/dist/tools/{create_case.js โ add_case.js} +10 -5
- package/dist/tools/get_case_fields.js +56 -17
- package/dist/tools/get_sections.js +1 -1
- package/dist/tools/shared_steps/add_shared_step.js +20 -0
- package/dist/tools/shared_steps/delete_shared_step.js +18 -0
- package/dist/tools/shared_steps/get_shared_step.js +15 -0
- package/dist/tools/shared_steps/get_shared_step_history.js +15 -0
- package/dist/tools/shared_steps/get_shared_steps.js +16 -0
- package/dist/tools/shared_steps/update_shared_step.js +24 -0
- package/dist/tools/update_case.js +8 -2
- package/dist/tools/update_cases.js +8 -2
- package/dist/types/testrail.js +21 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
<div align="center">
|
|
2
2
|
<h1>๐ TestRail MCP Server</h1>
|
|
3
3
|
<p>
|
|
4
|
-
<strong>
|
|
5
|
-
|
|
4
|
+
<strong>An open-source Model Context Protocol (MCP) server that connects Claude, Cursor, Windsurf, and other AI assistants directly to TestRail.</strong>
|
|
5
|
+
</p>
|
|
6
|
+
<p>
|
|
7
|
+
Manage TestRail projects, search and create test cases, kick off test runs, record results, and attach files โ all through natural-language conversation with your AI assistant. Built for QA engineers and AI-assisted test automation.
|
|
6
8
|
</p>
|
|
7
9
|
</div>
|
|
8
10
|
|
|
@@ -15,6 +17,16 @@
|
|
|
15
17
|
<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>
|
|
16
18
|
</p>
|
|
17
19
|
|
|
20
|
+
<p align="center">
|
|
21
|
+
<strong>Compatible with:</strong>
|
|
22
|
+
<img src="https://img.shields.io/badge/Claude%20Desktop-D97757?logo=anthropic&logoColor=white" alt="Claude Desktop">
|
|
23
|
+
<img src="https://img.shields.io/badge/Cursor-000000?logo=cursor&logoColor=white" alt="Cursor">
|
|
24
|
+
<img src="https://img.shields.io/badge/Windsurf-19A1BC?logo=codeium&logoColor=white" alt="Windsurf">
|
|
25
|
+
<img src="https://img.shields.io/badge/VS%20Code-007ACC?logo=visualstudiocode&logoColor=white" alt="VS Code">
|
|
26
|
+
</p>
|
|
27
|
+
|
|
28
|
+
<!-- TODO: insert demo.gif โ short screencast of asking "create a test case" in Claude or Cursor and seeing the case appear in TestRail -->
|
|
29
|
+
|
|
18
30
|
---
|
|
19
31
|
|
|
20
32
|
## ๐ Why Choose TestRail MCP Server?
|
|
@@ -40,10 +52,9 @@ Navigate to **My Settings โ API Keys** in your TestRail platform and generate
|
|
|
40
52
|
|
|
41
53
|
### 2. Configure Your MCP Client
|
|
42
54
|
|
|
43
|
-
Add the server to your chosen MCP client configuration.
|
|
55
|
+
Add the server to your chosen MCP client configuration. The Claude Desktop example is shown below; Cursor, Windsurf, and other clients use the same pattern (see the collapsible sections further down).
|
|
44
56
|
|
|
45
|
-
|
|
46
|
-
<summary><strong>๐ค Claude Desktop</strong></summary>
|
|
57
|
+
#### ๐ค Claude Desktop
|
|
47
58
|
|
|
48
59
|
Add this to your `claude_desktop_config.json`:
|
|
49
60
|
|
|
@@ -56,13 +67,13 @@ Add this to your `claude_desktop_config.json`:
|
|
|
56
67
|
"env": {
|
|
57
68
|
"TESTRAIL_INSTANCE_URL": "https://your-instance.testrail.io",
|
|
58
69
|
"TESTRAIL_USERNAME": "your@email.com",
|
|
59
|
-
"TESTRAIL_API_KEY": "your-api-key"
|
|
70
|
+
"TESTRAIL_API_KEY": "your-api-key",
|
|
71
|
+
"TESTRAIL_ENABLE_SHARED_STEPS": "true"
|
|
60
72
|
}
|
|
61
73
|
}
|
|
62
74
|
}
|
|
63
75
|
}
|
|
64
76
|
```
|
|
65
|
-
</details>
|
|
66
77
|
|
|
67
78
|
<details>
|
|
68
79
|
<summary><strong>โจ๏ธ Cursor</strong></summary>
|
|
@@ -133,6 +144,7 @@ Once configured, turbo-charge your QA workflow by asking your AI assistant:
|
|
|
133
144
|
| `TESTRAIL_INSTANCE_URL` | Your TestRail instance URL (e.g., `https://example.testrail.io`) | โ
|
|
|
134
145
|
| `TESTRAIL_USERNAME` | Your TestRail user email address | โ
|
|
|
135
146
|
| `TESTRAIL_API_KEY` | Your TestRail API key ([Guide](https://support.testrail.com/hc/en-us/articles/7077039051412-Accessing-the-TestRail-API)) | โ
|
|
|
147
|
+
| `TESTRAIL_ENABLE_SHARED_STEPS` | Set to `true` to enable Shared Steps management tools | |
|
|
136
148
|
|
|
137
149
|
---
|
|
138
150
|
|
|
@@ -151,7 +163,7 @@ The TestRail MCP Server provides heavily typed, descriptive tools designed speci
|
|
|
151
163
|
|------|-------------|
|
|
152
164
|
| `get_cases` | Query test cases with advanced filtering (priority, template, type, etc.) and pagination. |
|
|
153
165
|
| `get_case` | Fetch complete, structured details of a specific test case, including custom steps and fields. |
|
|
154
|
-
| `
|
|
166
|
+
| `add_case` | Seamlessly create a new test case equipped with robust custom field validation. |
|
|
155
167
|
| `update_case` | Modify an existing test case's steps, metadata, or titles. |
|
|
156
168
|
| `update_cases` | Execute bulk-updates on multiple test cases simultaneously to save time. |
|
|
157
169
|
|
|
@@ -164,12 +176,22 @@ The TestRail MCP Server provides heavily typed, descriptive tools designed speci
|
|
|
164
176
|
| `add_results_for_cases` | Submits results to a run mapping directly to `case_id`s, streamlining automation workflows. |
|
|
165
177
|
| `add_attachment_to_run` | Attach logs, files, or zipped artifacts directly to an ongoing test run. |
|
|
166
178
|
|
|
179
|
+
### ๐ Shared Steps (Optional)
|
|
180
|
+
| Tool | Functionality |
|
|
181
|
+
|------|-------------|
|
|
182
|
+
| `get_shared_steps` | List shared test steps for a project with optional reference filtering. |
|
|
183
|
+
| `get_shared_step` | Retrieve precise details of a specific shared test step set. |
|
|
184
|
+
| `get_shared_step_history` | View the complete audit trail and version history of a shared step set. |
|
|
185
|
+
| `add_shared_step` | Create a new reusable set of shared test steps. |
|
|
186
|
+
| `update_shared_step` | Modify an existing shared step set (changes propagate to all linked test cases). |
|
|
187
|
+
| `delete_shared_step` | Securely delete a shared step entity. |
|
|
188
|
+
|
|
167
189
|
### ๐ง System Metadata
|
|
168
190
|
| Tool | Functionality |
|
|
169
191
|
|------|-------------|
|
|
170
192
|
| `get_statuses` | Systematically list all configured test statuses (Passed, Failed, Blocked, Retest, etc.). |
|
|
171
193
|
| `get_priorities` | Retrieve priority levels configured within your instance structure. |
|
|
172
|
-
| `get_case_fields` | Discover custom field definitions, formats, and UI dropdown options. |
|
|
194
|
+
| `get_case_fields` | Discover custom field definitions, formats, and UI dropdown options. Supports filtering by `project_id`. |
|
|
173
195
|
| `get_templates` | Identify available case templates to mandate correct AI structuring. |
|
|
174
196
|
|
|
175
197
|
---
|
|
@@ -185,12 +207,3 @@ This project is securely licensed under the [Apache License 2.0](LICENSE).
|
|
|
185
207
|
<p align="center">
|
|
186
208
|
<b>TestRail MCP Server</b> ยท Engineered with the <a href="https://modelcontextprotocol.io">Model Context Protocol</a>
|
|
187
209
|
</p>
|
|
188
|
-
|
|
189
|
-
<!-- SEO Keywords -->
|
|
190
|
-
<div align="center">
|
|
191
|
-
<p>
|
|
192
|
-
<small>
|
|
193
|
-
<b>Keywords:</b> TestRail MCP Server, Model Context Protocol, TestRail API Integration, AI Test Management, LLM Testing Tools, Claude TestRail Plugin, Cursor TestRail, Windsurf QA Tool, Test Case Automation, QA AI Assistant, MCP Server Developer Tools, Prompt Engineering TestRail, Test Run Tracking, QA Productivity Automation.
|
|
194
|
-
</small>
|
|
195
|
-
</p>
|
|
196
|
-
</div>
|
package/dist/client/testrail.js
CHANGED
|
@@ -95,7 +95,7 @@ export class TestRailClient {
|
|
|
95
95
|
});
|
|
96
96
|
return response.updated_cases;
|
|
97
97
|
}
|
|
98
|
-
async
|
|
98
|
+
async addCase(sectionId, fields) {
|
|
99
99
|
return this.post(`${API_BASE_V2}/add_case/${sectionId}`, fields);
|
|
100
100
|
}
|
|
101
101
|
async addRun(projectId, fields) {
|
|
@@ -139,6 +139,28 @@ export class TestRailClient {
|
|
|
139
139
|
};
|
|
140
140
|
return this._executeRequest('POST', `${API_BASE_V2}/add_attachment_to_run/${runId}`, headers, formData);
|
|
141
141
|
}
|
|
142
|
+
async getSharedSteps(projectId, options) {
|
|
143
|
+
let url = `${API_BASE_V2}/get_shared_steps/${projectId}`;
|
|
144
|
+
if (options?.refs) {
|
|
145
|
+
url += `&refs=${options.refs}`;
|
|
146
|
+
}
|
|
147
|
+
return this.paginateAll(url, 'shared_steps');
|
|
148
|
+
}
|
|
149
|
+
async getSharedStep(sharedStepId) {
|
|
150
|
+
return this.get(`${API_BASE_V2}/get_shared_step/${sharedStepId}`);
|
|
151
|
+
}
|
|
152
|
+
async getSharedStepHistory(sharedStepId) {
|
|
153
|
+
return this.get(`${API_BASE_V2}/get_shared_step_history/${sharedStepId}`);
|
|
154
|
+
}
|
|
155
|
+
async addSharedStep(projectId, data) {
|
|
156
|
+
return this.post(`${API_BASE_V2}/add_shared_step/${projectId}`, data);
|
|
157
|
+
}
|
|
158
|
+
async updateSharedStep(sharedStepId, data) {
|
|
159
|
+
return this.post(`${API_BASE_V2}/update_shared_step/${sharedStepId}`, data);
|
|
160
|
+
}
|
|
161
|
+
async deleteSharedStep(sharedStepId) {
|
|
162
|
+
return this._executeRequest('POST', `${API_BASE_V2}/delete_shared_step/${sharedStepId}`, this.headers, JSON.stringify({}), false);
|
|
163
|
+
}
|
|
142
164
|
async paginateAll(url, dataKey) {
|
|
143
165
|
const allItems = [];
|
|
144
166
|
let nextUrl = url;
|
|
@@ -167,7 +189,7 @@ export class TestRailClient {
|
|
|
167
189
|
}
|
|
168
190
|
return this._executeRequest(method, endpoint, this.headers, jsonData);
|
|
169
191
|
}
|
|
170
|
-
async _executeRequest(method, endpoint, headers, body) {
|
|
192
|
+
async _executeRequest(method, endpoint, headers, body, parseBody = true) {
|
|
171
193
|
const url = `${this.baseUrl}${endpoint}`;
|
|
172
194
|
const params = {
|
|
173
195
|
method,
|
|
@@ -205,7 +227,10 @@ export class TestRailClient {
|
|
|
205
227
|
console.error(`[TestRailClient] Error Details: ${errorMessage}`);
|
|
206
228
|
throw new Error(errorMessage);
|
|
207
229
|
}
|
|
208
|
-
|
|
230
|
+
if (parseBody) {
|
|
231
|
+
return await response.json();
|
|
232
|
+
}
|
|
233
|
+
return await response.text();
|
|
209
234
|
}
|
|
210
235
|
catch (error) {
|
|
211
236
|
const networkError = error instanceof Error && !error.message.startsWith('TestRail API Error');
|
package/dist/index.js
CHANGED
|
@@ -9,7 +9,7 @@ import { getCaseFieldsTool } from "./tools/get_case_fields.js";
|
|
|
9
9
|
import { getTemplatesTool } from "./tools/get_templates.js";
|
|
10
10
|
import { updateCaseTool } from "./tools/update_case.js";
|
|
11
11
|
import { updateCasesTool } from "./tools/update_cases.js";
|
|
12
|
-
import {
|
|
12
|
+
import { addCaseTool } from "./tools/add_case.js";
|
|
13
13
|
import { getSectionsTool } from "./tools/get_sections.js";
|
|
14
14
|
import { getProjectsTool } from "./tools/get_projects.js";
|
|
15
15
|
import { addRunTool } from "./tools/add_run.js";
|
|
@@ -20,22 +20,29 @@ import { addResultsTool } from "./tools/add_results.js";
|
|
|
20
20
|
import { addAttachmentToRunTool } from "./tools/add_attachment_to_run.js";
|
|
21
21
|
import { addResultsForCasesTool } from "./tools/add_results_for_cases.js";
|
|
22
22
|
import { getLabelsTool } from "./tools/get_labels.js";
|
|
23
|
+
import { getSharedStepsTool } from "./tools/shared_steps/get_shared_steps.js";
|
|
24
|
+
import { getSharedStepTool } from "./tools/shared_steps/get_shared_step.js";
|
|
25
|
+
import { getSharedStepHistoryTool } from "./tools/shared_steps/get_shared_step_history.js";
|
|
26
|
+
import { addSharedStepTool } from "./tools/shared_steps/add_shared_step.js";
|
|
27
|
+
import { updateSharedStepTool } from "./tools/shared_steps/update_shared_step.js";
|
|
28
|
+
import { deleteSharedStepTool } from "./tools/shared_steps/delete_shared_step.js";
|
|
23
29
|
import { removeNullish } from "./utils/sanitizer.js";
|
|
24
30
|
import z from "zod";
|
|
25
31
|
const EnvSchema = z.object({
|
|
26
32
|
TESTRAIL_INSTANCE_URL: z.url('Must be a valid TestRail URL'),
|
|
27
33
|
TESTRAIL_USERNAME: z.email('Must be a valid email address'),
|
|
28
|
-
TESTRAIL_API_KEY: z.string().min(1, 'API key is required')
|
|
34
|
+
TESTRAIL_API_KEY: z.string().min(1, 'API key is required'),
|
|
35
|
+
TESTRAIL_ENABLE_SHARED_STEPS: z.string().optional().transform(val => val === 'true')
|
|
29
36
|
});
|
|
30
37
|
const parseResult = EnvSchema.safeParse(process.env);
|
|
31
38
|
if (!parseResult.success) {
|
|
32
39
|
console.error("Invalid TestRail environment configuration:", JSON.stringify(z.treeifyError(parseResult.error), null, 2));
|
|
33
40
|
process.exit(1);
|
|
34
41
|
}
|
|
35
|
-
const { TESTRAIL_INSTANCE_URL, TESTRAIL_USERNAME, TESTRAIL_API_KEY } = parseResult.data;
|
|
42
|
+
const { TESTRAIL_INSTANCE_URL, TESTRAIL_USERNAME, TESTRAIL_API_KEY, TESTRAIL_ENABLE_SHARED_STEPS } = parseResult.data;
|
|
36
43
|
const server = new McpServer({
|
|
37
44
|
name: "TestRail MCP Server",
|
|
38
|
-
version: "1.
|
|
45
|
+
version: "1.9.0",
|
|
39
46
|
});
|
|
40
47
|
const client = new TestRailClient(TESTRAIL_INSTANCE_URL, TESTRAIL_USERNAME, TESTRAIL_API_KEY);
|
|
41
48
|
const tools = [
|
|
@@ -47,7 +54,7 @@ const tools = [
|
|
|
47
54
|
getSectionsTool,
|
|
48
55
|
updateCaseTool,
|
|
49
56
|
updateCasesTool,
|
|
50
|
-
|
|
57
|
+
addCaseTool,
|
|
51
58
|
addRunTool,
|
|
52
59
|
getStatusesTool,
|
|
53
60
|
getPrioritiesTool,
|
|
@@ -57,6 +64,14 @@ const tools = [
|
|
|
57
64
|
addAttachmentToRunTool,
|
|
58
65
|
getLabelsTool,
|
|
59
66
|
];
|
|
67
|
+
if (TESTRAIL_ENABLE_SHARED_STEPS) {
|
|
68
|
+
tools.push(getSharedStepsTool);
|
|
69
|
+
tools.push(getSharedStepTool);
|
|
70
|
+
tools.push(getSharedStepHistoryTool);
|
|
71
|
+
tools.push(addSharedStepTool);
|
|
72
|
+
tools.push(updateSharedStepTool);
|
|
73
|
+
tools.push(deleteSharedStepTool);
|
|
74
|
+
}
|
|
60
75
|
for (const tool of tools) {
|
|
61
76
|
server.registerTool(tool.name, {
|
|
62
77
|
description: tool.description,
|
|
@@ -1,13 +1,18 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { validateCaseFields } from "../utils/validator.js";
|
|
3
|
+
import { CASE_FIELDS_PARAM_DESCRIPTION } from "./get_case_fields.js";
|
|
3
4
|
const parameters = {
|
|
4
5
|
section_id: z.number().describe("The ID of the section where the case should be created. Use get_sections to find available sections"),
|
|
5
6
|
title: z.string().describe("The title of the test case"),
|
|
6
|
-
fields: z.record(z.string(), z.any()).optional().describe(
|
|
7
|
+
fields: z.record(z.string(), z.any()).optional().describe(CASE_FIELDS_PARAM_DESCRIPTION),
|
|
7
8
|
};
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
const description = `
|
|
10
|
+
Create a new test case in TestRail.
|
|
11
|
+
The create operation requires knowing valid field names that are returned by get_case_fields tool.
|
|
12
|
+
`;
|
|
13
|
+
export const addCaseTool = {
|
|
14
|
+
name: "add_case",
|
|
15
|
+
description: description.trim(),
|
|
11
16
|
parameters,
|
|
12
17
|
handler: async ({ section_id, title, fields }, client) => {
|
|
13
18
|
if (fields) {
|
|
@@ -17,7 +22,7 @@ export const createCaseTool = {
|
|
|
17
22
|
title,
|
|
18
23
|
...fields,
|
|
19
24
|
};
|
|
20
|
-
const createdCase = await client.
|
|
25
|
+
const createdCase = await client.addCase(section_id, caseData);
|
|
21
26
|
return {
|
|
22
27
|
success: true,
|
|
23
28
|
case_id: createdCase.id,
|
|
@@ -1,5 +1,16 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
1
2
|
import { parseDropdownOptions } from "../utils/mapper.js";
|
|
2
|
-
|
|
3
|
+
// Field remains optional for backward compatibility with previous implementation.
|
|
4
|
+
// However, it is recommended to always provide project_id to get fields applicable to the project.
|
|
5
|
+
const parameters = {
|
|
6
|
+
project_id: z.number().optional().describe("The project ID to get fields for. This is the primary way to use this tool, as it returns only the fields relevant to your current project. If omitted, returns all fields across all projects."),
|
|
7
|
+
};
|
|
8
|
+
export const CASE_FIELDS_PARAM_DESCRIPTION = `
|
|
9
|
+
Must use system_name values from get_case_fields.
|
|
10
|
+
Call get_case_fields with project_id first if field names are not already known.
|
|
11
|
+
Using an unknown field name (e.g. 'label_ids') will result in an error.
|
|
12
|
+
Example: {"priority_id": 2, "template_id": 1, "labels": [1, 2]}
|
|
13
|
+
`;
|
|
3
14
|
const FIELD_TYPE_NAMES = {
|
|
4
15
|
1: "String",
|
|
5
16
|
2: "Integer",
|
|
@@ -35,29 +46,57 @@ export function mapToFieldSchema(field) {
|
|
|
35
46
|
if (field.type_id === 6 || field.type_id === 12) {
|
|
36
47
|
schema.options = Array.from(parseDropdownOptions(field).values());
|
|
37
48
|
}
|
|
49
|
+
const contexts = field.configs.map(c => c.context).filter(Boolean);
|
|
50
|
+
if (contexts.length > 0) {
|
|
51
|
+
const isGlobal = contexts.some(ctx => ctx.is_global);
|
|
52
|
+
if (isGlobal) {
|
|
53
|
+
schema.project_scope = { scope: "global" };
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
const projectIds = [...new Set(contexts.flatMap(ctx => ctx.project_ids))];
|
|
57
|
+
schema.project_scope = { scope: "projects", project_ids: projectIds };
|
|
58
|
+
}
|
|
59
|
+
}
|
|
38
60
|
return schema;
|
|
39
61
|
}
|
|
62
|
+
function isFieldForProject(field, projectId) {
|
|
63
|
+
const contexts = field.configs.map(c => c.context).filter(Boolean);
|
|
64
|
+
if (contexts.length === 0)
|
|
65
|
+
return true;
|
|
66
|
+
return contexts.some(ctx => ctx.is_global || ctx.project_ids.includes(projectId));
|
|
67
|
+
}
|
|
40
68
|
export const SYSTEM_FIELDS = [
|
|
41
|
-
{ system_name: "title", label: "Title", type: "String", is_required: true },
|
|
42
|
-
{ system_name: "section_id", label: "Section", type: "Integer", is_required: true },
|
|
43
|
-
{ system_name: "template_id", label: "Template", type: "Integer", is_required: false },
|
|
44
|
-
{ system_name: "type_id", label: "Type", type: "Integer", is_required: false },
|
|
45
|
-
{ system_name: "priority_id", label: "Priority", type: "Integer", is_required: false },
|
|
46
|
-
{ system_name: "estimate", label: "Estimate", type: "String", is_required: false },
|
|
47
|
-
{ system_name: "milestone_id", label: "Milestone", type: "Integer", is_required: false },
|
|
48
|
-
{ system_name: "refs", label: "References", type: "String", is_required: false },
|
|
49
|
-
{ system_name: "labels", label: "Labels", type: "List", is_required: false, comment: "Use get_labels tool to get available labels/tags." },
|
|
69
|
+
{ system_name: "title", label: "Title", type: "String", is_required: true, project_scope: { scope: "global" } },
|
|
70
|
+
{ system_name: "section_id", label: "Section", type: "Integer", is_required: true, project_scope: { scope: "global" } },
|
|
71
|
+
{ system_name: "template_id", label: "Template", type: "Integer", is_required: false, project_scope: { scope: "global" } },
|
|
72
|
+
{ system_name: "type_id", label: "Type", type: "Integer", is_required: false, project_scope: { scope: "global" } },
|
|
73
|
+
{ system_name: "priority_id", label: "Priority", type: "Integer", is_required: false, project_scope: { scope: "global" } },
|
|
74
|
+
{ system_name: "estimate", label: "Estimate", type: "String", is_required: false, project_scope: { scope: "global" } },
|
|
75
|
+
{ system_name: "milestone_id", label: "Milestone", type: "Integer", is_required: false, project_scope: { scope: "global" } },
|
|
76
|
+
{ system_name: "refs", label: "References", type: "String", is_required: false, project_scope: { scope: "global" } },
|
|
77
|
+
{ system_name: "labels", label: "Labels", type: "List", is_required: false, project_scope: { scope: "global" }, comment: "Use get_labels tool to get available labels/tags." },
|
|
50
78
|
];
|
|
79
|
+
const description = `
|
|
80
|
+
Get the field schema for test cases for a specific project.
|
|
81
|
+
You should normally provide project_id to get fields applicable to your project.
|
|
82
|
+
If you truly need all fields across all projects, you may omit project_id, but this is rarely what you want.
|
|
83
|
+
Returns available fields with their types and options (for dropdown fields).
|
|
84
|
+
`;
|
|
51
85
|
export const getCaseFieldsTool = {
|
|
52
86
|
name: "get_case_fields",
|
|
53
|
-
description:
|
|
87
|
+
description: description.trim(),
|
|
54
88
|
parameters,
|
|
55
|
-
handler: async (
|
|
89
|
+
handler: async ({ project_id }, client) => {
|
|
56
90
|
const caseFields = await client.getCaseFields();
|
|
57
|
-
const
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
91
|
+
const activeFields = caseFields.filter(field => field.is_active);
|
|
92
|
+
const filtered = project_id !== undefined
|
|
93
|
+
? activeFields.filter(field => isFieldForProject(field, project_id))
|
|
94
|
+
: activeFields;
|
|
95
|
+
const customFieldSchemas = filtered.map(mapToFieldSchema);
|
|
96
|
+
const allFields = [...SYSTEM_FIELDS, ...customFieldSchemas];
|
|
97
|
+
const fields = project_id !== undefined
|
|
98
|
+
? allFields.map(({ project_scope: _, ...rest }) => rest)
|
|
99
|
+
: allFields;
|
|
100
|
+
return { fields };
|
|
62
101
|
}
|
|
63
102
|
};
|
|
@@ -5,7 +5,7 @@ const parameters = {
|
|
|
5
5
|
};
|
|
6
6
|
export const getSectionsTool = {
|
|
7
7
|
name: "get_sections",
|
|
8
|
-
description: "Get all sections for a project. Returns section IDs and names that can be used with
|
|
8
|
+
description: "Get all sections for a project. Returns section IDs and names that can be used with add_case",
|
|
9
9
|
parameters,
|
|
10
10
|
handler: async ({ project_id }, client) => {
|
|
11
11
|
const sections = await client.getSections(project_id);
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { SharedStepSchema } from "../../types/testrail.js";
|
|
3
|
+
const parameters = {
|
|
4
|
+
project_id: z.number().describe("The ID of the project where the shared step will be created"),
|
|
5
|
+
title: z.string().describe("The title of the shared test step"),
|
|
6
|
+
custom_steps_separated: z.array(z.object({
|
|
7
|
+
content: z.string().describe("The step instruction"),
|
|
8
|
+
expected: z.string().optional().describe("The expected result of the step"),
|
|
9
|
+
})).optional().describe("The steps of the shared test step"),
|
|
10
|
+
};
|
|
11
|
+
export const addSharedStepTool = {
|
|
12
|
+
name: "add_shared_step",
|
|
13
|
+
description: "Creates a new set of shared test steps.",
|
|
14
|
+
parameters,
|
|
15
|
+
handler: async (args, client) => {
|
|
16
|
+
const { project_id, ...data } = args;
|
|
17
|
+
const sharedStep = await client.addSharedStep(project_id, data);
|
|
18
|
+
return { shared_step: SharedStepSchema.parse(sharedStep) };
|
|
19
|
+
}
|
|
20
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
const parameters = {
|
|
3
|
+
shared_step_id: z.number().describe("The ID of the shared test step to delete"),
|
|
4
|
+
};
|
|
5
|
+
const description = `
|
|
6
|
+
Deletes an existing shared step entity.
|
|
7
|
+
By default, this removes the shared step entity but keeps the steps in the test cases that used them.
|
|
8
|
+
`;
|
|
9
|
+
export const deleteSharedStepTool = {
|
|
10
|
+
name: "delete_shared_step",
|
|
11
|
+
description: description.trim(),
|
|
12
|
+
parameters,
|
|
13
|
+
handler: async (args, client) => {
|
|
14
|
+
const { shared_step_id } = args;
|
|
15
|
+
await client.deleteSharedStep(shared_step_id);
|
|
16
|
+
return { message: `Shared step ${shared_step_id} deleted successfully.` };
|
|
17
|
+
}
|
|
18
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { SharedStepSchema } from "../../types/testrail.js";
|
|
3
|
+
const parameters = {
|
|
4
|
+
shared_step_id: z.number().describe("The ID of the shared test step"),
|
|
5
|
+
};
|
|
6
|
+
export const getSharedStepTool = {
|
|
7
|
+
name: "get_shared_step",
|
|
8
|
+
description: "Returns the details of a specific shared test step.",
|
|
9
|
+
parameters,
|
|
10
|
+
handler: async (args, client) => {
|
|
11
|
+
const { shared_step_id } = args;
|
|
12
|
+
const sharedStep = await client.getSharedStep(shared_step_id);
|
|
13
|
+
return { shared_step: SharedStepSchema.parse(sharedStep) };
|
|
14
|
+
}
|
|
15
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { SharedStepHistorySchema } from "../../types/testrail.js";
|
|
3
|
+
const parameters = {
|
|
4
|
+
shared_step_id: z.number().describe("The ID of the shared test step"),
|
|
5
|
+
};
|
|
6
|
+
export const getSharedStepHistoryTool = {
|
|
7
|
+
name: "get_shared_step_history",
|
|
8
|
+
description: "Returns the change history of a set of shared test steps.",
|
|
9
|
+
parameters,
|
|
10
|
+
handler: async (args, client) => {
|
|
11
|
+
const { shared_step_id } = args;
|
|
12
|
+
const history = await client.getSharedStepHistory(shared_step_id);
|
|
13
|
+
return { history: history.map(h => SharedStepHistorySchema.parse(h)) };
|
|
14
|
+
}
|
|
15
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { SharedStepSchema } from "../../types/testrail.js";
|
|
3
|
+
const parameters = {
|
|
4
|
+
project_id: z.number().describe("The ID of the project"),
|
|
5
|
+
refs: z.string().optional().describe("Filter by a single Reference ID"),
|
|
6
|
+
};
|
|
7
|
+
export const getSharedStepsTool = {
|
|
8
|
+
name: "get_shared_steps",
|
|
9
|
+
description: "Returns a list of shared test steps for a specified project.",
|
|
10
|
+
parameters,
|
|
11
|
+
handler: async (args, client) => {
|
|
12
|
+
const { project_id, refs } = args;
|
|
13
|
+
const sharedSteps = await client.getSharedSteps(project_id, { refs });
|
|
14
|
+
return { shared_steps: sharedSteps.map(s => SharedStepSchema.parse(s)) };
|
|
15
|
+
}
|
|
16
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { SharedStepSchema } from "../../types/testrail.js";
|
|
3
|
+
const parameters = {
|
|
4
|
+
shared_step_id: z.number().describe("The ID of the shared test step to update"),
|
|
5
|
+
title: z.string().optional().describe("The updated title of the shared test step"),
|
|
6
|
+
custom_steps_separated: z.array(z.object({
|
|
7
|
+
content: z.string().describe("The step instruction"),
|
|
8
|
+
expected: z.string().optional().describe("The expected result of the step"),
|
|
9
|
+
})).optional().describe("The updated steps (replaces all existing steps)"),
|
|
10
|
+
};
|
|
11
|
+
const description = `
|
|
12
|
+
Updates an existing set of shared test steps.
|
|
13
|
+
Updates are propagated automatically to all test cases using these steps.
|
|
14
|
+
`;
|
|
15
|
+
export const updateSharedStepTool = {
|
|
16
|
+
name: "update_shared_step",
|
|
17
|
+
description: description.trim(),
|
|
18
|
+
parameters,
|
|
19
|
+
handler: async (args, client) => {
|
|
20
|
+
const { shared_step_id, ...data } = args;
|
|
21
|
+
const sharedStep = await client.updateSharedStep(shared_step_id, data);
|
|
22
|
+
return { shared_step: SharedStepSchema.parse(sharedStep) };
|
|
23
|
+
}
|
|
24
|
+
};
|
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { validateCaseFields } from "../utils/validator.js";
|
|
3
|
+
import { CASE_FIELDS_PARAM_DESCRIPTION } from "./get_case_fields.js";
|
|
3
4
|
const parameters = {
|
|
4
5
|
case_id: z.string().describe("The ID of the test case to update (e.g. '123' or 'C123')"),
|
|
5
|
-
fields: z.record(z.string(), z.any()).describe(
|
|
6
|
+
fields: z.record(z.string(), z.any()).describe(CASE_FIELDS_PARAM_DESCRIPTION),
|
|
6
7
|
};
|
|
8
|
+
const description = `
|
|
9
|
+
Update a test case in TestRail.
|
|
10
|
+
The update operation requires knowing valid field names that are returned by get_case_fields tool.
|
|
11
|
+
Supports partial updates โ only specify the fields you want to change.
|
|
12
|
+
`;
|
|
7
13
|
export const updateCaseTool = {
|
|
8
14
|
name: "update_case",
|
|
9
|
-
description:
|
|
15
|
+
description: description.trim(),
|
|
10
16
|
parameters,
|
|
11
17
|
handler: async ({ case_id, fields }, client) => {
|
|
12
18
|
validateCaseFields(fields, await client.getCaseFields());
|
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { validateCaseFields } from "../utils/validator.js";
|
|
3
|
+
import { CASE_FIELDS_PARAM_DESCRIPTION } from "./get_case_fields.js";
|
|
3
4
|
const parameters = {
|
|
4
5
|
case_ids: z.array(z.number()).min(1).describe("Array of case IDs to update (e.g. [123, 456, 789])"),
|
|
5
|
-
fields: z.record(z.string(), z.any()).describe(
|
|
6
|
+
fields: z.record(z.string(), z.any()).describe(CASE_FIELDS_PARAM_DESCRIPTION),
|
|
6
7
|
};
|
|
8
|
+
const description = `
|
|
9
|
+
Bulk update multiple test cases with the same field values.
|
|
10
|
+
The update operation requires knowing valid field names that are returned by get_case_fields tool.
|
|
11
|
+
More efficient than calling update_case multiple times.
|
|
12
|
+
`;
|
|
7
13
|
export const updateCasesTool = {
|
|
8
14
|
name: "update_cases",
|
|
9
|
-
description:
|
|
15
|
+
description: description.trim(),
|
|
10
16
|
parameters,
|
|
11
17
|
handler: async ({ case_ids, fields }, client) => {
|
|
12
18
|
validateCaseFields(fields, await client.getCaseFields());
|
package/dist/types/testrail.js
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
export const CaseFieldConfigSchema = z.object({
|
|
3
|
+
context: z.object({
|
|
4
|
+
is_global: z.boolean(),
|
|
5
|
+
project_ids: z.array(z.number()),
|
|
6
|
+
}).optional(),
|
|
3
7
|
options: z.object({
|
|
4
8
|
default_value: z.string().optional(),
|
|
5
9
|
is_required: z.boolean().optional(),
|
|
@@ -102,3 +106,20 @@ export const ResultSchema = z.object({
|
|
|
102
106
|
export const AttachmentSchema = z.object({
|
|
103
107
|
attachment_id: z.number(),
|
|
104
108
|
});
|
|
109
|
+
export const SharedStepContentSchema = z.object({
|
|
110
|
+
content: z.string(),
|
|
111
|
+
expected: z.string().nullable().optional(),
|
|
112
|
+
});
|
|
113
|
+
export const SharedStepSchema = z.object({
|
|
114
|
+
id: z.number(),
|
|
115
|
+
title: z.string(),
|
|
116
|
+
project_id: z.number().optional(),
|
|
117
|
+
custom_steps_separated: z.array(SharedStepContentSchema).optional(),
|
|
118
|
+
case_ids: z.array(z.number()).optional(),
|
|
119
|
+
});
|
|
120
|
+
export const SharedStepHistorySchema = z.object({
|
|
121
|
+
id: z.number(),
|
|
122
|
+
title: z.string(),
|
|
123
|
+
timestamp: z.number().optional(),
|
|
124
|
+
custom_steps_separated: z.array(SharedStepContentSchema).optional(),
|
|
125
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uarlouski/testrail-mcp-server",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.9.0",
|
|
4
4
|
"mcpName": "io.github.uarlouski/testrail-mcp-server",
|
|
5
5
|
"description": "Model Context Protocol (MCP) server for TestRail integration, enabling LLMs to fetch test cases and metadata.",
|
|
6
6
|
"main": "dist/index.js",
|