@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 +7 -0
- package/README.md +52 -16
- package/package.json +1 -2
- package/src/client/testrail.ts +42 -53
- package/src/index.ts +22 -2
- package/src/tools/create_case.ts +6 -13
- package/src/tools/get_case.ts +4 -11
- package/src/tools/get_case_fields.ts +4 -11
- package/src/tools/get_cases.ts +4 -11
- package/src/tools/get_projects.ts +7 -25
- package/src/tools/get_sections.ts +7 -25
- package/src/tools/get_templates.ts +7 -25
- package/src/tools/update_case.ts +6 -13
- package/src/tools/update_cases.ts +7 -14
- package/src/types/custom.ts +1 -16
- package/src/types/testrail.ts +87 -87
- package/src/utils/sanitizer.ts +29 -0
- package/test/client/testrail.test.ts +215 -142
- package/test/tools/create_case.test.ts +10 -16
- package/test/tools/get_case.test.ts +46 -61
- package/test/tools/get_case_fields.test.ts +7 -80
- package/test/tools/get_cases.test.ts +19 -108
- package/test/tools/get_projects.test.ts +4 -41
- package/test/tools/get_sections.test.ts +7 -36
- package/test/tools/get_templates.test.ts +1 -30
- package/test/tools/update_case.test.ts +10 -16
- package/test/tools/update_cases.test.ts +14 -41
- package/test/utils/mapper.test.ts +13 -13
- package/test/utils/sanitizer.test.ts +85 -1
package/src/tools/update_case.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { TestRailClient } from "../client/testrail.js";
|
|
3
|
-
import { ToolDefinition
|
|
3
|
+
import { ToolDefinition } from "../types/custom.js";
|
|
4
4
|
|
|
5
5
|
const parameters = {
|
|
6
6
|
case_id: z.string().describe("The ID of the test case to update (e.g. '123' or 'C123')"),
|
|
@@ -11,22 +11,15 @@ export const updateCaseTool: ToolDefinition<typeof parameters, TestRailClient> =
|
|
|
11
11
|
name: "update_case",
|
|
12
12
|
description: "Update a test case in TestRail. Supports partial updates - only specify the fields you want to change. Use get_case_fields to see available fields and their types.",
|
|
13
13
|
parameters,
|
|
14
|
-
handler:
|
|
14
|
+
handler: async ({ case_id, fields }, client) => {
|
|
15
15
|
const id = case_id.toUpperCase().startsWith("C") ? case_id.substring(1) : case_id;
|
|
16
16
|
|
|
17
17
|
const updatedCase = await client.updateCase(id, fields);
|
|
18
18
|
|
|
19
19
|
return {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
text: JSON.stringify({
|
|
24
|
-
success: true,
|
|
25
|
-
case_id: updatedCase.id,
|
|
26
|
-
message: `Test case C${updatedCase.id} updated successfully`,
|
|
27
|
-
}, null, 2),
|
|
28
|
-
},
|
|
29
|
-
],
|
|
20
|
+
success: true,
|
|
21
|
+
case_id: updatedCase.id,
|
|
22
|
+
message: `Test case C${updatedCase.id} updated successfully`,
|
|
30
23
|
};
|
|
31
|
-
}
|
|
24
|
+
}
|
|
32
25
|
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { TestRailClient } from "../client/testrail.js";
|
|
3
|
-
import { ToolDefinition
|
|
3
|
+
import { ToolDefinition } from "../types/custom.js";
|
|
4
4
|
|
|
5
5
|
const parameters = {
|
|
6
6
|
case_ids: z.array(z.number()).describe("Array of case IDs to update (e.g. [123, 456, 789])"),
|
|
@@ -11,21 +11,14 @@ export const updateCasesTool: ToolDefinition<typeof parameters, TestRailClient>
|
|
|
11
11
|
name: "update_cases",
|
|
12
12
|
description: "Bulk update multiple test cases with the same field values. More efficient than calling update_case multiple times. All cases will receive the same field values.",
|
|
13
13
|
parameters,
|
|
14
|
-
handler:
|
|
14
|
+
handler: async ({ case_ids, fields }, client) => {
|
|
15
15
|
const updatedCases = await client.updateCases(case_ids, fields);
|
|
16
16
|
|
|
17
17
|
return {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
success: true,
|
|
23
|
-
updated_count: updatedCases.length,
|
|
24
|
-
case_ids: updatedCases.map(c => c.id),
|
|
25
|
-
message: `Successfully updated ${updatedCases.length} test cases`,
|
|
26
|
-
}, null, 2),
|
|
27
|
-
},
|
|
28
|
-
],
|
|
18
|
+
success: true,
|
|
19
|
+
updated_count: updatedCases.length,
|
|
20
|
+
case_ids: updatedCases.map(c => c.id),
|
|
21
|
+
message: `Successfully updated ${updatedCases.length} test cases`,
|
|
29
22
|
};
|
|
30
|
-
}
|
|
23
|
+
}
|
|
31
24
|
};
|
package/src/types/custom.ts
CHANGED
|
@@ -15,20 +15,5 @@ export interface ToolDefinition<T extends z.ZodRawShape, Context = any> {
|
|
|
15
15
|
name: string;
|
|
16
16
|
description: string;
|
|
17
17
|
parameters: T;
|
|
18
|
-
handler: (args: z.infer<z.ZodObject<T>>, context: Context) => Promise<any
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export function withErrorHandling<T extends z.ZodRawShape, Context = any>(
|
|
22
|
-
handler: (args: z.infer<z.ZodObject<T>>, context: Context) => Promise<any>
|
|
23
|
-
): (args: z.infer<z.ZodObject<T>>, context: Context) => Promise<any> {
|
|
24
|
-
return async (args, context) => {
|
|
25
|
-
try {
|
|
26
|
-
return await handler(args, context);
|
|
27
|
-
} catch (error: any) {
|
|
28
|
-
return {
|
|
29
|
-
content: [{ type: "text", text: `Error: ${error.message}` }],
|
|
30
|
-
isError: true,
|
|
31
|
-
};
|
|
32
|
-
}
|
|
33
|
-
};
|
|
18
|
+
handler: (args: z.infer<z.ZodObject<T>>, context: Context) => Promise<Record<string, any>>;
|
|
34
19
|
}
|
package/src/types/testrail.ts
CHANGED
|
@@ -1,87 +1,87 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
export const CaseFieldConfigSchema = z.object({
|
|
4
|
+
options: z.object({
|
|
5
|
+
default_value: z.string().optional(),
|
|
6
|
+
is_required: z.boolean().optional(),
|
|
7
|
+
items: z.string().optional(),
|
|
8
|
+
}),
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
export type CaseFieldConfig = z.infer<typeof CaseFieldConfigSchema>;
|
|
12
|
+
|
|
13
|
+
export const CaseFieldSchema = z.object({
|
|
14
|
+
id: z.number(),
|
|
15
|
+
name: z.string(),
|
|
16
|
+
system_name: z.string(),
|
|
17
|
+
label: z.string(),
|
|
18
|
+
type_id: z.number(),
|
|
19
|
+
template_ids: z.array(z.number()),
|
|
20
|
+
is_active: z.boolean(),
|
|
21
|
+
description: z.string().nullable(),
|
|
22
|
+
include_all: z.boolean(),
|
|
23
|
+
configs: z.array(CaseFieldConfigSchema),
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
export type CaseField = z.infer<typeof CaseFieldSchema>;
|
|
27
|
+
|
|
28
|
+
export const CaseSchema = z.object({
|
|
29
|
+
id: z.number(),
|
|
30
|
+
title: z.string(),
|
|
31
|
+
section_id: z.number(),
|
|
32
|
+
template_id: z.number(),
|
|
33
|
+
type_id: z.number(),
|
|
34
|
+
priority_id: z.number(),
|
|
35
|
+
milestone_id: z.number().nullable(),
|
|
36
|
+
refs: z.string().nullable(),
|
|
37
|
+
created_on: z.number(),
|
|
38
|
+
updated_on: z.number(),
|
|
39
|
+
estimate: z.string().nullable(),
|
|
40
|
+
suite_id: z.number(),
|
|
41
|
+
labels: z.array(z.any()),
|
|
42
|
+
}).loose();
|
|
43
|
+
|
|
44
|
+
export type Case = z.infer<typeof CaseSchema>;
|
|
45
|
+
|
|
46
|
+
export const SectionSchema = z.object({
|
|
47
|
+
id: z.number(),
|
|
48
|
+
name: z.string(),
|
|
49
|
+
description: z.string().nullish(),
|
|
50
|
+
parent_id: z.number().nullish(),
|
|
51
|
+
suite_id: z.number(),
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
export type Section = z.infer<typeof SectionSchema>;
|
|
55
|
+
|
|
56
|
+
export const PrioritySchema = z.object({
|
|
57
|
+
id: z.number(),
|
|
58
|
+
is_default: z.boolean(),
|
|
59
|
+
name: z.string(),
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
export type Priority = z.infer<typeof PrioritySchema>;
|
|
63
|
+
|
|
64
|
+
export const CaseTypeSchema = z.object({
|
|
65
|
+
id: z.number(),
|
|
66
|
+
is_default: z.boolean(),
|
|
67
|
+
name: z.string(),
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
export type CaseType = z.infer<typeof CaseTypeSchema>;
|
|
71
|
+
|
|
72
|
+
export const TemplateSchema = z.object({
|
|
73
|
+
id: z.number(),
|
|
74
|
+
name: z.string(),
|
|
75
|
+
is_default: z.boolean(),
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
export type Template = z.infer<typeof TemplateSchema>;
|
|
79
|
+
|
|
80
|
+
export const ProjectSchema = z.object({
|
|
81
|
+
id: z.number(),
|
|
82
|
+
name: z.string(),
|
|
83
|
+
is_completed: z.boolean(),
|
|
84
|
+
suite_mode: z.number(),
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
export type Project = z.infer<typeof ProjectSchema>;
|
package/src/utils/sanitizer.ts
CHANGED
|
@@ -19,3 +19,32 @@ export function sanitizeValue(value: any): any {
|
|
|
19
19
|
}
|
|
20
20
|
return value;
|
|
21
21
|
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Recursively removes null and undefined values from objects and arrays.
|
|
25
|
+
* For arrays, filters nested object items as well.
|
|
26
|
+
*/
|
|
27
|
+
export function removeNullish(value: any): any {
|
|
28
|
+
if (value == null) {
|
|
29
|
+
return value;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (Array.isArray(value)) {
|
|
33
|
+
return value
|
|
34
|
+
.map(item => removeNullish(item))
|
|
35
|
+
.filter(item => item != null);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (typeof value === 'object') {
|
|
39
|
+
const result: Record<string, any> = {};
|
|
40
|
+
for (const [key, val] of Object.entries(value)) {
|
|
41
|
+
const cleanedVal = removeNullish(val);
|
|
42
|
+
if (cleanedVal != null) {
|
|
43
|
+
result[key] = cleanedVal;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return result;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return value;
|
|
50
|
+
}
|