@uarlouski/testrail-mcp-server 2.9.0 β†’ 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,10 +1,10 @@
1
1
  <div align="center">
2
2
  <h1>πŸš€ TestRail MCP Server</h1>
3
3
  <p>
4
- <strong>An open-source Model Context Protocol (MCP) server that connects Claude, Cursor, Windsurf, and other AI assistants directly to TestRail.</strong>
4
+ <strong>Connect Claude, Cursor, Windsurf, and VS Code to TestRail β€” an open-source Model Context Protocol (MCP) server for AI-assisted test management.</strong>
5
5
  </p>
6
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.
7
+ Manage TestRail projects, search and create test cases, kick off test runs, record results, and attach files through natural-language conversation with your AI assistant β€” or from CI/CD with the bundled <code>testrail-cli</code>. Built for QA engineers and AI-assisted test automation.
8
8
  </p>
9
9
  </div>
10
10
 
@@ -16,6 +16,7 @@
16
16
  <a href="https://www.typescriptlang.org/"><img src="https://img.shields.io/badge/TypeScript-5.9-blue.svg" alt="TypeScript"></a>
17
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>
18
18
  <a href="https://glama.ai/mcp/servers/uarlouski/testrail-mcp-server"><img src="https://glama.ai/mcp/servers/uarlouski/testrail-mcp-server/badges/score.svg" alt="Score Badge"></a>
19
+ <a href="https://m8ven.ai/mcp/uarlouski/testrail-mcp-server"><img src="https://m8ven.ai/badge/mcp/uarlouski/testrail-mcp-server" alt="M8ven Score"></a>
19
20
  </p>
20
21
 
21
22
  <p align="center">
@@ -26,18 +27,38 @@
26
27
  <img src="https://img.shields.io/badge/VS%20Code-007ACC?logo=visualstudiocode&logoColor=white" alt="VS Code">
27
28
  </p>
28
29
 
30
+ <p align="center">
31
+ <a href="https://uarlouski.github.io/testrail-mcp-server/"><strong>πŸ“š Documentation</strong></a> Β·
32
+ <a href="https://uarlouski.github.io/testrail-mcp-server/guide/getting-started"><strong>Getting Started</strong></a> Β·
33
+ <a href="https://uarlouski.github.io/testrail-mcp-server/reference/"><strong>Tool Reference</strong></a> Β·
34
+ <a href="https://uarlouski.github.io/testrail-mcp-server/guide/faq"><strong>FAQ</strong></a>
35
+ </p>
36
+
29
37
  <!-- TODO: insert demo.gif β€” short screencast of asking "create a test case" in Claude or Cursor and seeing the case appear in TestRail -->
30
38
 
31
39
  ---
32
40
 
33
- ## 🌟 Why Choose TestRail MCP Server?
41
+ ## What is the TestRail MCP Server?
34
42
 
