@uarlouski/testrail-mcp-server 1.1.0 → 1.2.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/CHANGELOG.md +7 -0
- package/README.md +52 -16
- package/package.json +1 -2
- package/src/client/testrail.ts +42 -53
- package/src/index.ts +22 -2
- package/src/tools/create_case.ts +6 -13
- package/src/tools/get_case.ts +4 -11
- package/src/tools/get_case_fields.ts +4 -11
- package/src/tools/get_cases.ts +4 -11
- package/src/tools/get_projects.ts +7 -25
- package/src/tools/get_sections.ts +7 -25
- package/src/tools/get_templates.ts +7 -25
- package/src/tools/update_case.ts +6 -13
- package/src/tools/update_cases.ts +7 -14
- package/src/types/custom.ts +1 -16
- package/src/types/testrail.ts +87 -87
- package/src/utils/sanitizer.ts +29 -0
- package/test/client/testrail.test.ts +215 -142
- package/test/tools/create_case.test.ts +10 -16
- package/test/tools/get_case.test.ts +46 -61
- package/test/tools/get_case_fields.test.ts +7 -80
- package/test/tools/get_cases.test.ts +19 -108
- package/test/tools/get_projects.test.ts +4 -41
- package/test/tools/get_sections.test.ts +7 -36
- package/test/tools/get_templates.test.ts +1 -30
- package/test/tools/update_case.test.ts +10 -16
- package/test/tools/update_cases.test.ts +14 -41
- package/test/utils/mapper.test.ts +13 -13
- package/test/utils/sanitizer.test.ts +85 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.2.0] - 2026-02-14
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
|
|
7
|
+
- Optimize MCP call responses by removing non-essential fields and nullish values
|
|
8
|
+
- Migrated from `axios` to native `fetch` API for reduced dependency footprint
|
|
9
|
+
|
|
3
10
|
## [1.1.0] - 2026-02-09
|
|
4
11
|
|
|
5
12
|
### Added
|
package/README.md
CHANGED
|
@@ -1,8 +1,26 @@
|
|
|
1
1
|
# TestRail MCP Server
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/@uarlouski/testrail-mcp-server)
|
|
4
|
+
[](https://github.com/uarlouski/testrail-mcp-server/blob/main/LICENSE)
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
A powerful **Model Context Protocol (MCP)** server for seamless integration between **Large Language Models (LLMs)** and **TestRail** test case management.
|
|
7
|
+
|
|
8
|
+
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.
|
|
9
|
+
|
|
10
|
+
## 🚀 Features
|
|
11
|
+
|
|
12
|
+
- **AI-Powered Test Management**: Bridge the gap between AI and your QA process.
|
|
13
|
+
- **Smart Discovery**: List projects, suites, and sections to help LLMs understand your test organization.
|
|
14
|
+
- **Comprehensive Case Operations**: Fetch, create, and update test cases with full support for custom fields.
|
|
15
|
+
- **Context-Aware**: Designed to provide LLMs with the right context (templates, field options) for accurate test generation.
|
|
16
|
+
|
|
17
|
+
## ⚙️ Usage
|
|
18
|
+
|
|
19
|
+
Configure the server in your MCP client settings (e.g., `claude_desktop_config.json` or Cursor settings).
|
|
20
|
+
|
|
21
|
+
### Claude Desktop / General MCP Clients
|
|
22
|
+
|
|
23
|
+
Add the following to your configuration file:
|
|
6
24
|
|
|
7
25
|
```json
|
|
8
26
|
{
|
|
@@ -10,6 +28,7 @@ A Model Context Protocol (MCP) server for integrating LLMs with TestRail test ca
|
|
|
10
28
|
"testrail": {
|
|
11
29
|
"command": "npx",
|
|
12
30
|
"args": [
|
|
31
|
+
"-y",
|
|
13
32
|
"@uarlouski/testrail-mcp-server@latest"
|
|
14
33
|
],
|
|
15
34
|
"env": {
|
|
@@ -22,20 +41,37 @@ A Model Context Protocol (MCP) server for integrating LLMs with TestRail test ca
|
|
|
22
41
|
}
|
|
23
42
|
```
|
|
24
43
|
|
|
25
|
-
|
|
44
|
+
### Environment Variables
|
|
45
|
+
|
|
46
|
+
| Variable | Description | Required |
|
|
47
|
+
|----------|-------------|:--------:|
|
|
48
|
+
| `TESTRAIL_INSTANCE_URL` | The URL of your TestRail instance (e.g., `https://example.testrail.io`) | Yes |
|
|
49
|
+
| `TESTRAIL_USERNAME` | Your TestRail email address | Yes |
|
|
50
|
+
| `TESTRAIL_API_KEY` | Your TestRail API key (generate in My Settings > API Keys) | Yes |
|
|
51
|
+
|
|
52
|
+
## 🛠️ Available Tools
|
|
53
|
+
|
|
54
|
+
This MCP server exposes the following tools to the LLM:
|
|
26
55
|
|
|
27
56
|
| Tool | Description |
|
|
28
57
|
|------|-------------|
|
|
29
|
-
| `get_projects` |
|
|
30
|
-
| `
|
|
31
|
-
| `
|
|
32
|
-
| `
|
|
33
|
-
| `
|
|
34
|
-
| `
|
|
35
|
-
| `
|
|
36
|
-
| `
|
|
37
|
-
| `
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
58
|
+
| `get_projects` | **Discovery**: List all available projects in the TestRail instance to establish context. |
|
|
59
|
+
| `get_suites` | **Discovery**: Retrieve test suites for a given project (if not in single-suite mode). |
|
|
60
|
+
| `get_sections` | **Structure**: navigate the folder/section hierarchy of a test suite. |
|
|
61
|
+
| `get_cases` | **Search**: Query test cases with filtering (priority, template, type, etc.) and pagination. |
|
|
62
|
+
| `get_case` | **Detail**: Fetch comprehensive details of a specific test case, including steps and custom fields. |
|
|
63
|
+
| `create_case` | **Authoring**: Create new test cases. *Tip: The model should check templates and fields first to ensure validity.* |
|
|
64
|
+
| `update_case` | **Maintenance**: Modify existing test case fields, title, or steps. |
|
|
65
|
+
| `update_cases` | **Bulk Edit**: Update multiple cases simultaneously for efficient refactoring. |
|
|
66
|
+
| `get_case_fields` | **Metadata**: Retrieve definitions of custom fields to understand available inputs. |
|
|
67
|
+
| `get_templates` | **Metadata**: List available case templates to ensure correct case creation structure. |
|
|
68
|
+
| `get_priorities` | **Metadata**: List available priority levels. |
|
|
69
|
+
| `get_case_types` | **Metadata**: List available case types (e.g., Automated, Manual). |
|
|
70
|
+
|
|
71
|
+
## 📜 License
|
|
72
|
+
|
|
73
|
+
This project is licensed under the Apache 2.0 License - see the [LICENSE](LICENSE) file for details.
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
*Keywords: TestRail, MCP, Model Context Protocol, LLM, AI, Claude, Cursor, Windsurf, Test Management, QA, Automation, Testing, API*
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uarlouski/testrail-mcp-server",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "Model Context Protocol (MCP) server for TestRail integration, enabling LLMs to fetch test cases and metadata.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -34,7 +34,6 @@
|
|
|
34
34
|
"license": "Apache-2.0",
|
|
35
35
|
"dependencies": {
|
|
36
36
|
"@modelcontextprotocol/sdk": "^1.25.3",
|
|
37
|
-
"axios": "^1.13.2",
|
|
38
37
|
"dotenv": "^17.2.3",
|
|
39
38
|
"zod": "^4.3.5"
|
|
40
39
|
},
|
package/src/client/testrail.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import axios, { AxiosInstance, AxiosError } from "axios";
|
|
2
1
|
import { Case, Section, Priority, CaseType, CaseField, Template, Project } from "../types/testrail.js";
|
|
3
2
|
|
|
4
3
|
interface PaginatedCasesResponse {
|
|
@@ -10,7 +9,8 @@ const API_INDEX = '/index.php?';
|
|
|
10
9
|
const API_BASE_V2 = `${API_INDEX}/api/v2`;
|
|
11
10
|
|
|
12
11
|
export class TestRailClient {
|
|
13
|
-
private
|
|
12
|
+
private baseUrl: string;
|
|
13
|
+
private headers: HeadersInit;
|
|
14
14
|
private prioritiesPromise: Promise<Priority[]> | null = null;
|
|
15
15
|
private caseTypesPromise: Promise<CaseType[]> | null = null;
|
|
16
16
|
private caseFieldsPromise: Promise<CaseField[]> | null = null;
|
|
@@ -18,25 +18,16 @@ export class TestRailClient {
|
|
|
18
18
|
private templatesPromiseMap: Map<string, Promise<Template[]>> = new Map();
|
|
19
19
|
|
|
20
20
|
constructor(baseUrl: string, email: string, apiKey: string) {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
this.
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
},
|
|
28
|
-
auth: {
|
|
29
|
-
username: email,
|
|
30
|
-
password: apiKey,
|
|
31
|
-
},
|
|
32
|
-
validateStatus: (status: number) => {
|
|
33
|
-
return status >= 200 && status < 300;
|
|
34
|
-
}
|
|
35
|
-
});
|
|
21
|
+
this.baseUrl = baseUrl.replace(/\/$/, "");
|
|
22
|
+
const auth = Buffer.from(`${email}:${apiKey}`).toString('base64');
|
|
23
|
+
this.headers = {
|
|
24
|
+
"Content-Type": "application/json",
|
|
25
|
+
"Authorization": `Basic ${auth}`,
|
|
26
|
+
};
|
|
36
27
|
}
|
|
37
28
|
|
|
38
29
|
async getCase(caseId: string): Promise<Case> {
|
|
39
|
-
return this.
|
|
30
|
+
return this.get<Case>(`${API_BASE_V2}/get_case/${caseId}`);
|
|
40
31
|
}
|
|
41
32
|
|
|
42
33
|
async getCases(projectId: string, sectionId?: string, filter?: Record<string, string>): Promise<Case[]> {
|
|
@@ -56,7 +47,7 @@ export class TestRailClient {
|
|
|
56
47
|
let nextUrl: string | null = url;
|
|
57
48
|
|
|
58
49
|
while (nextUrl) {
|
|
59
|
-
const response: PaginatedCasesResponse = await this.
|
|
50
|
+
const response: PaginatedCasesResponse = await this.get<PaginatedCasesResponse>(nextUrl);
|
|
60
51
|
|
|
61
52
|
allCases.push(...response.cases);
|
|
62
53
|
|
|
@@ -70,26 +61,26 @@ export class TestRailClient {
|
|
|
70
61
|
}
|
|
71
62
|
|
|
72
63
|
async getSection(sectionId: string): Promise<Section> {
|
|
73
|
-
return this.
|
|
64
|
+
return this.get<Section>(`${API_BASE_V2}/get_section/${sectionId}`);
|
|
74
65
|
}
|
|
75
66
|
|
|
76
67
|
async getPriorities(): Promise<Priority[]> {
|
|
77
68
|
if (!this.prioritiesPromise) {
|
|
78
|
-
this.prioritiesPromise = this.
|
|
69
|
+
this.prioritiesPromise = this.get<Priority[]>(`${API_BASE_V2}/get_priorities`);
|
|
79
70
|
}
|
|
80
71
|
return this.prioritiesPromise;
|
|
81
72
|
}
|
|
82
73
|
|
|
83
74
|
async getCaseTypes(): Promise<CaseType[]> {
|
|
84
75
|
if (!this.caseTypesPromise) {
|
|
85
|
-
this.caseTypesPromise = this.
|
|
76
|
+
this.caseTypesPromise = this.get<CaseType[]>(`${API_BASE_V2}/get_case_types`);
|
|
86
77
|
}
|
|
87
78
|
return this.caseTypesPromise;
|
|
88
79
|
}
|
|
89
80
|
|
|
90
81
|
async getCaseFields(): Promise<CaseField[]> {
|
|
91
82
|
if (!this.caseFieldsPromise) {
|
|
92
|
-
this.caseFieldsPromise = this.
|
|
83
|
+
this.caseFieldsPromise = this.get<CaseField[]>(`${API_BASE_V2}/get_case_fields`);
|
|
93
84
|
}
|
|
94
85
|
return this.caseFieldsPromise;
|
|
95
86
|
}
|
|
@@ -98,21 +89,21 @@ export class TestRailClient {
|
|
|
98
89
|
if (!this.templatesPromiseMap.has(projectId)) {
|
|
99
90
|
this.templatesPromiseMap.set(
|
|
100
91
|
projectId,
|
|
101
|
-
this.
|
|
92
|
+
this.get<Template[]>(`${API_BASE_V2}/get_templates/${projectId}`)
|
|
102
93
|
);
|
|
103
94
|
}
|
|
104
95
|
return this.templatesPromiseMap.get(projectId)!;
|
|
105
96
|
}
|
|
106
97
|
|
|
107
98
|
async updateCase(caseId: string, fields: Record<string, any>): Promise<Case> {
|
|
108
|
-
return this.
|
|
99
|
+
return this.post<Case>(`${API_BASE_V2}/update_case/${caseId}`, fields);
|
|
109
100
|
}
|
|
110
101
|
|
|
111
102
|
async updateCases(caseIds: number[], fields: Record<string, any>): Promise<Case[]> {
|
|
112
103
|
// Assume the project is operating in single suite mode, get suite_id from the first case
|
|
113
104
|
const caseData = await this.getCase(String(caseIds[0]));
|
|
114
105
|
|
|
115
|
-
const response = await this.
|
|
106
|
+
const response = await this.post<{ updated_cases: Case[] }>(`${API_BASE_V2}/update_cases/${caseData.suite_id}`, {
|
|
116
107
|
case_ids: caseIds,
|
|
117
108
|
...fields,
|
|
118
109
|
});
|
|
@@ -121,52 +112,50 @@ export class TestRailClient {
|
|
|
121
112
|
}
|
|
122
113
|
|
|
123
114
|
async createCase(sectionId: string, fields: Record<string, any>): Promise<Case> {
|
|
124
|
-
return this.
|
|
115
|
+
return this.post<Case>(`${API_BASE_V2}/add_case/${sectionId}`, fields);
|
|
125
116
|
}
|
|
126
117
|
|
|
127
118
|
async getSections(projectId: string): Promise<Section[]> {
|
|
128
|
-
const response = await this.
|
|
119
|
+
const response = await this.get<{ sections: Section[] }>(`${API_BASE_V2}/get_sections/${projectId}`);
|
|
129
120
|
return response.sections;
|
|
130
121
|
}
|
|
131
122
|
|
|
132
123
|
async getProjects(): Promise<Project[]> {
|
|
133
124
|
if (!this.projectsPromise) {
|
|
134
|
-
this.projectsPromise = this.
|
|
125
|
+
this.projectsPromise = this.get<{ projects: Project[] }>(`${API_BASE_V2}/get_projects`)
|
|
135
126
|
.then(response => response.projects);
|
|
136
127
|
}
|
|
137
128
|
return this.projectsPromise;
|
|
138
129
|
}
|
|
139
130
|
|
|
140
|
-
private async
|
|
141
|
-
|
|
142
|
-
const response = await this.client.get<T>(url);
|
|
143
|
-
return response.data;
|
|
144
|
-
} catch (error: any) {
|
|
145
|
-
throw this.handleError(error);
|
|
146
|
-
}
|
|
131
|
+
private async get<T>(endpoint: string): Promise<T> {
|
|
132
|
+
return this.executeRequest<T>('GET', endpoint);
|
|
147
133
|
}
|
|
148
134
|
|
|
149
|
-
private async
|
|
150
|
-
|
|
151
|
-
const response = await this.client.post<T>(url, data);
|
|
152
|
-
return response.data;
|
|
153
|
-
} catch (error: any) {
|
|
154
|
-
throw this.handleError(error);
|
|
155
|
-
}
|
|
135
|
+
private async post<T>(endpoint: string, data: Record<string, any>): Promise<T> {
|
|
136
|
+
return this.executeRequest<T>('POST', endpoint, data);
|
|
156
137
|
}
|
|
157
138
|
|
|
158
|
-
private
|
|
159
|
-
|
|
139
|
+
private async executeRequest<T>(method: 'GET' | 'POST', endpoint: string, data?: Record<string, any>): Promise<T> {
|
|
140
|
+
const url = `${this.baseUrl}${endpoint}`;
|
|
141
|
+
const params: RequestInit = {
|
|
142
|
+
method,
|
|
143
|
+
headers: this.headers,
|
|
144
|
+
};
|
|
160
145
|
|
|
161
|
-
if (
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
146
|
+
if (data) {
|
|
147
|
+
params.body = JSON.stringify(data);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const response = await fetch(url, params);
|
|
151
|
+
|
|
152
|
+
if (!response.ok) {
|
|
153
|
+
let errorMessage = `TestRail: ${response.status} ${response.statusText}`;
|
|
154
|
+
const errorText = await response.text();
|
|
155
|
+
errorMessage += ` - ${errorText}`;
|
|
156
|
+
throw new Error(errorMessage);
|
|
168
157
|
}
|
|
169
158
|
|
|
170
|
-
return
|
|
159
|
+
return await response.json() as T;
|
|
171
160
|
}
|
|
172
161
|
}
|
package/src/index.ts
CHANGED
|
@@ -11,6 +11,7 @@ import { updateCasesTool } from "./tools/update_cases.js";
|
|
|
11
11
|
import { createCaseTool } from "./tools/create_case.js";
|
|
12
12
|
import { getSectionsTool } from "./tools/get_sections.js";
|
|
13
13
|
import { getProjectsTool } from "./tools/get_projects.js";
|
|
14
|
+
import { removeNullish } from "./utils/sanitizer.js";
|
|
14
15
|
|
|
15
16
|
const TESTRAIL_INSTANCE_URL = process.env.TESTRAIL_INSTANCE_URL;
|
|
16
17
|
const TESTRAIL_USERNAME = process.env.TESTRAIL_USERNAME;
|
|
@@ -23,7 +24,7 @@ if (!TESTRAIL_INSTANCE_URL || !TESTRAIL_USERNAME || !TESTRAIL_API_KEY) {
|
|
|
23
24
|
|
|
24
25
|
const server = new McpServer({
|
|
25
26
|
name: "TestRail MCP Server",
|
|
26
|
-
version: "1.
|
|
27
|
+
version: "1.2.0",
|
|
27
28
|
});
|
|
28
29
|
|
|
29
30
|
const client = new TestRailClient(TESTRAIL_INSTANCE_URL, TESTRAIL_USERNAME, TESTRAIL_API_KEY);
|
|
@@ -47,7 +48,26 @@ for (const tool of tools) {
|
|
|
47
48
|
description: tool.description,
|
|
48
49
|
inputSchema: tool.parameters,
|
|
49
50
|
},
|
|
50
|
-
(args: any) =>
|
|
51
|
+
async (args: any) => {
|
|
52
|
+
try {
|
|
53
|
+
const output: Record<string, any> = await tool.handler(args, client);
|
|
54
|
+
const sanitized = removeNullish(output);
|
|
55
|
+
|
|
56
|
+
return {
|
|
57
|
+
content: [
|
|
58
|
+
{
|
|
59
|
+
type: "text" as const,
|
|
60
|
+
text: JSON.stringify(sanitized, null, 2),
|
|
61
|
+
},
|
|
62
|
+
],
|
|
63
|
+
} as any;
|
|
64
|
+
} catch (error: any) {
|
|
65
|
+
return {
|
|
66
|
+
content: [{ type: "text", text: `Error: ${error.message}` }],
|
|
67
|
+
isError: true,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
}
|
|
51
71
|
);
|
|
52
72
|
}
|
|
53
73
|
|
package/src/tools/create_case.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { TestRailClient } from "../client/testrail.js";
|
|
3
|
-
import { ToolDefinition
|
|
3
|
+
import { ToolDefinition } from "../types/custom.js";
|
|
4
4
|
|
|
5
5
|
const parameters = {
|
|
6
6
|
section_id: z.string().describe("The ID of the section where the case should be created"),
|
|
@@ -12,7 +12,7 @@ export const createCaseTool: ToolDefinition<typeof parameters, TestRailClient> =
|
|
|
12
12
|
name: "create_case",
|
|
13
13
|
description: "Create a new test case in TestRail. IMPORTANT: Before creating a case, first call get_templates to discover available templates for the project, then use get_case_fields to see which fields are available for the chosen template. Requires section_id and title.",
|
|
14
14
|
parameters,
|
|
15
|
-
handler:
|
|
15
|
+
handler: async ({ section_id, title, fields }, client) => {
|
|
16
16
|
const caseData = {
|
|
17
17
|
title,
|
|
18
18
|
...fields,
|
|
@@ -21,16 +21,9 @@ export const createCaseTool: ToolDefinition<typeof parameters, TestRailClient> =
|
|
|
21
21
|
const createdCase = await client.createCase(section_id, caseData);
|
|
22
22
|
|
|
23
23
|
return {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
text: JSON.stringify({
|
|
28
|
-
success: true,
|
|
29
|
-
case_id: createdCase.id,
|
|
30
|
-
message: `Test case C${createdCase.id} created successfully`,
|
|
31
|
-
}, null, 2),
|
|
32
|
-
},
|
|
33
|
-
],
|
|
24
|
+
success: true,
|
|
25
|
+
case_id: createdCase.id,
|
|
26
|
+
message: `Test case C${createdCase.id} created successfully`,
|
|
34
27
|
};
|
|
35
|
-
}
|
|
28
|
+
}
|
|
36
29
|
};
|
package/src/tools/get_case.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { processCustomFields } from "../utils/mapper.js";
|
|
3
3
|
import { TestRailClient } from "../client/testrail.js";
|
|
4
|
-
import { TestCaseResponse, ToolDefinition
|
|
4
|
+
import { TestCaseResponse, ToolDefinition } from "../types/custom.js";
|
|
5
5
|
|
|
6
6
|
const parameters = {
|
|
7
7
|
case_id: z.string().describe("The ID of the test case (e.g. '123' or 'C123')"),
|
|
@@ -11,7 +11,7 @@ export const getCaseTool: ToolDefinition<typeof parameters, TestRailClient> = {
|
|
|
11
11
|
name: "get_case",
|
|
12
12
|
description: "Get a test case from TestRail by ID",
|
|
13
13
|
parameters,
|
|
14
|
-
handler:
|
|
14
|
+
handler: async ({ case_id }, client) => {
|
|
15
15
|
const id = case_id.toUpperCase().startsWith("C") ? case_id.substring(1) : case_id;
|
|
16
16
|
const testCase = await client.getCase(id);
|
|
17
17
|
|
|
@@ -39,13 +39,6 @@ export const getCaseTool: ToolDefinition<typeof parameters, TestRailClient> = {
|
|
|
39
39
|
...customFields
|
|
40
40
|
};
|
|
41
41
|
|
|
42
|
-
return
|
|
43
|
-
|
|
44
|
-
{
|
|
45
|
-
type: "text" as const,
|
|
46
|
-
text: JSON.stringify(response, null, 2),
|
|
47
|
-
},
|
|
48
|
-
],
|
|
49
|
-
};
|
|
50
|
-
})
|
|
42
|
+
return response;
|
|
43
|
+
}
|
|
51
44
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { TestRailClient } from "../client/testrail.js";
|
|
2
|
-
import { ToolDefinition
|
|
2
|
+
import { ToolDefinition } from "../types/custom.js";
|
|
3
3
|
import { CaseField } from "../types/testrail.js";
|
|
4
4
|
import { parseDropdownOptions } from "../utils/mapper.js";
|
|
5
5
|
|
|
@@ -76,7 +76,7 @@ export const getCaseFieldsTool: ToolDefinition<typeof parameters, TestRailClient
|
|
|
76
76
|
name: "get_case_fields",
|
|
77
77
|
description: "Get the field schema for test cases. Returns all available fields with their types, descriptions, and options (for dropdown fields). Use this to understand what fields can be set when creating or updating test cases.",
|
|
78
78
|
parameters,
|
|
79
|
-
handler:
|
|
79
|
+
handler: async (_args, client) => {
|
|
80
80
|
const caseFields = await client.getCaseFields();
|
|
81
81
|
|
|
82
82
|
const customFieldSchemas = caseFields.filter(field => field.is_active).map(mapToFieldSchema);
|
|
@@ -85,13 +85,6 @@ export const getCaseFieldsTool: ToolDefinition<typeof parameters, TestRailClient
|
|
|
85
85
|
fields: [...SYSTEM_FIELDS, ...customFieldSchemas],
|
|
86
86
|
};
|
|
87
87
|
|
|
88
|
-
return
|
|
89
|
-
|
|
90
|
-
{
|
|
91
|
-
type: "text" as const,
|
|
92
|
-
text: JSON.stringify(response, null, 2),
|
|
93
|
-
},
|
|
94
|
-
],
|
|
95
|
-
};
|
|
96
|
-
})
|
|
88
|
+
return response;
|
|
89
|
+
}
|
|
97
90
|
};
|
package/src/tools/get_cases.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { TestRailClient } from "../client/testrail.js";
|
|
3
|
-
import { ToolDefinition
|
|
3
|
+
import { ToolDefinition } from "../types/custom.js";
|
|
4
4
|
import { Case } from "../types/testrail.js";
|
|
5
5
|
|
|
6
6
|
const parameters = {
|
|
@@ -19,7 +19,7 @@ export const getCasesTool: ToolDefinition<typeof parameters, TestRailClient> = {
|
|
|
19
19
|
name: "get_cases",
|
|
20
20
|
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.",
|
|
21
21
|
parameters,
|
|
22
|
-
handler:
|
|
22
|
+
handler: async ({ project_id, section_id, filter, where, fields }, client) => {
|
|
23
23
|
let cases: Case[] = await client.getCases(project_id, section_id, filter);
|
|
24
24
|
|
|
25
25
|
// Client-side filtering for custom fields and other unsupported API filters
|
|
@@ -55,13 +55,6 @@ export const getCasesTool: ToolDefinition<typeof parameters, TestRailClient> = {
|
|
|
55
55
|
}),
|
|
56
56
|
};
|
|
57
57
|
|
|
58
|
-
return
|
|
59
|
-
|
|
60
|
-
{
|
|
61
|
-
type: "text" as const,
|
|
62
|
-
text: JSON.stringify(response, null, 2),
|
|
63
|
-
},
|
|
64
|
-
],
|
|
65
|
-
};
|
|
66
|
-
})
|
|
58
|
+
return response;
|
|
59
|
+
}
|
|
67
60
|
};
|
|
@@ -1,40 +1,22 @@
|
|
|
1
1
|
import { TestRailClient } from "../client/testrail.js";
|
|
2
|
-
import { ToolDefinition
|
|
2
|
+
import { ToolDefinition } from "../types/custom.js";
|
|
3
|
+
import { ProjectSchema } from "../types/testrail.js";
|
|
3
4
|
|
|
4
5
|
const parameters = {};
|
|
5
6
|
|
|
6
|
-
interface ProjectSchema {
|
|
7
|
-
id: number;
|
|
8
|
-
name: string;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
interface ProjectsResponse {
|
|
12
|
-
projects: ProjectSchema[];
|
|
13
|
-
}
|
|
14
|
-
|
|
15
7
|
export const getProjectsTool: ToolDefinition<typeof parameters, TestRailClient> = {
|
|
16
8
|
name: "get_projects",
|
|
17
9
|
description: "Get all available projects in TestRail. Returns project IDs and names. Use this to find the project_id needed for get_sections and get_templates.",
|
|
18
10
|
parameters,
|
|
19
|
-
handler:
|
|
11
|
+
handler: async (_args, client) => {
|
|
20
12
|
const projects = await client.getProjects();
|
|
21
13
|
|
|
22
|
-
const response
|
|
14
|
+
const response = {
|
|
23
15
|
projects: projects
|
|
24
16
|
.filter(p => !p.is_completed)
|
|
25
|
-
.map(p => (
|
|
26
|
-
id: p.id,
|
|
27
|
-
name: p.name,
|
|
28
|
-
})),
|
|
17
|
+
.map(p => ProjectSchema.parse(p)),
|
|
29
18
|
};
|
|
30
19
|
|
|
31
|
-
return
|
|
32
|
-
|
|
33
|
-
{
|
|
34
|
-
type: "text" as const,
|
|
35
|
-
text: JSON.stringify(response, null, 2),
|
|
36
|
-
},
|
|
37
|
-
],
|
|
38
|
-
};
|
|
39
|
-
})
|
|
20
|
+
return response;
|
|
21
|
+
}
|
|
40
22
|
};
|
|
@@ -1,41 +1,23 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { TestRailClient } from "../client/testrail.js";
|
|
3
|
-
import { ToolDefinition
|
|
3
|
+
import { ToolDefinition } from "../types/custom.js";
|
|
4
|
+
import { SectionSchema } from "../types/testrail.js";
|
|
4
5
|
|
|
5
6
|
const parameters = {
|
|
6
7
|
project_id: z.string().describe("The ID of the project"),
|
|
7
8
|
};
|
|
8
9
|
|
|
9
|
-
interface SectionSchema {
|
|
10
|
-
id: number;
|
|
11
|
-
name: string;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
interface SectionsResponse {
|
|
15
|
-
sections: SectionSchema[];
|
|
16
|
-
}
|
|
17
|
-
|
|
18
10
|
export const getSectionsTool: ToolDefinition<typeof parameters, TestRailClient> = {
|
|
19
11
|
name: "get_sections",
|
|
20
12
|
description: "Get all sections for a project. Returns section IDs and names. Use this to find the section_id needed for create_case.",
|
|
21
13
|
parameters,
|
|
22
|
-
handler:
|
|
14
|
+
handler: async ({ project_id }, client) => {
|
|
23
15
|
const sections = await client.getSections(project_id);
|
|
24
16
|
|
|
25
|
-
const response
|
|
26
|
-
sections: sections.map(s => (
|
|
27
|
-
id: s.id,
|
|
28
|
-
name: s.name,
|
|
29
|
-
})),
|
|
17
|
+
const response = {
|
|
18
|
+
sections: sections.map(s => SectionSchema.parse(s)),
|
|
30
19
|
};
|
|
31
20
|
|
|
32
|
-
return
|
|
33
|
-
|
|
34
|
-
{
|
|
35
|
-
type: "text" as const,
|
|
36
|
-
text: JSON.stringify(response, null, 2),
|
|
37
|
-
},
|
|
38
|
-
],
|
|
39
|
-
};
|
|
40
|
-
})
|
|
21
|
+
return response;
|
|
22
|
+
}
|
|
41
23
|
};
|
|
@@ -1,41 +1,23 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { TestRailClient } from "../client/testrail.js";
|
|
3
|
-
import { ToolDefinition
|
|
3
|
+
import { ToolDefinition } from "../types/custom.js";
|
|
4
|
+
import { TemplateSchema } from "../types/testrail.js";
|
|
4
5
|
|
|
5
6
|
const parameters = {
|
|
6
7
|
project_id: z.string().describe("The ID of the project"),
|
|
7
8
|
};
|
|
8
9
|
|
|
9
|
-
interface TemplateSchema {
|
|
10
|
-
id: number;
|
|
11
|
-
name: string;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
interface TemplatesResponse {
|
|
15
|
-
templates: TemplateSchema[];
|
|
16
|
-
}
|
|
17
|
-
|
|
18
10
|
export const getTemplatesTool: ToolDefinition<typeof parameters, TestRailClient> = {
|
|
19
11
|
name: "get_templates",
|
|
20
12
|
description: "Get available test case templates for a project. Returns template IDs and names. Use template IDs with get_case_fields to understand which fields apply to each template.",
|
|
21
13
|
parameters,
|
|
22
|
-
handler:
|
|
14
|
+
handler: async ({ project_id }, client) => {
|
|
23
15
|
const templates = await client.getTemplates(project_id);
|
|
24
16
|
|
|
25
|
-
const response
|
|
26
|
-
templates: templates.map(t => (
|
|
27
|
-
id: t.id,
|
|
28
|
-
name: t.name,
|
|
29
|
-
})),
|
|
17
|
+
const response = {
|
|
18
|
+
templates: templates.map(t => TemplateSchema.parse(t)),
|
|
30
19
|
};
|
|
31
20
|
|
|
32
|
-
return
|
|
33
|
-
|
|
34
|
-
{
|
|
35
|
-
type: "text" as const,
|
|
36
|
-
text: JSON.stringify(response, null, 2),
|
|
37
|
-
},
|
|
38
|
-
],
|
|
39
|
-
};
|
|
40
|
-
})
|
|
21
|
+
return response;
|
|
22
|
+
}
|
|
41
23
|
};
|