@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 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
- A Model Context Protocol (MCP) server for integrating LLMs with TestRail test case management.
3
+ [![npm version](https://img.shields.io/npm/v/@uarlouski/testrail-mcp-server.svg)](https://www.npmjs.com/package/@uarlouski/testrail-mcp-server)
4
+ [![License](https://img.shields.io/npm/l/@uarlouski/testrail-mcp-server.svg)](https://github.com/uarlouski/testrail-mcp-server/blob/main/LICENSE)
4
5
 
5
- ## Usage
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
- ## Tools
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` | Discover available projects in your TestRail instance |
30
- | `get_case` | Fetch detailed test case info including custom fields |
31
- | `get_cases` | Query cases with filtering and pagination support |
32
- | `get_case_fields` | Explore available fields and dropdown options |
33
- | `get_templates` | List project templates to understand case structures |
34
- | `get_sections` | Navigate the test case hierarchy |
35
- | `create_case` | Create new test cases with full custom field support |
36
- | `update_case` | Modify a single test case |
37
- | `update_cases` | Bulk update multiple cases at once |
38
-
39
- ## License
40
-
41
- Apache 2.0
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.1.0",
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
  },
@@ -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 client: AxiosInstance;
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
- const cleanedBaseUrl = baseUrl.replace(/\/$/, "");
22
-
23
- this.client = axios.create({
24
- baseURL: cleanedBaseUrl,
25
- headers: {
26
- "Content-Type": "application/json",
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.makeRequest<Case>(`${API_BASE_V2}/get_case/${caseId}`);
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.makeRequest<PaginatedCasesResponse>(nextUrl);
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.makeRequest<Section>(`${API_BASE_V2}/get_section/${sectionId}`);
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.makeRequest<Priority[]>(`${API_BASE_V2}/get_priorities`);
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.makeRequest<CaseType[]>(`${API_BASE_V2}/get_case_types`);
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.makeRequest<CaseField[]>(`${API_BASE_V2}/get_case_fields`);
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.makeRequest<Template[]>(`${API_BASE_V2}/get_templates/${projectId}`)
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.postRequest<Case>(`${API_BASE_V2}/update_case/${caseId}`, fields);
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.postRequest<{ updated_cases: Case[] }>(`${API_BASE_V2}/update_cases/${caseData.suite_id}`, {
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.postRequest<Case>(`${API_BASE_V2}/add_case/${sectionId}`, fields);
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.makeRequest<{ sections: Section[] }>(`${API_BASE_V2}/get_sections/${projectId}`);
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.makeRequest<{ projects: Project[] }>(`${API_BASE_V2}/get_projects`)
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 makeRequest<T>(url: string): Promise<T> {
141
- try {
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 postRequest<T>(url: string, data: Record<string, any>): Promise<T> {
150
- try {
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 handleError(error: any): Error {
159
- let errorMessage = `TestRail API error: ${error.message}`;
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 (axios.isAxiosError(error)) {
162
- const axiosError = error as AxiosError;
163
- if (axiosError.response) {
164
- errorMessage = `TestRail API error: ${axiosError.response.status} ${axiosError.response.statusText} - ${JSON.stringify(axiosError.response.data)}`;
165
- } else if (axiosError.request) {
166
- errorMessage = `TestRail API error: No response received. ${axiosError.message}`;
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 new Error(errorMessage);
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.1.0",
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) => tool.handler(args, client)
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
 
@@ -1,6 +1,6 @@
1
1
  import { z } from "zod";
2
2
  import { TestRailClient } from "../client/testrail.js";
3
- import { ToolDefinition, withErrorHandling } from "../types/custom.js";
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: withErrorHandling<typeof parameters, TestRailClient>(async ({ section_id, title, fields }, client) => {
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
- content: [
25
- {
26
- type: "text" as const,
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
  };
@@ -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, withErrorHandling } from "../types/custom.js";
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: withErrorHandling<typeof parameters, TestRailClient>(async ({ case_id }, client) => {
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
- content: [
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, withErrorHandling } from "../types/custom.js";
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: withErrorHandling<typeof parameters, TestRailClient>(async (_args, client) => {
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
- content: [
90
- {
91
- type: "text" as const,
92
- text: JSON.stringify(response, null, 2),
93
- },
94
- ],
95
- };
96
- })
88
+ return response;
89
+ }
97
90
  };
@@ -1,6 +1,6 @@
1
1
  import { z } from "zod";
2
2
  import { TestRailClient } from "../client/testrail.js";
3
- import { ToolDefinition, withErrorHandling } from "../types/custom.js";
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: withErrorHandling<typeof parameters, TestRailClient>(async ({ project_id, section_id, filter, where, fields }, client) => {
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
- content: [
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, withErrorHandling } from "../types/custom.js";
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: withErrorHandling<typeof parameters, TestRailClient>(async (_args, client) => {
11
+ handler: async (_args, client) => {
20
12
  const projects = await client.getProjects();
21
13
 
22
- const response: ProjectsResponse = {
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
- content: [
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, withErrorHandling } from "../types/custom.js";
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: withErrorHandling<typeof parameters, TestRailClient>(async ({ project_id }, client) => {
14
+ handler: async ({ project_id }, client) => {
23
15
  const sections = await client.getSections(project_id);
24
16
 
25
- const response: SectionsResponse = {
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
- content: [
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, withErrorHandling } from "../types/custom.js";
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: withErrorHandling<typeof parameters, TestRailClient>(async ({ project_id }, client) => {
14
+ handler: async ({ project_id }, client) => {
23
15
  const templates = await client.getTemplates(project_id);
24
16
 
25
- const response: TemplatesResponse = {
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
- content: [
34
- {
35
- type: "text" as const,
36
- text: JSON.stringify(response, null, 2),
37
- },
38
- ],
39
- };
40
- })
21
+ return response;
22
+ }
41
23
  };