35
- Managing test cases manually is tedious and error-prone. With the **TestRail MCP Server**, your AI assistant (whether it’s **Claude, Cursor, Windsurf**, or any MCP-compliant client) interacts directly with your TestRail instance. Instruct it to find test cases, draft new ones, kick off test runs, and record test resultsβ€”all through natural conversation.
43
+ The **TestRail MCP Server** is a free, open-source [Model Context Protocol](https://modelcontextprotocol.io) server that gives AI assistants direct, structured access to a [TestRail](https://www.testrail.com/) instance through the TestRail API v2. Once configured, an assistant such as **Claude Desktop, Cursor, Windsurf, or GitHub Copilot in VS Code** can search test cases, draft new ones, start test runs, record results, and upload attachments on your behalf β€” without you leaving the chat window.
44
+
45
+ It exposes **34 tools**, runs locally on **Node.js 18+** over the MCP stdio transport, and is licensed under **Apache 2.0**. There is nothing to host or deploy: your MCP client launches it on demand with `npx`.
36
46
 
37
47
  **No context switching. No tedious copy-pasting. Just ask your AI.**
38
48
 
39
49
  > [!NOTE]
40
- > **Compatibility Baseline**: The primary baseline version this MCP server is tested and validated against is **TestRail 10.6.2** (API v2). Older TestRail instances (including pre-7.x pagination) are also supported via built-in backward compatibility.
50
+ > **Compatibility baseline**: tested and validated against **TestRail 10.6.2** (API v2). Older TestRail instances (including pre-7.x pagination) are also supported via built-in backward compatibility. TestRail Cloud and self-hosted TestRail Server both work.
51
+
52
+ ## Table of contents
53
+
54
+ - [Key features](#-key-features--capabilities)
55
+ - [Quick start](#-quick-start-guide)
56
+ - [Command line interface & CI/CD](#️-command-line-interface-cli--cicd-automation)
57
+ - [Environment variables](#️-environment-variables--security-controls)
58
+ - [Available tools](#-available-tools)
59
+ - [FAQ](#-frequently-asked-questions)
60
+ - [Documentation](#-documentation--complete-tool-reference)
61
+ - [Contributing](#-contributing)
41
62
 
42
63
  ## ✨ Key Features & Capabilities
43
64
 
@@ -47,16 +68,18 @@ Managing test cases manually is tedious and error-prone. With the **TestRail MCP
47
68
  | **πŸ“‹ Full Case Management** | Fetch, create, update, and bulk-edit test cases with comprehensive custom field support. |
48
69
  | **▢️ Actionable Execution** | Create test runs, update results by `test_id` or `case_id`, attach files, and track statuses. |
49
70
  | **🧠 Context-Aware AI** | Dynamically exposes templates, fields, priorities, and statuses so LLMs generate valid, structured data. |
71
+ | **πŸ–₯️ CLI & CI/CD Native** | Run every tool from bash, GitHub Actions, GitLab CI, or Jenkins with zero LLM overhead. |
72
+ | **πŸ” Least-Privilege Controls** | Per-mode permissions plus per-tool allowlisting; destructive deletes are off by default. |
50
73
 
51
74
  ## πŸš€ Quick Start Guide
52
75
 
53
- ### 1. Obtain Your TestRail API Key
76
+ ### 1. Obtain your TestRail API key
54
77
 
55
- Navigate to **My Settings β†’ API Keys** in your TestRail platform and generate a new key for authentication.
78
+ Navigate to **My Settings β†’ API Keys** in TestRail and generate a new key. Copy it immediately β€” TestRail shows it only once. The API must also be enabled instance-wide under **Administration β†’ Site Settings β†’ API**.
56
79
 
57
- ### 2. Configure Your MCP Client
80
+ ### 2. Configure your MCP client
58
81
 
59
- 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).
82
+ Add the server to your MCP client configuration. The Claude Desktop example is shown below; Cursor, Windsurf, and VS Code use the same pattern (see the collapsible sections).
60
83
 
61
84
  #### πŸ€– Claude Desktop
62
85
 
@@ -82,7 +105,7 @@ Add this to your `claude_desktop_config.json`:
82
105
  <details>
83
106
  <summary><strong>⌨️ Cursor</strong></summary>
84
107
 
85
- Open **Settings β†’ Features β†’ MCP** and add a new configuration:
108
+ Open **Settings β†’ MCP β†’ Add new MCP server**, or edit `.cursor/mcp.json` in your project (`~/.cursor/mcp.json` for all projects):
86
109
 
87
110
  ```json
88
111
  {
@@ -104,7 +127,7 @@ Open **Settings β†’ Features β†’ MCP** and add a new configuration:
104
127
  <details>
105
128
  <summary><strong>🌊 Windsurf</strong></summary>
106
129
 
107
- Update your Windsurf MCP configuration file:
130
+ Edit `~/.codeium/windsurf/mcp_config.json`:
108
131
 
109
132
  ```json
110
133
  {
@@ -123,22 +146,120 @@ Update your Windsurf MCP configuration file:
123
146
  ```
124
147
  </details>
125
148
 
149
+ <details>
150
+ <summary><strong>πŸ’» VS Code</strong></summary>
151
+
152
+ Add to `.vscode/mcp.json`. The `inputs` block keeps your API key out of the file:
153
+
154
+ ```json
155
+ {
156
+ "inputs": [
157
+ {
158
+ "id": "testrail-api-key",
159
+ "type": "promptString",
160
+ "description": "TestRail API key",
161
+ "password": true
162
+ }
163
+ ],
164
+ "servers": {
165
+ "testrail": {
166
+ "command": "npx",
167
+ "args": ["-y", "@uarlouski/testrail-mcp-server@latest"],
168
+ "env": {
169
+ "TESTRAIL_INSTANCE_URL": "https://your-instance.testrail.io",
170
+ "TESTRAIL_USERNAME": "your@email.com",
171
+ "TESTRAIL_API_KEY": "${input:testrail-api-key}"
172
+ }
173
+ }
174
+ }
175
+ }
176
+ ```
177
+ </details>
178
+
126
179
  <details>
127
180
  <summary><strong>🌐 Other MCP Clients</strong></summary>
128
181
 
129
- Any MCP-compliant client can utilize this server. The pattern is universalβ€”point your client at the `npx` command with the required environment variables.
182
+ Any MCP-compliant client can use this server, because it speaks the standard MCP **stdio** transport. Point your client at the `npx` command with the required environment variables β€” no port, URL, or transport configuration needed.
130
183
  </details>
131
184
 
132
- ### 3. See It in Action
185
+ ### 3. See it in action
133
186
 
134
- Once configured, turbo-charge your QA workflow by asking your AI assistant:
187
+ Restart your client completely, then turbo-charge your QA workflow by asking your AI assistant:
135
188
 
136
189
  - *"List all projects in TestRail to find the latest active project."*
137
190
  - *"Show me all active users in the project to find the right assignee."*
138
191
  - *"Show me all test cases in section 5 of project 3."*
139
192
  - *"Create a comprehensive test case for 'Login Validation' with detailed steps."*
140
193
  - *"Start a new test run containing cases from section 5."*
141
- - *"Mark test case ID 1042 as passed with the comment 'Tested successfully on staging'."*
194
+ - *"Mark test case C1042 as passed with the comment 'verified on staging'."*
195
+
196
+ Full per-client setup instructions, including troubleshooting, are in the [Getting Started guide](https://uarlouski.github.io/testrail-mcp-server/guide/getting-started).
197
+
198
+ ---
199
+
200
+ ## πŸ–₯️ Command Line Interface (CLI) & CI/CD Automation
201
+
202
+ In addition to interacting via AI assistants, you can invoke any TestRail tool directly from shell scripts, terminal environments, and automated CI/CD pipelines (GitHub Actions, GitLab CI, Jenkins) using **`testrail-cli`** or **`npx`** β€” with zero LLM overhead.
203
+
204
+ - **Deterministic Execution**: Returns standard Unix exit codes (`0` on success, `1` on error).
205
+ - **Pipeline-Native Output**: Emits clean JSON to `stdout` for piping into tools like `jq`, while diagnostics and errors go to `stderr`.
206
+ - **Zero Duplication**: Reuses the exact same API client, retry logic, and validation schemas as the MCP server.
207
+
208
+ ### Invocation methods
209
+
210
+ ```bash
211
+ # Method 1: Direct npx subcommand (Recommended)
212
+ npx @uarlouski/testrail-mcp-server cli <command> [flags]
213
+
214
+ # Method 2: Global or local binary
215
+ testrail-cli <command> [flags]
216
+
217
+ # Method 3: Via package runner
218
+ npx -p @uarlouski/testrail-mcp-server testrail-cli <command> [flags]
219
+ ```
220
+
221
+ ### Examples
222
+
223
+ #### Query projects (`query_project`)
224
+ ```bash
225
+ # List all active projects
226
+ npx @uarlouski/testrail-mcp-server cli query_project --action many
227
+
228
+ # Query a single project by ID
229
+ npx @uarlouski/testrail-mcp-server cli query_project --action one --project_id 1
230
+ ```
231
+
232
+ #### Report automated test results (`add_results_for_cases`)
233
+ ```bash
234
+ # Submit results by case_id β€” what your test framework already knows
235
+ npx @uarlouski/testrail-mcp-server cli add_results_for_cases \
236
+ --run_id 88 \
237
+ --results '[{"case_id":1042,"status_id":1,"comment":"Passed in CI"}]'
238
+ ```
239
+
240
+ #### Export cases for knowledge base / RAG (`export_cases_for_rag`)
241
+ ```bash
242
+ # Export all cases for a project into Markdown & metadata sidecars
243
+ npx @uarlouski/testrail-mcp-server cli export_cases_for_rag \
244
+ --project_id 1 \
245
+ --output_dir ./rag_exports
246
+
247
+ # Export specific cases by ID (comma-separated list)
248
+ npx @uarlouski/testrail-mcp-server cli export_cases_for_rag \
249
+ --case_ids C101,C102,103 \
250
+ --output_dir ./rag_exports
251
+ ```
252
+
253
+ #### Command discovery & flag documentation
254
+ ```bash
255
+ # List all available commands
256
+ npx @uarlouski/testrail-mcp-server cli --help
257
+
258
+ # Show parameter options for a specific tool
259
+ npx @uarlouski/testrail-mcp-server cli query_project --help
260
+ ```
261
+
262
+ See the [CLI & CI/CD guide](https://uarlouski.github.io/testrail-mcp-server/guide/cli) for complete GitHub Actions, GitLab CI, and Jenkins workflows.
142
263
 
143
264
  ---
144
265
 
@@ -155,43 +276,102 @@ Once configured, turbo-charge your QA workflow by asking your AI assistant:
155
276
  | `TESTRAIL_ALLOW_WRITE_OPERATIONS` | Allow write operations (e.g. adding/updating test cases, test runs, sections) | | `true` |
156
277
  | `TESTRAIL_ALLOW_READ_OPERATIONS` | Allow read operations (e.g. retrieving projects, test cases, templates) | | `true` |
157
278
  | `TESTRAIL_ALLOW_DELETE_OPERATIONS` | Allow delete operations (e.g. deleting cases or shared steps). Enabled strictly via `true`. | | `false` |
158
- | `TESTRAIL_ENABLE_DEPRECATED_TOOLS` | Enable deprecated tools for backward compatibility. Set to `false` to reduce context token overhead. | | `true` |
279
+ | `TESTRAIL_ENABLE_DEPRECATED_TOOLS` | Preserved for backward compatibility with existing host configurations. | | `true` |
159
280
  | `TESTRAIL_DISABLED_TOOLS` | Comma-separated list of specific tool names to disable (e.g., `mutate_suite,delete_entity`). Fails if invalid tool names are specified. | | - |
160
281
 
161
- ### ⚠️ Deprecation Lifecycle & Features Scheduled for Removal
282
+ With only the three required credentials, **26 of the 34 tools** are registered. Ready-made read-only and least-privilege configurations are in the [Configuration guide](https://uarlouski.github.io/testrail-mcp-server/guide/configuration).
283
+
284
+ ---
285
+
286
+ ## 🧰 Available Tools
287
+
288
+ All 34 tools, grouped by area. Each declares a `read`, `write`, or `delete` mode, which the server surfaces to clients as MCP annotations (`readOnlyHint`, `destructiveHint`, `idempotentHint`).
289
+
290
+ | Area | Tools | Reference |
291
+ |------|-------|-----------|
292
+ | **Discovery & Navigation** | `query_project`, `query_suite`, `mutate_suite`, `query_section`, `mutate_section`, `get_users` | [Docs](https://uarlouski.github.io/testrail-mcp-server/reference/discovery) |
293
+ | **Test Case Management** | `get_case`, `get_cases`, `add_case`, `update_case`, `update_cases`, `get_case_fields`, `resolve_case_field`, `get_case_history`, `export_cases_for_rag` | [Docs](https://uarlouski.github.io/testrail-mcp-server/reference/cases) |
294
+ | **Execution & Tracking** | `query_run`, `mutate_run`, `get_tests`, `get_results`, `add_results`, `add_results_for_cases` | [Docs](https://uarlouski.github.io/testrail-mcp-server/reference/execution) |
295
+ | **Attachments & Media** | `add_attachment`, `query_attachment` | [Docs](https://uarlouski.github.io/testrail-mcp-server/reference/attachments) |
296
+ | **Shared Steps** | `get_shared_step`, `get_shared_steps`, `get_shared_step_history`, `add_shared_step`, `update_shared_step` | [Docs](https://uarlouski.github.io/testrail-mcp-server/reference/shared-steps) |
297
+ | **System Metadata** | `get_statuses`, `get_priorities`, `get_case_fields`, `get_templates`, `get_labels`, `get_configurations` | [Docs](https://uarlouski.github.io/testrail-mcp-server/reference/metadata) |
298
+ | **Deletion** | `delete_entity` | [Docs](https://uarlouski.github.io/testrail-mcp-server/reference/deletion) |
299
+
300
+ ---
301
+
302
+ ## ❓ Frequently Asked Questions
303
+
304
+ <details>
305
+ <summary><strong>Which AI assistants and MCP clients are supported?</strong></summary>
306
+
307
+ Any MCP-compliant client, because the server uses the standard MCP stdio transport. Setup is verified with Claude Desktop, Cursor, Windsurf, and VS Code. Other clients follow the same pattern: run `npx -y @uarlouski/testrail-mcp-server@latest` with three environment variables.
308
+ </details>
309
+
310
+ <details>
311
+ <summary><strong>Which TestRail versions are supported?</strong></summary>
312
+
313
+ The server targets TestRail API v2 and is tested against **TestRail 10.6.2**. Older instances work too β€” the client detects whether an endpoint returns a modern paginated response or a legacy bare array, so pre-7.x instances need no configuration. Both TestRail Cloud and self-hosted TestRail Server are supported.
314
+ </details>
315
+
316
+ <details>
317
+ <summary><strong>Is it safe to give an AI assistant write access to TestRail?</strong></summary>
318
+
319
+ Access is layered rather than all-or-nothing. Every tool declares a `read`, `write`, or `delete` mode; deletes are disabled unless you explicitly set `TESTRAIL_ALLOW_DELETE_OPERATIONS=true`. You can disable all writes with `TESTRAIL_ALLOW_WRITE_OPERATIONS=false`, or block individual tools by name with `TESTRAIL_DISABLED_TOOLS`. The server runs locally and talks only to your TestRail instance β€” there is no telemetry and no third-party service in the middle.
320
+ </details>
162
321
 
163
- To ensure smooth transitions, deprecated tools remain available by default (`TESTRAIL_ENABLE_DEPRECATED_TOOLS=true`) and will be removed in future major releases:
322
+ <details>
323
+ <summary><strong>Is it free?</strong></summary>
164
324
 
165
- | Deprecated Tool | Replacement | Status |
166
- |-----------------|-------------|--------|
167
- | `add_attachment_to_run` | `add_attachment` (`entity_type: "case" \| "run"`) | Deprecated in `2.3.0`, scheduled for removal in `3.0.0` |
168
- | `get_sections` | `query_section` (`action: "many"`) | Deprecated in `2.8.0`, scheduled for removal in `3.0.0` |
325
+ Yes β€” Apache 2.0 licensed, with no paid tier, licence key, or usage limit. You need your own TestRail subscription, and whatever your AI assistant costs. The CLI has no LLM cost at all.
326
+ </details>
169
327
 
170
- > **πŸ’‘ Token Tip**: If you are not using legacy tools, set `TESTRAIL_ENABLE_DEPRECATED_TOOLS=false` in your environment to eliminate deprecated tool definitions from the LLM prompt and save tokens!
328
+ <details>
329
+ <summary><strong>Can I use it in CI/CD without an AI assistant?</strong></summary>
330
+
331
+ Yes. The package ships a `testrail-cli` binary exposing every tool as a subcommand, reusing the same API client, retry logic, and validation schemas. JSON on stdout, diagnostics on stderr, exit code `0` or `1` β€” see the [CLI guide](https://uarlouski.github.io/testrail-mcp-server/guide/cli).
332
+ </details>
333
+
334
+ <details>
335
+ <summary><strong>How do I stop the AI from inventing custom fields that don't exist?</strong></summary>
336
+
337
+ It can't. Before `add_case` or `update_case` sends anything, the server validates every field key against your instance's real schema (fetched via `get_case_fields`) and rejects unknown keys. Templates, priorities, statuses, and configurations are all exposed as tools too, so the model looks up correct IDs instead of guessing them.
338
+ </details>
339
+
340
+ <details>
341
+ <summary><strong>How do I handle thousands of test cases without blowing the context window?</strong></summary>
342
+
343
+ Pass `output_file` to `get_cases` (or `output_dir` to `export_cases_for_rag`). The server paginates the full result set, writes raw JSON to disk, and returns only a short summary to the model.
344
+ </details>
345
+
346
+ More answers in the [full FAQ](https://uarlouski.github.io/testrail-mcp-server/guide/faq).
171
347
 
172
348
  ---
173
349
 
174
350
  ## πŸ“š Documentation & Complete Tool Reference
175
351
 
176
- For a comprehensive guide, detailed configuration options, and a complete breakdown of all available tools, please visit our official documentation site:
352
+ For a comprehensive guide, detailed configuration options, and a complete breakdown of all available tools, visit the official documentation site:
177
353
 
178
354
  **πŸ‘‰ [TestRail MCP Server Documentation](https://uarlouski.github.io/testrail-mcp-server/)**
179
355
 
180
- The documentation includes detailed explanations for:
181
- - πŸ”­ **Discovery & Navigation**: Exploring projects, suites, and sections.
182
- - πŸ“‹ **Test Case Management**: Fetching, creating, and bulk-updating test cases.
183
- - ▢️ **Execution & Tracking**: Managing test runs and submitting test results.
184
- - πŸ“Ž **Attachments**: Automatically zipping and uploading files or directories.
185
- - πŸ”— **Shared Steps**: Managing reusable step definitions.
186
-
356
+ - πŸš€ **[Getting Started](https://uarlouski.github.io/testrail-mcp-server/guide/getting-started)**: Per-client setup for Claude, Cursor, Windsurf, and VS Code.
357
+ - βš™οΈ **[Configuration](https://uarlouski.github.io/testrail-mcp-server/guide/configuration)**: Every environment variable, permission, and feature flag.
358
+ - 🧰 **[All Tools](https://uarlouski.github.io/testrail-mcp-server/reference/)**: All 34 tools with modes and feature flags in one table.
359
+ - πŸ”­ **[Discovery & Navigation](https://uarlouski.github.io/testrail-mcp-server/reference/discovery)**: Exploring projects, suites, and sections.
360
+ - πŸ“‹ **[Test Case Management](https://uarlouski.github.io/testrail-mcp-server/reference/cases)**: Fetching, creating, and bulk-updating test cases.
361
+ - ▢️ **[Execution & Tracking](https://uarlouski.github.io/testrail-mcp-server/reference/execution)**: Managing test runs and submitting test results.
362
+ - πŸ“Ž **[Attachments](https://uarlouski.github.io/testrail-mcp-server/reference/attachments)**: Automatically zipping and uploading files or directories.
363
+ - πŸ”— **[Shared Steps](https://uarlouski.github.io/testrail-mcp-server/reference/shared-steps)**: Managing reusable step definitions.
364
+ - πŸ–₯️ **[CLI & CI/CD](https://uarlouski.github.io/testrail-mcp-server/guide/cli)**: Pipeline automation without an LLM.
187
365
 
188
366
  ---
189
367
 
190
368
  ## 🀝 Contributing
369
+
191
370
  Open-source contributions are actively welcomed! Please feel free to open an [issue](https://github.com/uarlouski/testrail-mcp-server/issues) for feature requests or submit a pull request for improvements.
192
371
 
193
372
  ## πŸ“œ License
194
- This project is securely licensed under the [Apache License 2.0](LICENSE).
373
+
374
+ This project is licensed under the [Apache License 2.0](LICENSE).
195
375
 
196
376
  ---
197
377
 
@@ -0,0 +1,187 @@
1
+ /**
2
+ * Parses raw command-line arguments into a command name, tool flags, and global connection options.
3
+ */
4
+ export function parseArgs(argv) {
5
+ let command;
6
+ const flags = {};
7
+ const globalOptions = {};
8
+ for (let i = 0; i < argv.length; i++) {
9
+ const arg = argv[i];
10
+ if (arg === "-h" || arg === "--help") {
11
+ globalOptions.help = true;
12
+ flags.help = true;
13
+ }
14
+ else if (arg === "-v" || arg === "--version") {
15
+ globalOptions.version = true;
16
+ flags.version = true;
17
+ }
18
+ else if (!arg.startsWith("-")) {
19
+ if (!command) {
20
+ command = arg;
21
+ }
22
+ }
23
+ else {
24
+ let [rawKey, val] = arg.replace(/^--?/, "").split("=");
25
+ let isNegative = false;
26
+ if (rawKey.startsWith("no-")) {
27
+ rawKey = rawKey.slice(3);
28
+ isNegative = true;
29
+ }
30
+ const key = rawKey.replace(/-/g, "_");
31
+ if (val === undefined) {
32
+ if (isNegative) {
33
+ val = "false";
34
+ }
35
+ else if (i + 1 < argv.length && !argv[i + 1].startsWith("-")) {
36
+ val = argv[++i];
37
+ }
38
+ else {
39
+ val = "true";
40
+ }
41
+ }
42
+ if (key === "url") {
43
+ globalOptions.url = val;
44
+ }
45
+ else if (key === "username") {
46
+ globalOptions.username = val;
47
+ }
48
+ else if (key === "api_key" || key === "apikey") {
49
+ globalOptions.apiKey = val;
50
+ }
51
+ else {
52
+ flags[key] = val === "true" ? true : val === "false" ? false : val;
53
+ }
54
+ }
55
+ }
56
+ return { command, flags, globalOptions };
57
+ }
58
+ /**
59
+ * Coerces primitive CLI strings to numbers, booleans, JSON objects, or arrays.
60
+ */
61
+ export function coerceValue(raw) {
62
+ if (raw === undefined || raw === null || typeof raw === "boolean") {
63
+ return raw;
64
+ }
65
+ if (typeof raw === "string") {
66
+ const trimmed = raw.trim();
67
+ if (trimmed === "true") {
68
+ return true;
69
+ }
70
+ if (trimmed === "false") {
71
+ return false;
72
+ }
73
+ // JSON objects or arrays
74
+ if ((trimmed.startsWith("{") && trimmed.endsWith("}")) || (trimmed.startsWith("[") && trimmed.endsWith("]"))) {
75
+ try {
76
+ return JSON.parse(trimmed);
77
+ }
78
+ catch {
79
+ /* fall through */
80
+ }
81
+ }
82
+ // Comma-separated array list
83
+ if (trimmed.includes(",")) {
84
+ return trimmed.split(",").map(item => coerceValue(item.trim()));
85
+ }
86
+ // Numeric string
87
+ if (!isNaN(Number(trimmed)) && trimmed !== "") {
88
+ return Number(trimmed);
89
+ }
90
+ return raw;
91
+ }
92
+ if (Array.isArray(raw)) {
93
+ return raw.map(item => coerceValue(item));
94
+ }
95
+ return raw;
96
+ }
97
+ /**
98
+ * Coerces flat CLI flags and auto-wraps into `{ payload: ... }` for discriminated union tools.
99
+ */
100
+ export function coerceFlagsForTool(rawFlags, parameters) {
101
+ const cleanFlags = {};
102
+ for (const [key, val] of Object.entries(rawFlags)) {
103
+ if (key === "help" || key === "version" || key === "_") {
104
+ continue;
105
+ }
106
+ cleanFlags[key] = coerceValue(val);
107
+ }
108
+ // Auto-wrap into payload if the tool expects a payload container (e.g. query_project)
109
+ if (parameters.payload && !cleanFlags.payload) {
110
+ return { payload: cleanFlags };
111
+ }
112
+ return cleanFlags;
113
+ }
114
+ /**
115
+ * Formats general CLI help documentation.
116
+ */
117
+ export function formatGeneralHelp(tools) {
118
+ const maxLen = Math.max(...tools.map(t => t.name.length), 20);
119
+ const cmdList = tools
120
+ .map(t => ` ${t.name.padEnd(maxLen + 2, " ")}${t.description.split("\n")[0]}`)
121
+ .join("\n");
122
+ return [
123
+ "TestRail CLI - Execute TestRail actions directly in terminal & CI/CD",
124
+ "",
125
+ "Usage: testrail-cli <command> [options]",
126
+ "",
127
+ "Commands:",
128
+ cmdList,
129
+ "",
130
+ "Global Options:",
131
+ " --url <url> TestRail instance URL (overrides TESTRAIL_INSTANCE_URL)",
132
+ " --username <email> TestRail username (overrides TESTRAIL_USERNAME)",
133
+ " --api-key <key> TestRail API key (overrides TESTRAIL_API_KEY)",
134
+ " -h, --help Show help documentation",
135
+ " -v, --version Show CLI version",
136
+ "",
137
+ "Run 'testrail-cli <command> --help' for command-specific flags."
138
+ ].join("\n");
139
+ }
140
+ /**
141
+ * Formats tool-specific help documentation.
142
+ */
143
+ export function formatToolHelp(tool) {
144
+ const shapes = [];
145
+ const params = tool.parameters;
146
+ if (params.payload) {
147
+ const payloadDef = params.payload.def;
148
+ if (Array.isArray(payloadDef?.options)) {
149
+ for (const opt of payloadDef.options) {
150
+ shapes.push(opt.shape);
151
+ }
152
+ }
153
+ }
154
+ else {
155
+ shapes.push(params);
156
+ }
157
+ const seen = new Set();
158
+ const options = [];
159
+ for (const shape of shapes) {
160
+ for (const [name, fieldSchema] of Object.entries(shape)) {
161
+ if (seen.has(name)) {
162
+ continue;
163
+ }
164
+ seen.add(name);
165
+ const def = fieldSchema?.def;
166
+ const isOptional = def?.type === "optional";
167
+ const innerDef = isOptional ? def?.innerType?.def : def;
168
+ const type = innerDef?.type || "string";
169
+ const desc = fieldSchema?.description || "";
170
+ const req = isOptional ? "(Optional)" : "(Required)";
171
+ options.push(` --${name} <${type}> ${desc} ${req}`.trimEnd());
172
+ }
173
+ }
174
+ return [
175
+ `Command: ${tool.name}`,
176
+ `Description: ${tool.description}`,
177
+ "",
178
+ "Options:",
179
+ ...options,
180
+ "",
181
+ "Global Options:",
182
+ " --url <url> TestRail instance URL",
183
+ " --username <email> TestRail username",
184
+ " --api-key <key> TestRail API key",
185
+ " -h, --help Show help for this command"
186
+ ].join("\n");
187
+ }
package/dist/cli.js ADDED
@@ -0,0 +1,108 @@
1
+ #!/usr/bin/env node
2
+ import "dotenv/config";
3
+ import { z } from "zod";
4
+ import { TestRailClient } from "./client/testrail.js";
5
+ import { ALL_TOOLS } from "./tools/registry.js";
6
+ import { removeNullish } from "./utils/sanitizer.js";
7
+ import { parseArgs, coerceFlagsForTool, formatGeneralHelp, formatToolHelp } from "./adapters/cli_parser.js";
8
+ import { VERSION } from "./version.js";
9
+ const CredentialSchema = z.object({
10
+ url: z.url("Must be a valid TestRail URL (e.g. https://example.testrail.io)"),
11
+ username: z.email("Must be a valid email address"),
12
+ apiKey: z.string().min(1, "API key is required"),
13
+ });
14
+ export function resolveClient(options, env) {
15
+ const instanceUrl = options.url || env.TESTRAIL_INSTANCE_URL;
16
+ const username = options.username || env.TESTRAIL_USERNAME;
17
+ const apiKey = options.apiKey || env.TESTRAIL_API_KEY;
18
+ const credResult = CredentialSchema.safeParse({
19
+ url: instanceUrl,
20
+ username,
21
+ apiKey,
22
+ });
23
+ if (!credResult.success) {
24
+ console.error("Error: Missing or invalid TestRail credentials.");
25
+ for (const issue of credResult.error.issues) {
26
+ console.error(` - ${issue.path.join(".")}: ${issue.message}`);
27
+ }
28
+ console.error("\nPlease set TESTRAIL_INSTANCE_URL, TESTRAIL_USERNAME, and TESTRAIL_API_KEY environment variables,");
29
+ console.error("or supply them via --url, --username, and --api-key options.");
30
+ return null;
31
+ }
32
+ return new TestRailClient(credResult.data.url, credResult.data.username, credResult.data.apiKey);
33
+ }
34
+ export function validateToolArgs(tool, flags) {
35
+ const coercedArgs = coerceFlagsForTool(flags, tool.parameters);
36
+ const ToolSchema = z.object(tool.parameters);
37
+ const validationResult = ToolSchema.safeParse(coercedArgs);
38
+ if (!validationResult.success) {
39
+ console.error(`Validation error for command "${tool.name}":`);
40
+ for (const issue of validationResult.error.issues) {
41
+ console.error(` - ${issue.path.join(".")}: ${issue.message}`);
42
+ }
43
+ console.error(`\nRun 'testrail-cli ${tool.name} --help' for flag specifications.`);
44
+ return null;
45
+ }
46
+ return validationResult.data;
47
+ }
48
+ export async function executeTool(tool, client, args) {
49
+ try {
50
+ const output = await tool.handler(args, client);
51
+ const sanitized = removeNullish(output);
52
+ console.log(JSON.stringify(sanitized, null, 2));
53
+ return 0;
54
+ }
55
+ catch (error) {
56
+ console.error(`Error executing "${tool.name}": ${error.message || error}`);
57
+ return 1;
58
+ }
59
+ }
60
+ export async function runCli(argv, env = process.env, clientOverride) {
61
+ const { command, flags, globalOptions } = parseArgs(argv);
62
+ if (globalOptions.version) {
63
+ console.log(`testrail-cli v${VERSION}`);
64
+ return 0;
65
+ }
66
+ if (!command) {
67
+ if (globalOptions.help) {
68
+ console.log(formatGeneralHelp(ALL_TOOLS));
69
+ return 0;
70
+ }
71
+ console.error("Error: No command specified.\n");
72
+ console.error(formatGeneralHelp(ALL_TOOLS));
73
+ return 1;
74
+ }
75
+ const tool = ALL_TOOLS.find(t => t.name === command);
76
+ if (!tool) {
77
+ console.error(`Error: Unknown command "${command}".\n`);
78
+ console.error("Run 'testrail-cli --help' to view available commands.");
79
+ return 1;
80
+ }
81
+ if (globalOptions.help) {
82
+ console.log(formatToolHelp(tool));
83
+ return 0;
84
+ }
85
+ const client = clientOverride || resolveClient(globalOptions, env);
86
+ if (!client) {
87
+ return 1;
88
+ }
89
+ const toolArgs = validateToolArgs(tool, flags);
90
+ if (!toolArgs) {
91
+ return 1;
92
+ }
93
+ return executeTool(tool, client, toolArgs);
94
+ }
95
+ // Direct execution from CLI
96
+ const isDirectExecution = process.argv[1] && (process.argv[1].endsWith("/cli.js") ||
97
+ process.argv[1].endsWith("/cli.ts") ||
98
+ process.argv[1].endsWith("/testrail-cli"));
99
+ if (isDirectExecution) {
100
+ runCli(process.argv.slice(2)).then(code => {
101
+ if (code !== 0) {
102
+ process.exit(code);
103
+ }
104
+ }).catch(err => {
105
+ console.error("Unexpected error:", err);
106
+ process.exit(1);
107
+ });
108
+ }
package/dist/index.js CHANGED
@@ -5,7 +5,17 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
5
5
  import { TestRailClient } from "./client/testrail.js";
6
6
  import { getToolsToRegister } from "./tools/registry.js";
7
7
  import { removeNullish } from "./utils/sanitizer.js";
8
+ import { VERSION } from "./version.js";
8
9
  import z from "zod";
10
+ // Allow delegating to CLI directly when invoked via 'cli' subcommand (e.g. npx @uarlouski/testrail-mcp-server cli ...)
11
+ const isDirectExecution = process.argv[1] && (process.argv[1].endsWith("/index.js") ||
12
+ process.argv[1].endsWith("/index.ts") ||
13
+ process.argv[1].endsWith("/testrail-mcp-server"));
14
+ if (isDirectExecution && process.argv[2] === "cli") {
15
+ const { runCli } = await import("./cli.js");
16
+ const code = await runCli(process.argv.slice(3));
17
+ process.exit(code);
18
+ }
9
19
  const EnvSchema = z.object({
10
20
  TESTRAIL_INSTANCE_URL: z.url('Must be a valid TestRail URL'),
11
21
  TESTRAIL_USERNAME: z.email('Must be a valid email address'),
@@ -27,7 +37,7 @@ if (!parseResult.success) {
27
37
  const { TESTRAIL_INSTANCE_URL, TESTRAIL_USERNAME, TESTRAIL_API_KEY, TESTRAIL_ENABLE_SHARED_STEPS, TESTRAIL_ENABLE_CASE_HISTORY, TESTRAIL_ENABLE_RAG_TOOLS, TESTRAIL_ALLOW_WRITE_OPERATIONS, TESTRAIL_ALLOW_READ_OPERATIONS, TESTRAIL_ALLOW_DELETE_OPERATIONS, TESTRAIL_ENABLE_DEPRECATED_TOOLS, TESTRAIL_DISABLED_TOOLS } = parseResult.data;
28
38
  const server = new McpServer({
29
39
  name: "TestRail MCP Server",
30
- version: "2.9.0",
40
+ version: VERSION,
31
41
  });
32
42
  const client = new TestRailClient(TESTRAIL_INSTANCE_URL, TESTRAIL_USERNAME, TESTRAIL_API_KEY);
33
43
  let tools;
@@ -2,12 +2,12 @@ import { z } from "zod";
2
2
  import { AttachmentSchema } from "./types.js";
3
3
  import { normalizeEntityId, prepareUploadFile } from "../../utils/attachment_helper.js";
4
4
  const parameters = {
5
- entity_type: z.enum(["case", "run"]).describe("The type of entity to attach the file to ('case' or 'run')"),
6
- entity_id: z.union([z.number(), z.string()]).describe("The ID of the entity to attach the file to (e.g. 123 or 'C123' for cases, or numeric run ID)"),
5
+ entity_type: z.enum(["case", "run", "result"]).describe("The type of entity to attach the file to ('case', 'run', or 'result')"),
6
+ entity_id: z.union([z.number(), z.string()]).describe("The ID of the entity to attach the file to (e.g. 123 or 'C123' for cases, or numeric run / result ID)"),
7
7
  file_path: z.string().describe("The path to the file or directory to attach. Directories will be automatically zipped."),
8
8
  };
9
9
  const description = `
10
- Add an attachment to a test case or test run in TestRail.
10
+ Add an attachment to a test case, test run, or test result in TestRail.
11
11
  If the file_path points to a directory, it will be automatically zipped before uploading.
12
12
  Maximum upload size is 256MB.
13
13
  `;
@@ -10,8 +10,8 @@ export const AttachmentItemSchema = z.object({
10
10
  is_image: z.union([z.boolean(), z.number()]).optional(),
11
11
  });
12
12
  export const AddAttachmentSchema = z.object({
13
- entity_type: z.enum(["case", "run"]).describe("The type of entity to attach the file to ('case' or 'run')"),
14
- entity_id: z.union([z.number(), z.string()]).describe("The ID of the entity to attach the file to (e.g. 123 or 'C123' for cases, or numeric run ID)"),
13
+ entity_type: z.enum(["case", "run", "result"]).describe("The type of entity to attach the file to ('case', 'run', or 'result')"),
14
+ entity_id: z.union([z.number(), z.string()]).describe("The ID of the entity to attach the file to (e.g. 123 or 'C123' for cases, or numeric run / result ID)"),
15
15
  file_path: z.string().describe("The path to the file or directory to attach. Directories will be automatically zipped."),
16
16
  });
17
17
  export const GetOneAttachmentSchema = z.object({
@@ -2,7 +2,7 @@ import { z } from "zod";
2
2
  import { validateCaseFields } from "../../utils/validator.js";
3
3
  import { CASE_FIELDS_PARAM_DESCRIPTION } from "./get_case_fields.js";
4
4
  const parameters = {
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
+ section_id: z.number().describe("The ID of the section where the case should be created. Use query_section (action: \"many\") to find available sections"),
6
6
  title: z.string().describe("The title of the test case"),
7
7
  fields: z.record(z.string(), z.any()).optional().describe(CASE_FIELDS_PARAM_DESCRIPTION),
8
8
  };
@@ -13,7 +13,7 @@ const parameters = {
13
13
  filter: z.record(z.string(), z.string()).optional().describe("Optional API-side filters (e.g. priority_id, type_id, milestone_id, refs, created_after, etc.)."),
14
14
  where: z.record(z.string(), z.any()).optional().describe("Optional client-side filter for any field including custom fields (filters after fetching cases). Supports exact value matching. Example: {\"custom_automation_status\": 1}"),
15
15
  output_dir: z.string().optional().describe("Directory to save exported files. Defaults to an auto-generated directory in the current working directory."),
16
- ignored_fields: z.array(z.string()).optional().describe("Optional list of custom field names or system names to ignore/exclude from export (e.g. ['custom_review_status', 'review_status']). Supports both full system_name and stripped field names. Core metadata attributes (case_id, revision_id, title, section, priority, references, labels) cannot be ignored."),
16
+ ignored_fields: z.array(z.string()).optional().describe("Optional list of custom field names or system names to ignore/exclude from export (e.g. ['custom_review_status', 'review_status']). Supports both full system_name and stripped field names. Core metadata attributes (case_id, updated_on, title, section, priority, references, labels) cannot be ignored."),
17
17
  };
18
18
  function formatStepsSeparated(steps) {
19
19
  return steps.map((step, idx) => {
@@ -122,10 +122,10 @@ function buildMarkdownBody(testCase, sectionName, markdownFields) {
122
122
  }
123
123
  return mdLines.join("\n").trim() + "\n";
124
124
  }
125
- function buildMetadataAttributes(testCase, sectionName, priority, metadataFields, revisionId) {
125
+ function buildMetadataAttributes(testCase, sectionName, priority, metadataFields) {
126
126
  const metadataAttributes = {
127
127
  case_id: testCase.id,
128
- revision_id: revisionId,
128
+ updated_on: testCase.updated_on,
129
129
  title: testCase.title,
130
130
  section: sectionName,
131
131
  priority: priority,
@@ -207,18 +207,8 @@ export const exportCasesForRagTool = {
207
207
  const templateId = testCase.template_id;
208
208
  const applicableFields = caseFields.filter(field => field.include_all || (templateId !== null && templateId !== undefined && field.template_ids.includes(templateId)));
209
209
  const { markdownFields, metadataFields } = categorizeCustomFields(testCase, applicableFields, ignoredFieldsSet);
210
- let revisionId = null;
211
- try {
212
- const history = await client.getCaseHistory(testCase.id);
213
- if (history && history.length > 0) {
214
- revisionId = Math.max(...history.map(h => h.id));
215
- }
216
- }
217
- catch {
218
- revisionId = null;
219
- }
220
210
  const markdownContent = buildMarkdownBody(testCase, sectionName, markdownFields);
221
- const metadataAttributes = buildMetadataAttributes(testCase, sectionName, priority, metadataFields, revisionId);
211
+ const metadataAttributes = buildMetadataAttributes(testCase, sectionName, priority, metadataFields);
222
212
  const docFilename = `C${testCase.id}.md`;
223
213
  const metaFilename = `C${testCase.id}.md.metadata.json`;
224
214
  await fs.promises.writeFile(path.join(targetDir, docFilename), markdownContent, "utf-8");
@@ -2,13 +2,13 @@ import { z } from "zod";
2
2
  import fs from "fs";
3
3
  import { validateCaseFields, validateSuiteId } from "../../utils/validator.js";
4
4
  const parameters = {
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"),
5
+ project_id: z.number().describe("The ID of the project. Use query_project 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 query_suite to find available suites"),
7
7
  section: z.object({
8
8
  id: z.number(),
9
9
  recursive: z.boolean().optional().default(false).describe("If true, fetches cases from the section and all its child sections"),
10
10
  excludes: z.array(z.string()).optional().describe("List of section names to exclude from the recursive search"),
11
- }).optional().describe("Section filter configuration. Use get_sections to find available sections"),
11
+ }).optional().describe("Section filter configuration. Use query_section to find available sections"),
12
12
  filter: z.record(z.string(), z.string()).optional().describe("Optional API-side filters (more efficient for large datasets). Supported: priority_id, type_id, created_by, updated_by, milestone_id, refs, created_after, created_before, updated_after, updated_before. Use comma-separated values for IDs. Example: {\"priority_id\": \"1,2\", \"type_id\": \"3\"}"),
13
13
  where: z.record(z.string(), z.any()).optional().describe("Optional client-side filter for any field including custom fields (filters after fetching all cases). Supports exact value matching. Example: {\"custom_automation_status\": 1, \"priority_id\": 2}"),
14
14
  fields: z.array(z.string()).optional().describe("Additional fields to include in response beyond id, title, and suite_id. Use get_case_fields to see available fields. Example: [\"priority_id\", \"type_id\", \"custom_automation_status\"]"),
@@ -1,7 +1,7 @@
1
1
  import { z } from "zod";
2
2
  import { TemplateSchema } from "./types.js";
3
3
  const parameters = {
4
- project_id: z.number().describe("The ID of the project. Use get_projects to find available projects"),
4
+ project_id: z.number().describe("The ID of the project. Use query_project to find available projects"),
5
5
  };
6
6
  export const getTemplatesTool = {
7
7
  name: "get_templates",
@@ -2,7 +2,7 @@ import { z } from "zod";
2
2
  export const ResultSchema = z.object({
3
3
  id: z.number(),
4
4
  test_id: z.number(),
5
- status_id: z.number(),
5
+ status_id: z.number().nullable(),
6
6
  comment: z.string().nullable(),
7
7
  defects: z.string().nullable(),
8
8
  });
@@ -1,8 +1,6 @@
1
- import { addAttachmentToRunTool } from "./add_attachment_to_run.js";
2
1
  import { mutateRunTool } from "./mutate_run.js";
3
2
  import { queryRunTool } from "./query_run.js";
4
3
  export const runsTools = [
5
4
  mutateRunTool,
6
5
  queryRunTool,
7
- addAttachmentToRunTool,
8
6
  ];
@@ -1,9 +1,7 @@
1
- import { getSectionsTool } from "./get_sections.js";
2
1
  import { querySectionTool } from "./query_section.js";
3
2
  import { mutateSectionTool } from "./mutate_section.js";
4
3
  export const sectionsTools = [
5
4
  querySectionTool,
6
- getSectionsTool,
7
5
  mutateSectionTool,
8
6
  ];
9
- export { getSectionsTool, querySectionTool, mutateSectionTool };
7
+ export { querySectionTool, mutateSectionTool };
@@ -13,7 +13,7 @@ export const BaseSectionFields = z.object({
13
13
  export const CreateSectionSchema = BaseSectionFields.extend({
14
14
  action: z.literal("create").describe("The operation to perform: create a new section"),
15
15
  project_id: z.number().describe("The ID of the project the section should be added to"),
16
- suite_id: z.number().optional().describe("The ID of the test suite (required for multi-suite projects, i.e. suite_mode=2 or 3). Use get_suites to find available suites"),
16
+ suite_id: z.number().optional().describe("The ID of the test suite (required for multi-suite projects, i.e. suite_mode=2 or 3). Use query_suite to find available suites"),
17
17
  parent_id: z.number().optional().describe("The ID of the parent section (to build section hierarchies)"),
18
18
  });
19
19
  export const UpdateSectionSchema = BaseSectionFields.partial().extend({
@@ -12,7 +12,7 @@ export async function validateSuiteId(client, projectId, suiteId) {
12
12
  const project = await client.getProject(projectId);
13
13
  if (project.suite_mode === SUITE_MODE_MULTI || project.suite_mode === SUITE_MODE_SINGLE_WITH_BASELINES) {
14
14
  throw new Error(`Project "${project.name}" (id: ${project.id}) uses multiple test suites/baselines (suite_mode=${project.suite_mode}). ` +
15
- `The suite_id parameter is required. Use get_suites to find available suites for this project.`);
15
+ `The suite_id parameter is required. Use query_suite to find available suites for this project.`);
16
16
  }
17
17
  }
18
18
  /**
@@ -0,0 +1 @@
1
+ export const VERSION = "3.0.0";
package/package.json CHANGED
@@ -1,11 +1,12 @@
1
1
  {
2
2
  "name": "@uarlouski/testrail-mcp-server",
3
- "version": "2.9.0",
3
+ "version": "3.0.0",
4
4
  "mcpName": "io.github.uarlouski/testrail-mcp-server",
5
- "description": "Model Context Protocol (MCP) server for TestRail integration, enabling LLMs to fetch test cases and metadata.",
5
+ "description": "Model Context Protocol (MCP) server for TestRail: let Claude, Cursor, and Windsurf manage test cases, runs, results, and attachments.",
6
6
  "main": "dist/index.js",
7
7
  "bin": {
8
- "testrail-mcp-server": "./dist/index.js"
8
+ "testrail-mcp-server": "./dist/index.js",
9
+ "testrail-cli": "./dist/cli.js"
9
10
  },
10
11
  "files": [
11
12
  "dist"
@@ -16,19 +17,33 @@
16
17
  "model-context-protocol",
17
18
  "mcp-server",
18
19
  "testrail",
19
- "server",
20
- "typescript",
21
- "llm",
22
- "ai",
23
- "ai-agents",
20
+ "testrail-api",
21
+ "testrail-mcp",
22
+ "testrail-integration",
23
+ "test-management",
24
+ "test-case-management",
25
+ "test-cases",
26
+ "test-runs",
27
+ "test-results",
24
28
  "testing",
25
- "automation",
29
+ "qa",
30
+ "quality-assurance",
26
31
  "qa-automation",
27
32
  "qa-tools",
28
- "test-management",
33
+ "test-automation",
34
+ "automation",
35
+ "ci-cd",
36
+ "ai",
37
+ "ai-agents",
38
+ "llm",
29
39
  "claude",
30
40
  "cursor",
31
- "windsurf"
41
+ "windsurf",
42
+ "vscode",
43
+ "copilot",
44
+ "rag",
45
+ "typescript",
46
+ "cli"
32
47
  ],
33
48
  "author": "Uladzislau Arlouski",
34
49
  "repository": {
@@ -41,10 +56,10 @@
41
56
  "homepage": "https://github.com/uarlouski/testrail-mcp-server#readme",
42
57
  "license": "Apache-2.0",
43
58
  "dependencies": {
44
- "@modelcontextprotocol/sdk": "^1.29.0",
59
+ "@modelcontextprotocol/sdk": "^1.30.0",
45
60
  "archiver": "^7.0.1",
46
61
  "dotenv": "^17.4.2",
47
- "zod": "^4.4.3"
62
+ "zod": "^4.6.5"
48
63
  },
49
64
  "devDependencies": {
50
65
  "@jest/globals": "^29.7.0",
@@ -65,8 +80,11 @@
65
80
  }
66
81
  },
67
82
  "scripts": {
68
- "build": "rimraf dist && tsc",
83
+ "version:sync": "node scripts/sync-version.mjs",
84
+ "prebuild": "pnpm version:sync",
85
+ "build": "rimraf dist && tsc && node -e \"if (process.platform !== 'win32') { ['dist/index.js', 'dist/cli.js'].forEach(f => require('fs').chmodSync(f, 0o755)); }\"",
69
86
  "start": "node dist/index.js",
87
+ "cli": "ts-node src/cli.ts",
70
88
  "dev": "ts-node src/index.ts",
71
89
  "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js",
72
90
  "test:coverage": "node --experimental-vm-modules node_modules/jest/bin/jest.js --coverage",
@@ -1,23 +0,0 @@
1
- import { z } from "zod";
2
- import { AttachmentSchema } from "../attachments/types.js";
3
- import { prepareUploadFile } from "../../utils/attachment_helper.js";
4
- const parameters = {
5
- run_id: z.number().describe("The ID of the test run to attach the file to"),
6
- file_path: z.string().describe("The path to the file or directory to attach. Directories will be automatically zipped."),
7
- };
8
- /**
9
- * @deprecated Use `add_attachment` tool from the attachments domain instead.
10
- */
11
- export const addAttachmentToRunTool = {
12
- name: "add_attachment_to_run",
13
- mode: "write",
14
- deprecated: true,
15
- description: "Add an attachment to a test run in TestRail. (Deprecated: Prefer add_attachment tool). Maximum upload size is 256MB.",
16
- parameters,
17
- handler: async ({ run_id, file_path }, client) => {
18
- return prepareUploadFile(file_path, async (uploadPath, filename) => {
19
- const result = await client.addAttachment('run', run_id, uploadPath, filename);
20
- return AttachmentSchema.parse(result);
21
- });
22
- },
23
- };
@@ -1,35 +0,0 @@
1
- import { z } from "zod";
2
- import fs from "fs";
3
- import { SectionSchema } from "./types.js";
4
- import { validateSuiteId } from "../../utils/validator.js";
5
- const parameters = {
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"),
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."),
9
- };
10
- /**
11
- * @deprecated Use `query_section` with payload.action = 'many' instead.
12
- */
13
- export const getSectionsTool = {
14
- name: "get_sections",
15
- mode: "read",
16
- deprecated: true,
17
- description: "Get all sections for a project. (Deprecated: Prefer query_section tool). Returns section IDs and names that can be used with add_case",
18
- parameters,
19
- handler: async ({ project_id, suite_id, output_file }, client) => {
20
- await validateSuiteId(client, project_id, suite_id);
21
- const sections = await client.getSections(project_id, suite_id);
22
- const response = {
23
- sections: sections.map(s => SectionSchema.parse(s)),
24
- };
25
- if (output_file) {
26
- await fs.promises.writeFile(output_file, JSON.stringify(response), "utf-8");
27
- return {
28
- success: true,
29
- message: `Successfully exported ${response.sections.length} sections to ${output_file}`,
30
- file: output_file
31
- };
32
- }
33
- return response;
34
- }
35
- };