@uarlouski/testrail-mcp-server 2.0.0 → 2.1.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/dist/client/testrail.js
CHANGED
|
@@ -25,7 +25,9 @@ export class TestRailClient {
|
|
|
25
25
|
return this.get(`${API_BASE_V2}/get_case/${caseId}`);
|
|
26
26
|
}
|
|
27
27
|
async getCasesRecursively(projectId, sectionId, filter, excludedSectionNames) {
|
|
28
|
-
|
|
28
|
+
// Extract suite_id from filter if present, for getSections call
|
|
29
|
+
const suiteId = filter?.suite_id ? parseInt(filter.suite_id) : undefined;
|
|
30
|
+
const sections = await this.getSections(projectId, suiteId);
|
|
29
31
|
const sectionIds = this.getSubSectionIds(sections, sectionId, excludedSectionNames);
|
|
30
32
|
sectionIds.push(sectionId);
|
|
31
33
|
const promises = sectionIds.map(id => this.getCases(projectId, id, filter));
|
|
@@ -105,8 +107,11 @@ export class TestRailClient {
|
|
|
105
107
|
async updateRun(runId, fields) {
|
|
106
108
|
return this.post(`${API_BASE_V2}/update_run/${runId}`, fields);
|
|
107
109
|
}
|
|
108
|
-
async getSections(projectId) {
|
|
109
|
-
|
|
110
|
+
async getSections(projectId, suiteId) {
|
|
111
|
+
let url = `${API_BASE_V2}/get_sections/${projectId}`;
|
|
112
|
+
if (suiteId) {
|
|
113
|
+
url += `&suite_id=${suiteId}`;
|
|
114
|
+
}
|
|
110
115
|
return this.paginateAll(url, 'sections');
|
|
111
116
|
}
|
|
112
117
|
async addSection(projectId, data) {
|
|
@@ -152,8 +157,7 @@ export class TestRailClient {
|
|
|
152
157
|
}
|
|
153
158
|
async getProjects() {
|
|
154
159
|
if (!this.projectsPromise) {
|
|
155
|
-
this.projectsPromise = this.
|
|
156
|
-
.then(response => response.projects);
|
|
160
|
+
this.projectsPromise = this.paginateAll(`${API_BASE_V2}/get_projects`, 'projects');
|
|
157
161
|
}
|
|
158
162
|
return this.projectsPromise;
|
|
159
163
|
}
|
|
@@ -221,6 +225,12 @@ export class TestRailClient {
|
|
|
221
225
|
let nextUrl = url;
|
|
222
226
|
while (nextUrl) {
|
|
223
227
|
const response = await this.get(nextUrl);
|
|
228
|
+
// Handle older TestRail versions (pre-7.x) that return flat arrays
|
|
229
|
+
// instead of paginated objects like { "suites": [...], "_links": {...} }
|
|
230
|
+
if (Array.isArray(response)) {
|
|
231
|
+
allItems.push(...response);
|
|
232
|
+
break;
|
|
233
|
+
}
|
|
224
234
|
if (response[dataKey] && Array.isArray(response[dataKey])) {
|
|
225
235
|
allItems.push(...response[dataKey]);
|
|
226
236
|
}
|
package/dist/index.js
CHANGED
|
@@ -23,7 +23,7 @@ if (!parseResult.success) {
|
|
|
23
23
|
const { TESTRAIL_INSTANCE_URL, TESTRAIL_USERNAME, TESTRAIL_API_KEY, TESTRAIL_ENABLE_SHARED_STEPS, TESTRAIL_ALLOW_WRITE_OPERATIONS, TESTRAIL_ALLOW_READ_OPERATIONS, TESTRAIL_ALLOW_DELETE_OPERATIONS } = parseResult.data;
|
|
24
24
|
const server = new McpServer({
|
|
25
25
|
name: "TestRail MCP Server",
|
|
26
|
-
version: "2.
|
|
26
|
+
version: "2.1.0",
|
|
27
27
|
});
|
|
28
28
|
const client = new TestRailClient(TESTRAIL_INSTANCE_URL, TESTRAIL_USERNAME, TESTRAIL_API_KEY);
|
|
29
29
|
const tools = getToolsToRegister({
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import fs from "fs";
|
|
3
|
-
import { validateCaseFields } from "../../utils/validator.js";
|
|
3
|
+
import { validateCaseFields, validateSuiteId } from "../../utils/validator.js";
|
|
4
4
|
const parameters = {
|
|
5
5
|
project_id: z.number().describe("The ID of the project. Use get_projects to find available projects"),
|
|
6
|
+
suite_id: z.number().optional().describe("The ID of the test suite (required for multi-suite projects, i.e. suite_mode=3). Use get_suites to find available suites"),
|
|
6
7
|
section: z.object({
|
|
7
8
|
id: z.number(),
|
|
8
9
|
recursive: z.boolean().optional().default(false).describe("If true, fetches cases from the section and all its child sections"),
|
|
@@ -18,7 +19,8 @@ export const getCasesTool = {
|
|
|
18
19
|
mode: "read",
|
|
19
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.",
|
|
20
21
|
parameters,
|
|
21
|
-
handler: async ({ project_id, section, filter, where, fields, output_file }, client) => {
|
|
22
|
+
handler: async ({ project_id, suite_id, section, filter, where, fields, output_file }, client) => {
|
|
23
|
+
await validateSuiteId(client, project_id, suite_id);
|
|
22
24
|
const caseFields = await client.getCaseFields();
|
|
23
25
|
if (fields) {
|
|
24
26
|
validateCaseFields(fields, caseFields);
|
|
@@ -26,6 +28,10 @@ export const getCasesTool = {
|
|
|
26
28
|
if (where) {
|
|
27
29
|
validateCaseFields(Object.keys(where), caseFields);
|
|
28
30
|
}
|
|
31
|
+
// Inject suite_id into filter for multi-suite projects
|
|
32
|
+
if (suite_id) {
|
|
33
|
+
filter = { ...filter, suite_id: suite_id.toString() };
|
|
34
|
+
}
|
|
29
35
|
let cases;
|
|
30
36
|
if (section?.recursive) {
|
|
31
37
|
cases = await client.getCasesRecursively(project_id, section.id, filter, section.excludes);
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import fs from "fs";
|
|
3
3
|
import { SectionSchema } from "./types.js";
|
|
4
|
+
import { validateSuiteId } from "../../utils/validator.js";
|
|
4
5
|
const parameters = {
|
|
5
6
|
project_id: z.number().describe("The ID of the project. Use get_projects to find available projects"),
|
|
7
|
+
suite_id: z.number().optional().describe("The ID of the test suite (required for multi-suite projects, i.e. suite_mode=3). Use get_suites to find available suites"),
|
|
6
8
|
output_file: z.string().optional().describe("Absolute file path to save the JSON response to. Use this for large datasets to avoid blowing up context limits."),
|
|
7
9
|
};
|
|
8
10
|
export const getSectionsTool = {
|
|
@@ -10,8 +12,9 @@ export const getSectionsTool = {
|
|
|
10
12
|
mode: "read",
|
|
11
13
|
description: "Get all sections for a project. Returns section IDs and names that can be used with add_case",
|
|
12
14
|
parameters,
|
|
13
|
-
handler: async ({ project_id, output_file }, client) => {
|
|
14
|
-
|
|
15
|
+
handler: async ({ project_id, suite_id, output_file }, client) => {
|
|
16
|
+
await validateSuiteId(client, project_id, suite_id);
|
|
17
|
+
const sections = await client.getSections(project_id, suite_id);
|
|
15
18
|
const response = {
|
|
16
19
|
sections: sections.map(s => SectionSchema.parse(s)),
|
|
17
20
|
};
|
package/dist/utils/validator.js
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
import { SYSTEM_FIELDS, mapToFieldSchema } from "../tools/cases/get_case_fields.js";
|
|
2
2
|
import { isActive } from "./sanitizer.js";
|
|
3
|
+
const SUITE_MODE_MULTI = 3;
|
|
4
|
+
/**
|
|
5
|
+
* Validates that suite_id is provided when the project uses multiple test suites (suite_mode=3).
|
|
6
|
+
* Throws a descriptive Error if suite_id is required but missing.
|
|
7
|
+
*/
|
|
8
|
+
export async function validateSuiteId(client, projectId, suiteId) {
|
|
9
|
+
if (suiteId)
|
|
10
|
+
return;
|
|
11
|
+
const project = await client.getProject(projectId);
|
|
12
|
+
if (project.suite_mode === SUITE_MODE_MULTI) {
|
|
13
|
+
throw new Error(`Project "${project.name}" (id: ${project.id}) uses multiple test suites (suite_mode=3). ` +
|
|
14
|
+
`The suite_id parameter is required. Use get_suites to find available suites for this project.`);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
3
17
|
/**
|
|
4
18
|
* Validates that the provided fields exist in the TestRail case schema.
|
|
5
19
|
* Throws an Error if any field is invalid.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uarlouski/testrail-mcp-server",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.1.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",
|