@uarlouski/testrail-mcp-server 2.6.0 → 2.7.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.
@@ -13,6 +13,7 @@ export class TestRailClient {
13
13
  statusesPromise = null;
14
14
  projectsPromise = null;
15
15
  templatesPromiseMap = new Map();
16
+ sectionsPromiseMap = new Map();
16
17
  constructor(baseUrl, email, apiKey) {
17
18
  this.baseUrl = baseUrl.replace(/\/$/, "");
18
19
  const auth = Buffer.from(`${email}:${apiKey}`).toString('base64');
@@ -60,7 +61,10 @@ export class TestRailClient {
60
61
  return this.paginateAll(url, 'cases');
61
62
  }
62
63
  async getSection(sectionId) {
63
- return this.get(`${API_BASE_V2}/get_section/${sectionId}`);
64
+ if (!this.sectionsPromiseMap.has(sectionId.toString())) {
65
+ this.sectionsPromiseMap.set(sectionId.toString(), this.get(`${API_BASE_V2}/get_section/${sectionId}`));
66
+ }
67
+ return this.sectionsPromiseMap.get(sectionId.toString());
64
68
  }
65
69
  async getPriorities() {
66
70
  if (!this.prioritiesPromise) {
package/dist/index.js CHANGED
@@ -27,7 +27,7 @@ if (!parseResult.success) {
27
27
  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
28
  const server = new McpServer({
29
29
  name: "TestRail MCP Server",
30
- version: "2.6.0",
30
+ version: "2.7.0",
31
31
  });
32
32
  const client = new TestRailClient(TESTRAIL_INSTANCE_URL, TESTRAIL_USERNAME, TESTRAIL_API_KEY);
33
33
  let tools;
@@ -1,9 +1,9 @@
1
1
  import { z } from "zod";
2
2
  import * as fs from "fs";
3
3
  import * as path from "path";
4
- import * as os from "os";
5
4
  import { normalizeEntityId, sanitizeValue } from "../../utils/sanitizer.js";
6
5
  import { processCustomFields } from "../../utils/mapper.js";
6
+ import { getBaseDirectory } from "../../utils/fs.js";
7
7
  import { CaseFieldTypeId, getFieldType } from "./fields.js";
8
8
  const parameters = {
9
9
  case_ids: z.array(z.union([z.string(), z.number()])).min(1).describe("List of test case IDs (e.g. ['C123', 456])"),
@@ -144,14 +144,15 @@ export const exportCasesForRagTool = {
144
144
  description: "Export test cases as formatted Markdown documents with companion JSON metadata sidecar files for Knowledge Base and RAG ingestion.",
145
145
  parameters,
146
146
  handler: async ({ case_ids, output_dir }, client) => {
147
- const baseDir = (process.cwd() && process.cwd() !== "/") ? process.cwd() : (process.env.PWD || os.homedir());
148
- const targetDir = output_dir ? path.resolve(output_dir) : path.resolve(baseDir, `rag_export_${Date.now()}`);
147
+ const baseDir = getBaseDirectory();
148
+ const targetDir = output_dir
149
+ ? (path.isAbsolute(output_dir) ? output_dir : path.resolve(baseDir, output_dir))
150
+ : path.resolve(baseDir, `rag_export_${Date.now()}`);
149
151
  await fs.promises.mkdir(targetDir, { recursive: true });
150
152
  const [priorities, caseFields] = await Promise.all([
151
153
  client.getPriorities().catch(() => []),
152
154
  client.getCaseFields().catch(() => []),
153
155
  ]);
154
- const fieldDefMap = buildFieldDefinitionMap(caseFields);
155
156
  const writtenFiles = [];
156
157
  for (const rawId of case_ids) {
157
158
  const id = normalizeEntityId(rawId);
@@ -164,6 +165,9 @@ export const exportCasesForRagTool = {
164
165
  }
165
166
  }
166
167
  const priority = priorities.find(p => p.id === testCase.priority_id)?.name || "Unknown";
168
+ const templateId = testCase.template_id;
169
+ const applicableFields = caseFields.filter(field => field.include_all || (templateId !== null && templateId !== undefined && field.template_ids.includes(templateId)));
170
+ const fieldDefMap = buildFieldDefinitionMap(applicableFields);
167
171
  const customFields = processCustomFields(testCase, caseFields);
168
172
  const { markdownFields, metadataFields } = categorizeCustomFields(customFields, fieldDefMap);
169
173
  const markdownContent = buildMarkdownBody(testCase, sectionName, markdownFields);
@@ -0,0 +1,36 @@
1
+ import * as fs from "fs";
2
+ import * as os from "os";
3
+ /**
4
+ * Resolves a safe, writable base directory for exports and file operations.
5
+ *
6
+ * When the MCP server is spawned by host applications (IDE daemons, npx, Claude Desktop, Cursor, Gemini),
7
+ * `process.cwd()` or `process.env.PWD` often resolves to the root filesystem (`"/"`), where direct writes
8
+ * fail with ENOENT / permission errors (especially on macOS read-only APFS volumes).
9
+ *
10
+ * This function handles multi-environment fallbacks:
11
+ * 1. `process.env.INIT_CWD`: Set by package managers (npm/npx/pnpm) to the original caller's working directory.
12
+ * 2. `process.cwd()` / `process.env.PWD`: Used if pointing to a valid non-root directory.
13
+ * 3. `os.homedir()`: User home directory fallback if the working directory is root or invalid.
14
+ * 4. `os.tmpdir()`: Final safety fallback.
15
+ */
16
+ export function getBaseDirectory() {
17
+ const candidates = [
18
+ process.env.INIT_CWD,
19
+ process.cwd(),
20
+ process.env.PWD,
21
+ os.homedir(),
22
+ ];
23
+ for (const candidate of candidates) {
24
+ if (candidate && candidate !== "/" && candidate.trim() !== "") {
25
+ try {
26
+ if (fs.existsSync(candidate)) {
27
+ return candidate;
28
+ }
29
+ }
30
+ catch {
31
+ // Ignore and try next candidate
32
+ }
33
+ }
34
+ }
35
+ return os.tmpdir();
36
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uarlouski/testrail-mcp-server",
3
- "version": "2.6.0",
3
+ "version": "2.7.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",