@uarlouski/testrail-mcp-server 2.8.0 → 2.10.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 +59 -2
- package/dist/adapters/cli_parser.js +187 -0
- package/dist/cli.js +108 -0
- package/dist/index.js +11 -1
- package/dist/tools/attachments/add_attachment.js +3 -3
- package/dist/tools/attachments/types.js +2 -2
- package/dist/tools/cases/export_cases_for_rag.js +2 -1
- package/dist/tools/results/types.js +1 -1
- package/dist/version.js +1 -0
- package/package.json +13 -11
package/README.md
CHANGED
|
@@ -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">
|
|
@@ -138,7 +139,62 @@ Once configured, turbo-charge your QA workflow by asking your AI assistant:
|
|
|
138
139
|
- *"Show me all test cases in section 5 of project 3."*
|
|
139
140
|
- *"Create a comprehensive test case for 'Login Validation' with detailed steps."*
|
|
140
141
|
- *"Start a new test run containing cases from section 5."*
|
|
141
|
-
|
|
142
|
+
---
|
|
143
|
+
|
|
144
|
+
## 🖥️ Command Line Interface (CLI) & CI/CD Automation
|
|
145
|
+
|
|
146
|
+
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.
|
|
147
|
+
|
|
148
|
+
- **Deterministic Execution**: Returns standard Unix exit codes (`0` on success, `1` on error).
|
|
149
|
+
- **Pipeline-Native Output**: Emits clean JSON to `stdout` for piping into tools like `jq`, while diagnostics and errors go to `stderr`.
|
|
150
|
+
- **Zero Duplication**: Reuses the exact same API client, retry logic, and validation schemas as the MCP server.
|
|
151
|
+
|
|
152
|
+
### Invocation Methods
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
# Method 1: Direct npx subcommand (Recommended)
|
|
156
|
+
npx @uarlouski/testrail-mcp-server cli <command> [flags]
|
|
157
|
+
|
|
158
|
+
# Method 2: Global or local binary
|
|
159
|
+
testrail-cli <command> [flags]
|
|
160
|
+
|
|
161
|
+
# Method 3: Via package runner
|
|
162
|
+
npx -p @uarlouski/testrail-mcp-server testrail-cli <command> [flags]
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### Examples
|
|
166
|
+
|
|
167
|
+
#### Query Projects (`query_project`)
|
|
168
|
+
```bash
|
|
169
|
+
# List all active projects
|
|
170
|
+
npx @uarlouski/testrail-mcp-server cli query_project --action many
|
|
171
|
+
|
|
172
|
+
# Query a single project by ID
|
|
173
|
+
npx @uarlouski/testrail-mcp-server cli query_project --action one --project_id 1
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
#### Export Cases for Knowledge Base / RAG (`export_cases_for_rag`)
|
|
177
|
+
```bash
|
|
178
|
+
# Export all cases for a project into Markdown & metadata sidecars
|
|
179
|
+
npx @uarlouski/testrail-mcp-server cli export_cases_for_rag \
|
|
180
|
+
--project_id 1 \
|
|
181
|
+
--output_dir ./rag_exports
|
|
182
|
+
|
|
183
|
+
# Export specific cases by ID (comma-separated list)
|
|
184
|
+
npx @uarlouski/testrail-mcp-server cli export_cases_for_rag \
|
|
185
|
+
--case_ids C101,C102,103 \
|
|
186
|
+
--output_dir ./rag_exports
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
#### Command Discovery & Flag Documentation
|
|
190
|
+
```bash
|
|
191
|
+
# List all available commands
|
|
192
|
+
npx @uarlouski/testrail-mcp-server cli --help
|
|
193
|
+
|
|
194
|
+
# Show parameter options for a specific tool
|
|
195
|
+
npx @uarlouski/testrail-mcp-server cli query_project --help
|
|
196
|
+
npx @uarlouski/testrail-mcp-server cli export_cases_for_rag --help
|
|
197
|
+
```
|
|
142
198
|
|
|
143
199
|
---
|
|
144
200
|
|
|
@@ -164,7 +220,8 @@ To ensure smooth transitions, deprecated tools remain available by default (`TES
|
|
|
164
220
|
|
|
165
221
|
| Deprecated Tool | Replacement | Status |
|
|
166
222
|
|-----------------|-------------|--------|
|
|
167
|
-
| `add_attachment_to_run` | `add_attachment` (`entity_type: "case" \| "run"`) | Deprecated in `2.3.0`, scheduled for removal in `3.0.0` |
|
|
223
|
+
| `add_attachment_to_run` | `add_attachment` (`entity_type: "case" \| "run" \| "result"`) | Deprecated in `2.3.0`, scheduled for removal in `3.0.0` |
|
|
224
|
+
| `get_sections` | `query_section` (`action: "many"`) | Deprecated in `2.8.0`, scheduled for removal in `3.0.0` |
|
|
168
225
|
|
|
169
226
|
> **💡 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!
|
|
170
227
|
|
|
@@ -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:
|
|
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 '
|
|
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
|
|
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 '
|
|
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({
|
|
@@ -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, 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) => {
|
|
@@ -125,6 +125,7 @@ function buildMarkdownBody(testCase, sectionName, markdownFields) {
|
|
|
125
125
|
function buildMetadataAttributes(testCase, sectionName, priority, metadataFields) {
|
|
126
126
|
const metadataAttributes = {
|
|
127
127
|
case_id: testCase.id,
|
|
128
|
+
updated_on: testCase.updated_on,
|
|
128
129
|
title: testCase.title,
|
|
129
130
|
section: sectionName,
|
|
130
131
|
priority: priority,
|
package/dist/version.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const VERSION = "2.10.0";
|
package/package.json
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uarlouski/testrail-mcp-server",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.10.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",
|
|
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"
|
|
@@ -41,10 +42,10 @@
|
|
|
41
42
|
"homepage": "https://github.com/uarlouski/testrail-mcp-server#readme",
|
|
42
43
|
"license": "Apache-2.0",
|
|
43
44
|
"dependencies": {
|
|
44
|
-
"@modelcontextprotocol/sdk": "^1.
|
|
45
|
+
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
45
46
|
"archiver": "^7.0.1",
|
|
46
47
|
"dotenv": "^17.4.2",
|
|
47
|
-
"zod": "^4.
|
|
48
|
+
"zod": "^4.6.5"
|
|
48
49
|
},
|
|
49
50
|
"devDependencies": {
|
|
50
51
|
"@jest/globals": "^29.7.0",
|
|
@@ -55,9 +56,7 @@
|
|
|
55
56
|
"rimraf": "^6.1.3",
|
|
56
57
|
"ts-jest": "^29.4.6",
|
|
57
58
|
"ts-node": "^10.9.2",
|
|
58
|
-
"typescript": "^5.9.3"
|
|
59
|
-
"vitepress": "^1.6.4",
|
|
60
|
-
"vue": "^3.5.41"
|
|
59
|
+
"typescript": "^5.9.3"
|
|
61
60
|
},
|
|
62
61
|
"devEngines": {
|
|
63
62
|
"packageManager": {
|
|
@@ -67,13 +66,16 @@
|
|
|
67
66
|
}
|
|
68
67
|
},
|
|
69
68
|
"scripts": {
|
|
70
|
-
"
|
|
69
|
+
"version:sync": "node scripts/sync-version.mjs",
|
|
70
|
+
"prebuild": "pnpm version:sync",
|
|
71
|
+
"build": "rimraf dist && tsc && node -e \"if (process.platform !== 'win32') { ['dist/index.js', 'dist/cli.js'].forEach(f => require('fs').chmodSync(f, 0o755)); }\"",
|
|
71
72
|
"start": "node dist/index.js",
|
|
73
|
+
"cli": "ts-node src/cli.ts",
|
|
72
74
|
"dev": "ts-node src/index.ts",
|
|
73
75
|
"test": "node --experimental-vm-modules node_modules/jest/bin/jest.js",
|
|
74
76
|
"test:coverage": "node --experimental-vm-modules node_modules/jest/bin/jest.js --coverage",
|
|
75
|
-
"docs:dev": "
|
|
76
|
-
"docs:build": "
|
|
77
|
-
"docs:preview": "
|
|
77
|
+
"docs:dev": "pnpm --prefix docs dev",
|
|
78
|
+
"docs:build": "pnpm --prefix docs build",
|
|
79
|
+
"docs:preview": "pnpm --prefix docs preview"
|
|
78
80
|
}
|
|
79
81
|
}
|