imperial-mcp-qase 1.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/.claude/settings.local.json +15 -0
- package/README.md +208 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +14815 -0
- package/package.json +36 -0
- package/src/client.ts +331 -0
- package/src/index.ts +57 -0
- package/src/tools/cases.ts +219 -0
- package/src/tools/defects.ts +218 -0
- package/src/tools/projects.ts +49 -0
- package/src/tools/results.ts +204 -0
- package/src/tools/runs.ts +182 -0
- package/src/types/qase.ts +343 -0
- package/tsconfig.json +24 -0
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
+
import { QaseClient, QaseApiError } from "../client.js";
|
|
4
|
+
|
|
5
|
+
// Zod schemas for validation
|
|
6
|
+
const ListDefectsSchema = {
|
|
7
|
+
project_code: z.string().describe("Qase project code (e.g., 'DEMO', 'BPJS')"),
|
|
8
|
+
limit: z.number().optional().describe("Maximum number of results (default: 100)"),
|
|
9
|
+
offset: z.number().optional().describe("Offset for pagination"),
|
|
10
|
+
status: z.string().optional().describe("Filter by status ('open', 'resolved', 'in_progress', 'invalid')"),
|
|
11
|
+
severity: z.array(z.number()).optional().describe("Filter by severity (0=not set, 1=blocker, 2=critical, 3=major, 4=normal, 5=minor, 6=trivial)"),
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const GetDefectSchema = {
|
|
15
|
+
project_code: z.string().describe("Qase project code (e.g., 'DEMO', 'BPJS')"),
|
|
16
|
+
defect_id: z.number().describe("Defect ID"),
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const CreateDefectSchema = {
|
|
20
|
+
project_code: z.string().describe("Qase project code (e.g., 'DEMO', 'BPJS')"),
|
|
21
|
+
title: z.string().describe("Defect title"),
|
|
22
|
+
actual_result: z.string().describe("Actual result / defect description"),
|
|
23
|
+
severity: z.number().optional().describe("Severity (0=not set, 1=blocker, 2=critical, 3=major, 4=normal, 5=minor, 6=trivial)"),
|
|
24
|
+
milestone_id: z.number().optional().describe("Associated milestone ID"),
|
|
25
|
+
attachments: z.array(z.string()).optional().describe("Attachment hashes (from uploaded files)"),
|
|
26
|
+
tags: z.array(z.string()).optional().describe("Tags for the defect"),
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const UpdateDefectSchema = {
|
|
30
|
+
project_code: z.string().describe("Qase project code (e.g., 'DEMO', 'BPJS')"),
|
|
31
|
+
defect_id: z.number().describe("Defect ID to update"),
|
|
32
|
+
title: z.string().optional().describe("Updated defect title"),
|
|
33
|
+
actual_result: z.string().optional().describe("Updated actual result / description"),
|
|
34
|
+
severity: z.number().optional().describe("Updated severity"),
|
|
35
|
+
status: z.string().optional().describe("Updated status ('open', 'resolved', 'in_progress', 'invalid')"),
|
|
36
|
+
milestone_id: z.number().optional().describe("Updated milestone ID"),
|
|
37
|
+
attachments: z.array(z.string()).optional().describe("Updated attachment hashes"),
|
|
38
|
+
tags: z.array(z.string()).optional().describe("Updated tags"),
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const ResolveDefectSchema = {
|
|
42
|
+
project_code: z.string().describe("Qase project code (e.g., 'DEMO', 'BPJS')"),
|
|
43
|
+
defect_id: z.number().describe("Defect ID to resolve"),
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const DeleteDefectSchema = {
|
|
47
|
+
project_code: z.string().describe("Qase project code (e.g., 'DEMO', 'BPJS')"),
|
|
48
|
+
defect_id: z.number().describe("Defect ID to delete"),
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
function formatError(error: unknown): string {
|
|
52
|
+
if (error instanceof QaseApiError) {
|
|
53
|
+
let message = `Qase API Error (${error.statusCode}): ${error.message}`;
|
|
54
|
+
if (error.errorFields) {
|
|
55
|
+
message += `\nField errors: ${JSON.stringify(error.errorFields, null, 2)}`;
|
|
56
|
+
}
|
|
57
|
+
return message;
|
|
58
|
+
}
|
|
59
|
+
if (error instanceof Error) {
|
|
60
|
+
return `Error: ${error.message}`;
|
|
61
|
+
}
|
|
62
|
+
return `Unknown error: ${String(error)}`;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function registerDefectTools(server: McpServer, client: QaseClient): void {
|
|
66
|
+
// List defects
|
|
67
|
+
server.tool(
|
|
68
|
+
"qase_list_defects",
|
|
69
|
+
"List defects in a Qase project with optional filtering",
|
|
70
|
+
ListDefectsSchema,
|
|
71
|
+
async (args) => {
|
|
72
|
+
try {
|
|
73
|
+
const { project_code, ...filters } = args;
|
|
74
|
+
const response = await client.listDefects(project_code, filters);
|
|
75
|
+
return {
|
|
76
|
+
content: [
|
|
77
|
+
{
|
|
78
|
+
type: "text",
|
|
79
|
+
text: JSON.stringify(response, null, 2),
|
|
80
|
+
},
|
|
81
|
+
],
|
|
82
|
+
};
|
|
83
|
+
} catch (error) {
|
|
84
|
+
return {
|
|
85
|
+
content: [{ type: "text", text: formatError(error) }],
|
|
86
|
+
isError: true,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
// Get single defect
|
|
93
|
+
server.tool(
|
|
94
|
+
"qase_get_defect",
|
|
95
|
+
"Get a single defect by ID from a Qase project",
|
|
96
|
+
GetDefectSchema,
|
|
97
|
+
async (args) => {
|
|
98
|
+
try {
|
|
99
|
+
const response = await client.getDefect(args.project_code, args.defect_id);
|
|
100
|
+
return {
|
|
101
|
+
content: [
|
|
102
|
+
{
|
|
103
|
+
type: "text",
|
|
104
|
+
text: JSON.stringify(response, null, 2),
|
|
105
|
+
},
|
|
106
|
+
],
|
|
107
|
+
};
|
|
108
|
+
} catch (error) {
|
|
109
|
+
return {
|
|
110
|
+
content: [{ type: "text", text: formatError(error) }],
|
|
111
|
+
isError: true,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
// Create defect
|
|
118
|
+
server.tool(
|
|
119
|
+
"qase_create_defect",
|
|
120
|
+
"Create a new defect in a Qase project",
|
|
121
|
+
CreateDefectSchema,
|
|
122
|
+
async (args) => {
|
|
123
|
+
try {
|
|
124
|
+
const { project_code, ...defectData } = args;
|
|
125
|
+
const response = await client.createDefect(project_code, defectData);
|
|
126
|
+
return {
|
|
127
|
+
content: [
|
|
128
|
+
{
|
|
129
|
+
type: "text",
|
|
130
|
+
text: JSON.stringify(response, null, 2),
|
|
131
|
+
},
|
|
132
|
+
],
|
|
133
|
+
};
|
|
134
|
+
} catch (error) {
|
|
135
|
+
return {
|
|
136
|
+
content: [{ type: "text", text: formatError(error) }],
|
|
137
|
+
isError: true,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
// Update defect
|
|
144
|
+
server.tool(
|
|
145
|
+
"qase_update_defect",
|
|
146
|
+
"Update an existing defect in a Qase project",
|
|
147
|
+
UpdateDefectSchema,
|
|
148
|
+
async (args) => {
|
|
149
|
+
try {
|
|
150
|
+
const { project_code, defect_id, ...updateData } = args;
|
|
151
|
+
const response = await client.updateDefect(project_code, defect_id, updateData);
|
|
152
|
+
return {
|
|
153
|
+
content: [
|
|
154
|
+
{
|
|
155
|
+
type: "text",
|
|
156
|
+
text: JSON.stringify(response, null, 2),
|
|
157
|
+
},
|
|
158
|
+
],
|
|
159
|
+
};
|
|
160
|
+
} catch (error) {
|
|
161
|
+
return {
|
|
162
|
+
content: [{ type: "text", text: formatError(error) }],
|
|
163
|
+
isError: true,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
// Resolve defect
|
|
170
|
+
server.tool(
|
|
171
|
+
"qase_resolve_defect",
|
|
172
|
+
"Mark a defect as resolved in a Qase project",
|
|
173
|
+
ResolveDefectSchema,
|
|
174
|
+
async (args) => {
|
|
175
|
+
try {
|
|
176
|
+
const response = await client.resolveDefect(args.project_code, args.defect_id);
|
|
177
|
+
return {
|
|
178
|
+
content: [
|
|
179
|
+
{
|
|
180
|
+
type: "text",
|
|
181
|
+
text: JSON.stringify(response, null, 2),
|
|
182
|
+
},
|
|
183
|
+
],
|
|
184
|
+
};
|
|
185
|
+
} catch (error) {
|
|
186
|
+
return {
|
|
187
|
+
content: [{ type: "text", text: formatError(error) }],
|
|
188
|
+
isError: true,
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
);
|
|
193
|
+
|
|
194
|
+
// Delete defect
|
|
195
|
+
server.tool(
|
|
196
|
+
"qase_delete_defect",
|
|
197
|
+
"Delete a defect from a Qase project",
|
|
198
|
+
DeleteDefectSchema,
|
|
199
|
+
async (args) => {
|
|
200
|
+
try {
|
|
201
|
+
const response = await client.deleteDefect(args.project_code, args.defect_id);
|
|
202
|
+
return {
|
|
203
|
+
content: [
|
|
204
|
+
{
|
|
205
|
+
type: "text",
|
|
206
|
+
text: JSON.stringify(response, null, 2),
|
|
207
|
+
},
|
|
208
|
+
],
|
|
209
|
+
};
|
|
210
|
+
} catch (error) {
|
|
211
|
+
return {
|
|
212
|
+
content: [{ type: "text", text: formatError(error) }],
|
|
213
|
+
isError: true,
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
);
|
|
218
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
+
import { QaseClient, QaseApiError } from "../client.js";
|
|
4
|
+
|
|
5
|
+
const ListProjectsSchema = {
|
|
6
|
+
limit: z.number().optional().describe("Maximum number of results (default: 100)"),
|
|
7
|
+
offset: z.number().optional().describe("Offset for pagination"),
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
function formatError(error: unknown): string {
|
|
11
|
+
if (error instanceof QaseApiError) {
|
|
12
|
+
let message = `Qase API Error (${error.statusCode}): ${error.message}`;
|
|
13
|
+
if (error.errorFields) {
|
|
14
|
+
message += `\nField errors: ${JSON.stringify(error.errorFields, null, 2)}`;
|
|
15
|
+
}
|
|
16
|
+
return message;
|
|
17
|
+
}
|
|
18
|
+
if (error instanceof Error) {
|
|
19
|
+
return `Error: ${error.message}`;
|
|
20
|
+
}
|
|
21
|
+
return `Unknown error: ${String(error)}`;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function registerProjectTools(server: McpServer, client: QaseClient): void {
|
|
25
|
+
// List all projects
|
|
26
|
+
server.tool(
|
|
27
|
+
"qase_list_projects",
|
|
28
|
+
"List all Qase projects accessible with the current API token",
|
|
29
|
+
ListProjectsSchema,
|
|
30
|
+
async (args) => {
|
|
31
|
+
try {
|
|
32
|
+
const response = await client.listProjects(args);
|
|
33
|
+
return {
|
|
34
|
+
content: [
|
|
35
|
+
{
|
|
36
|
+
type: "text",
|
|
37
|
+
text: JSON.stringify(response, null, 2),
|
|
38
|
+
},
|
|
39
|
+
],
|
|
40
|
+
};
|
|
41
|
+
} catch (error) {
|
|
42
|
+
return {
|
|
43
|
+
content: [{ type: "text", text: formatError(error) }],
|
|
44
|
+
isError: true,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
);
|
|
49
|
+
}
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
+
import { QaseClient, QaseApiError } from "../client.js";
|
|
4
|
+
|
|
5
|
+
// Zod schemas for validation
|
|
6
|
+
const ListResultsSchema = {
|
|
7
|
+
project_code: z.string().describe("Qase project code (e.g., 'DEMO', 'BPJS')"),
|
|
8
|
+
run_id: z.number().describe("Test run ID to list results from"),
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
const GetResultSchema = {
|
|
12
|
+
project_code: z.string().describe("Qase project code (e.g., 'DEMO', 'BPJS')"),
|
|
13
|
+
run_id: z.number().describe("Test run ID"),
|
|
14
|
+
hash: z.string().describe("Result hash identifier"),
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const ResultStepSchema = z.object({
|
|
18
|
+
position: z.number().describe("Step position (order)"),
|
|
19
|
+
status: z.enum(["passed", "failed", "blocked", "skipped"]).describe("Step status"),
|
|
20
|
+
comment: z.string().optional().describe("Step comment"),
|
|
21
|
+
attachments: z.array(z.string()).optional().describe("Attachment hashes for this step"),
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
const CreateResultSchema = {
|
|
25
|
+
project_code: z.string().describe("Qase project code (e.g., 'DEMO', 'BPJS')"),
|
|
26
|
+
run_id: z.number().describe("Test run ID to add result to"),
|
|
27
|
+
case_id: z.number().describe("Test case ID for this result"),
|
|
28
|
+
status: z.enum(["passed", "failed", "blocked", "skipped", "invalid"]).describe("Test result status"),
|
|
29
|
+
time_ms: z.number().optional().describe("Time spent in milliseconds"),
|
|
30
|
+
member_id: z.number().optional().describe("Team member ID who executed the test"),
|
|
31
|
+
comment: z.string().optional().describe("Result comment"),
|
|
32
|
+
stacktrace: z.string().optional().describe("Stack trace (for failed tests)"),
|
|
33
|
+
defect: z.boolean().optional().describe("Whether to create a defect (for failed tests)"),
|
|
34
|
+
steps: z.array(ResultStepSchema).optional().describe("Individual step results"),
|
|
35
|
+
attachments: z.array(z.string()).optional().describe("Attachment hashes"),
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const CreateResultBulkSchema = {
|
|
39
|
+
project_code: z.string().describe("Qase project code (e.g., 'DEMO', 'BPJS')"),
|
|
40
|
+
run_id: z.number().describe("Test run ID to add results to"),
|
|
41
|
+
results: z.array(z.object({
|
|
42
|
+
case_id: z.number().describe("Test case ID"),
|
|
43
|
+
status: z.enum(["passed", "failed", "blocked", "skipped", "invalid"]).describe("Test result status"),
|
|
44
|
+
time_ms: z.number().optional().describe("Time spent in milliseconds"),
|
|
45
|
+
member_id: z.number().optional().describe("Team member ID"),
|
|
46
|
+
comment: z.string().optional().describe("Result comment"),
|
|
47
|
+
stacktrace: z.string().optional().describe("Stack trace"),
|
|
48
|
+
defect: z.boolean().optional().describe("Whether to create a defect"),
|
|
49
|
+
steps: z.array(ResultStepSchema).optional().describe("Step results"),
|
|
50
|
+
attachments: z.array(z.string()).optional().describe("Attachment hashes"),
|
|
51
|
+
})).describe("Array of test results to add"),
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const DeleteResultSchema = {
|
|
55
|
+
project_code: z.string().describe("Qase project code (e.g., 'DEMO', 'BPJS')"),
|
|
56
|
+
run_id: z.number().describe("Test run ID"),
|
|
57
|
+
hash: z.string().describe("Result hash to delete"),
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
function formatError(error: unknown): string {
|
|
61
|
+
if (error instanceof QaseApiError) {
|
|
62
|
+
let message = `Qase API Error (${error.statusCode}): ${error.message}`;
|
|
63
|
+
if (error.errorFields) {
|
|
64
|
+
message += `\nField errors: ${JSON.stringify(error.errorFields, null, 2)}`;
|
|
65
|
+
}
|
|
66
|
+
return message;
|
|
67
|
+
}
|
|
68
|
+
if (error instanceof Error) {
|
|
69
|
+
return `Error: ${error.message}`;
|
|
70
|
+
}
|
|
71
|
+
return `Unknown error: ${String(error)}`;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function registerResultTools(server: McpServer, client: QaseClient): void {
|
|
75
|
+
// List test results
|
|
76
|
+
server.tool(
|
|
77
|
+
"qase_list_results",
|
|
78
|
+
"List test results for a test run in a Qase project",
|
|
79
|
+
ListResultsSchema,
|
|
80
|
+
async (args) => {
|
|
81
|
+
try {
|
|
82
|
+
const response = await client.listResults(args.project_code, args.run_id);
|
|
83
|
+
return {
|
|
84
|
+
content: [
|
|
85
|
+
{
|
|
86
|
+
type: "text",
|
|
87
|
+
text: JSON.stringify(response, null, 2),
|
|
88
|
+
},
|
|
89
|
+
],
|
|
90
|
+
};
|
|
91
|
+
} catch (error) {
|
|
92
|
+
return {
|
|
93
|
+
content: [{ type: "text", text: formatError(error) }],
|
|
94
|
+
isError: true,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
// Get single test result
|
|
101
|
+
server.tool(
|
|
102
|
+
"qase_get_result",
|
|
103
|
+
"Get a single test result by hash from a test run",
|
|
104
|
+
GetResultSchema,
|
|
105
|
+
async (args) => {
|
|
106
|
+
try {
|
|
107
|
+
const response = await client.getResult(args.project_code, args.run_id, args.hash);
|
|
108
|
+
return {
|
|
109
|
+
content: [
|
|
110
|
+
{
|
|
111
|
+
type: "text",
|
|
112
|
+
text: JSON.stringify(response, null, 2),
|
|
113
|
+
},
|
|
114
|
+
],
|
|
115
|
+
};
|
|
116
|
+
} catch (error) {
|
|
117
|
+
return {
|
|
118
|
+
content: [{ type: "text", text: formatError(error) }],
|
|
119
|
+
isError: true,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
// Add test result
|
|
126
|
+
server.tool(
|
|
127
|
+
"qase_add_result",
|
|
128
|
+
"Add a test result to a test run in a Qase project",
|
|
129
|
+
CreateResultSchema,
|
|
130
|
+
async (args) => {
|
|
131
|
+
try {
|
|
132
|
+
const { project_code, run_id, ...resultData } = args;
|
|
133
|
+
const response = await client.createResult(project_code, run_id, resultData);
|
|
134
|
+
return {
|
|
135
|
+
content: [
|
|
136
|
+
{
|
|
137
|
+
type: "text",
|
|
138
|
+
text: JSON.stringify(response, null, 2),
|
|
139
|
+
},
|
|
140
|
+
],
|
|
141
|
+
};
|
|
142
|
+
} catch (error) {
|
|
143
|
+
return {
|
|
144
|
+
content: [{ type: "text", text: formatError(error) }],
|
|
145
|
+
isError: true,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
// Add bulk test results
|
|
152
|
+
server.tool(
|
|
153
|
+
"qase_add_results_bulk",
|
|
154
|
+
"Add multiple test results to a test run in a Qase project",
|
|
155
|
+
CreateResultBulkSchema,
|
|
156
|
+
async (args) => {
|
|
157
|
+
try {
|
|
158
|
+
const response = await client.createResultBulk(
|
|
159
|
+
args.project_code,
|
|
160
|
+
args.run_id,
|
|
161
|
+
args.results
|
|
162
|
+
);
|
|
163
|
+
return {
|
|
164
|
+
content: [
|
|
165
|
+
{
|
|
166
|
+
type: "text",
|
|
167
|
+
text: JSON.stringify(response, null, 2),
|
|
168
|
+
},
|
|
169
|
+
],
|
|
170
|
+
};
|
|
171
|
+
} catch (error) {
|
|
172
|
+
return {
|
|
173
|
+
content: [{ type: "text", text: formatError(error) }],
|
|
174
|
+
isError: true,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
);
|
|
179
|
+
|
|
180
|
+
// Delete test result
|
|
181
|
+
server.tool(
|
|
182
|
+
"qase_delete_result",
|
|
183
|
+
"Delete a test result from a test run",
|
|
184
|
+
DeleteResultSchema,
|
|
185
|
+
async (args) => {
|
|
186
|
+
try {
|
|
187
|
+
const response = await client.deleteResult(args.project_code, args.run_id, args.hash);
|
|
188
|
+
return {
|
|
189
|
+
content: [
|
|
190
|
+
{
|
|
191
|
+
type: "text",
|
|
192
|
+
text: JSON.stringify(response, null, 2),
|
|
193
|
+
},
|
|
194
|
+
],
|
|
195
|
+
};
|
|
196
|
+
} catch (error) {
|
|
197
|
+
return {
|
|
198
|
+
content: [{ type: "text", text: formatError(error) }],
|
|
199
|
+
isError: true,
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
);
|
|
204
|
+
}
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
+
import { QaseClient, QaseApiError } from "../client.js";
|
|
4
|
+
|
|
5
|
+
// Zod schemas for validation
|
|
6
|
+
const ListRunsSchema = {
|
|
7
|
+
project_code: z.string().describe("Qase project code (e.g., 'DEMO', 'BPJS')"),
|
|
8
|
+
limit: z.number().optional().describe("Maximum number of results (default: 100)"),
|
|
9
|
+
offset: z.number().optional().describe("Offset for pagination"),
|
|
10
|
+
status: z.string().optional().describe("Filter by status ('active', 'complete', 'abort')"),
|
|
11
|
+
include: z.string().optional().describe("Include additional data (e.g., 'cases')"),
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const GetRunSchema = {
|
|
15
|
+
project_code: z.string().describe("Qase project code (e.g., 'DEMO', 'BPJS')"),
|
|
16
|
+
run_id: z.number().describe("Test run ID"),
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const CreateRunSchema = {
|
|
20
|
+
project_code: z.string().describe("Qase project code (e.g., 'DEMO', 'BPJS')"),
|
|
21
|
+
title: z.string().describe("Test run title"),
|
|
22
|
+
description: z.string().optional().describe("Test run description"),
|
|
23
|
+
environment_id: z.number().optional().describe("Environment ID"),
|
|
24
|
+
milestone_id: z.number().optional().describe("Associated milestone ID"),
|
|
25
|
+
plan_id: z.number().optional().describe("Test plan ID to use"),
|
|
26
|
+
cases: z.array(z.number()).optional().describe("Array of test case IDs to include"),
|
|
27
|
+
is_autotest: z.boolean().optional().describe("Mark as automated test run"),
|
|
28
|
+
tags: z.array(z.string()).optional().describe("Tags for the test run"),
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const CompleteRunSchema = {
|
|
32
|
+
project_code: z.string().describe("Qase project code (e.g., 'DEMO', 'BPJS')"),
|
|
33
|
+
run_id: z.number().describe("Test run ID to complete"),
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const DeleteRunSchema = {
|
|
37
|
+
project_code: z.string().describe("Qase project code (e.g., 'DEMO', 'BPJS')"),
|
|
38
|
+
run_id: z.number().describe("Test run ID to delete"),
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
function formatError(error: unknown): string {
|
|
42
|
+
if (error instanceof QaseApiError) {
|
|
43
|
+
let message = `Qase API Error (${error.statusCode}): ${error.message}`;
|
|
44
|
+
if (error.errorFields) {
|
|
45
|
+
message += `\nField errors: ${JSON.stringify(error.errorFields, null, 2)}`;
|
|
46
|
+
}
|
|
47
|
+
return message;
|
|
48
|
+
}
|
|
49
|
+
if (error instanceof Error) {
|
|
50
|
+
return `Error: ${error.message}`;
|
|
51
|
+
}
|
|
52
|
+
return `Unknown error: ${String(error)}`;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function registerRunTools(server: McpServer, client: QaseClient): void {
|
|
56
|
+
// List test runs
|
|
57
|
+
server.tool(
|
|
58
|
+
"qase_list_runs",
|
|
59
|
+
"List test runs in a Qase project with optional filtering",
|
|
60
|
+
ListRunsSchema,
|
|
61
|
+
async (args) => {
|
|
62
|
+
try {
|
|
63
|
+
const { project_code, ...filters } = args;
|
|
64
|
+
const response = await client.listRuns(project_code, filters);
|
|
65
|
+
return {
|
|
66
|
+
content: [
|
|
67
|
+
{
|
|
68
|
+
type: "text",
|
|
69
|
+
text: JSON.stringify(response, null, 2),
|
|
70
|
+
},
|
|
71
|
+
],
|
|
72
|
+
};
|
|
73
|
+
} catch (error) {
|
|
74
|
+
return {
|
|
75
|
+
content: [{ type: "text", text: formatError(error) }],
|
|
76
|
+
isError: true,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
// Get single test run
|
|
83
|
+
server.tool(
|
|
84
|
+
"qase_get_run",
|
|
85
|
+
"Get a single test run by ID from a Qase project",
|
|
86
|
+
GetRunSchema,
|
|
87
|
+
async (args) => {
|
|
88
|
+
try {
|
|
89
|
+
const response = await client.getRun(args.project_code, args.run_id);
|
|
90
|
+
return {
|
|
91
|
+
content: [
|
|
92
|
+
{
|
|
93
|
+
type: "text",
|
|
94
|
+
text: JSON.stringify(response, null, 2),
|
|
95
|
+
},
|
|
96
|
+
],
|
|
97
|
+
};
|
|
98
|
+
} catch (error) {
|
|
99
|
+
return {
|
|
100
|
+
content: [{ type: "text", text: formatError(error) }],
|
|
101
|
+
isError: true,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
// Create test run
|
|
108
|
+
server.tool(
|
|
109
|
+
"qase_create_run",
|
|
110
|
+
"Create a new test run in a Qase project",
|
|
111
|
+
CreateRunSchema,
|
|
112
|
+
async (args) => {
|
|
113
|
+
try {
|
|
114
|
+
const { project_code, ...runData } = args;
|
|
115
|
+
const response = await client.createRun(project_code, runData);
|
|
116
|
+
return {
|
|
117
|
+
content: [
|
|
118
|
+
{
|
|
119
|
+
type: "text",
|
|
120
|
+
text: JSON.stringify(response, null, 2),
|
|
121
|
+
},
|
|
122
|
+
],
|
|
123
|
+
};
|
|
124
|
+
} catch (error) {
|
|
125
|
+
return {
|
|
126
|
+
content: [{ type: "text", text: formatError(error) }],
|
|
127
|
+
isError: true,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
// Complete test run
|
|
134
|
+
server.tool(
|
|
135
|
+
"qase_complete_run",
|
|
136
|
+
"Complete/finish a test run in a Qase project",
|
|
137
|
+
CompleteRunSchema,
|
|
138
|
+
async (args) => {
|
|
139
|
+
try {
|
|
140
|
+
const response = await client.completeRun(args.project_code, args.run_id);
|
|
141
|
+
return {
|
|
142
|
+
content: [
|
|
143
|
+
{
|
|
144
|
+
type: "text",
|
|
145
|
+
text: JSON.stringify(response, null, 2),
|
|
146
|
+
},
|
|
147
|
+
],
|
|
148
|
+
};
|
|
149
|
+
} catch (error) {
|
|
150
|
+
return {
|
|
151
|
+
content: [{ type: "text", text: formatError(error) }],
|
|
152
|
+
isError: true,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
// Delete test run
|
|
159
|
+
server.tool(
|
|
160
|
+
"qase_delete_run",
|
|
161
|
+
"Delete a test run from a Qase project",
|
|
162
|
+
DeleteRunSchema,
|
|
163
|
+
async (args) => {
|
|
164
|
+
try {
|
|
165
|
+
const response = await client.deleteRun(args.project_code, args.run_id);
|
|
166
|
+
return {
|
|
167
|
+
content: [
|
|
168
|
+
{
|
|
169
|
+
type: "text",
|
|
170
|
+
text: JSON.stringify(response, null, 2),
|
|
171
|
+
},
|
|
172
|
+
],
|
|
173
|
+
};
|
|
174
|
+
} catch (error) {
|
|
175
|
+
return {
|
|
176
|
+
content: [{ type: "text", text: formatError(error) }],
|
|
177
|
+
isError: true,
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
);
|
|
182
|
+
}
|