@testkase/mcp-server 2.0.12 → 2.1.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 +86 -95
- package/build/index.js +1029 -723
- package/build/index.js.map +1 -1
- package/package.json +2 -2
package/build/index.js
CHANGED
|
@@ -3,854 +3,1160 @@ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
|
3
3
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
4
|
import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
5
5
|
import axios from "axios";
|
|
6
|
-
// Get configuration from environment variables
|
|
7
6
|
const config = {
|
|
8
7
|
apiBaseUrl: process.env.TESTKASE_API_BASE_URL || "https://apiqa.testkase.com",
|
|
9
8
|
patToken: process.env.TESTKASE_PAT_TOKEN,
|
|
10
9
|
};
|
|
11
|
-
//
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
async function getOrganizationId(providedOrgId) {
|
|
17
|
-
// If explicitly provided, use it
|
|
18
|
-
if (providedOrgId) {
|
|
19
|
-
return providedOrgId;
|
|
10
|
+
// ─── Auth helpers (reused from v1) ───────────────────────────────────────────
|
|
11
|
+
function validateAuth() {
|
|
12
|
+
if (!config.patToken) {
|
|
13
|
+
throw new Error("Authentication required. Please set TESTKASE_PAT_TOKEN environment variable. " +
|
|
14
|
+
"Generate a Personal Access Token (PAT) from your TestKase account settings.");
|
|
20
15
|
}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
16
|
+
if (!config.patToken.startsWith("xyz_")) {
|
|
17
|
+
throw new Error("Invalid PAT token format. TestKase PAT tokens must start with 'xyz_' prefix. " +
|
|
18
|
+
"Please generate a valid PAT from your TestKase account settings.");
|
|
24
19
|
}
|
|
25
|
-
|
|
20
|
+
}
|
|
21
|
+
function getApiHeaders() {
|
|
22
|
+
return {
|
|
23
|
+
"Content-Type": "application/json",
|
|
24
|
+
Authorization: `Bearer ${config.patToken}`,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
// ─── Cached org ID (auto-resolved from PAT, never a tool param) ─────────────
|
|
28
|
+
let cachedOrganizationId = null;
|
|
29
|
+
async function getOrganizationId() {
|
|
30
|
+
if (cachedOrganizationId !== null)
|
|
31
|
+
return cachedOrganizationId;
|
|
26
32
|
validateAuth();
|
|
27
33
|
try {
|
|
28
|
-
const response = await axios.get(`${config.apiBaseUrl}/api/v1/organizations/my-organization`, {
|
|
29
|
-
headers: {
|
|
30
|
-
"Content-Type": "application/json",
|
|
31
|
-
"Authorization": `Bearer ${config.patToken}`,
|
|
32
|
-
},
|
|
33
|
-
});
|
|
34
|
+
const response = await axios.get(`${config.apiBaseUrl}/api/v1/organizations/my-organization`, { headers: getApiHeaders() });
|
|
34
35
|
const orgData = response.data?.data || response.data;
|
|
35
36
|
if (orgData?.organization_id) {
|
|
36
|
-
|
|
37
|
-
cachedOrganizationId
|
|
38
|
-
return orgId;
|
|
37
|
+
cachedOrganizationId = orgData.organization_id;
|
|
38
|
+
return cachedOrganizationId;
|
|
39
39
|
}
|
|
40
40
|
throw new Error("Could not determine organization from PAT token");
|
|
41
41
|
}
|
|
42
42
|
catch (error) {
|
|
43
|
-
|
|
43
|
+
if (error instanceof Error && error.message.includes("Could not determine"))
|
|
44
|
+
throw error;
|
|
45
|
+
throw new Error("Failed to fetch organization. Check your PAT token.");
|
|
44
46
|
}
|
|
45
47
|
}
|
|
46
|
-
//
|
|
47
|
-
function
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
48
|
+
// ─── API call helper ─────────────────────────────────────────────────────────
|
|
49
|
+
async function callApi(method, path, options = {}) {
|
|
50
|
+
validateAuth();
|
|
51
|
+
const orgId = options.skipOrgId ? undefined : await getOrganizationId();
|
|
52
|
+
const queryParams = new URLSearchParams();
|
|
53
|
+
if (orgId)
|
|
54
|
+
queryParams.append("organizationId", orgId.toString());
|
|
55
|
+
if (options.query) {
|
|
56
|
+
for (const [key, val] of Object.entries(options.query)) {
|
|
57
|
+
if (val !== undefined && val !== null && val !== "") {
|
|
58
|
+
queryParams.append(key, String(val));
|
|
59
|
+
}
|
|
60
|
+
}
|
|
52
61
|
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
62
|
+
const qs = queryParams.toString();
|
|
63
|
+
const url = `${config.apiBaseUrl}/api/v1/${path}${qs ? `?${qs}` : ""}`;
|
|
64
|
+
const response = await axios({
|
|
65
|
+
method,
|
|
66
|
+
url,
|
|
67
|
+
headers: getApiHeaders(),
|
|
68
|
+
...(options.body !== undefined && { data: options.body }),
|
|
69
|
+
});
|
|
70
|
+
return response.data;
|
|
71
|
+
}
|
|
72
|
+
// ─── Error handler (reused) ──────────────────────────────────────────────────
|
|
73
|
+
function handleApiError(error) {
|
|
74
|
+
if (axios.isAxiosError(error)) {
|
|
75
|
+
const e = error;
|
|
76
|
+
let msg = `API request failed: ${e.message}`;
|
|
77
|
+
if (e.response) {
|
|
78
|
+
msg += `\nStatus: ${e.response.status}`;
|
|
79
|
+
msg += `\nResponse: ${JSON.stringify(e.response.data)}`;
|
|
80
|
+
}
|
|
81
|
+
return new Error(msg);
|
|
56
82
|
}
|
|
57
|
-
|
|
58
|
-
"2) ASK the user which project they want to work with, " +
|
|
59
|
-
"3) Call set_active_project with the user's chosen project ID. " +
|
|
60
|
-
"Do NOT select a project automatically.");
|
|
83
|
+
return error;
|
|
61
84
|
}
|
|
62
|
-
//
|
|
63
|
-
function
|
|
64
|
-
|
|
85
|
+
// ─── JSON response helper ────────────────────────────────────────────────────
|
|
86
|
+
function jsonResponse(data) {
|
|
87
|
+
return {
|
|
88
|
+
content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
|
|
89
|
+
};
|
|
65
90
|
}
|
|
66
|
-
//
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
91
|
+
// ─── Param normalizer (handles camelCase / snake_case) ───────────────────────
|
|
92
|
+
function p(args, ...names) {
|
|
93
|
+
for (const n of names) {
|
|
94
|
+
if (args[n] !== undefined)
|
|
95
|
+
return args[n];
|
|
96
|
+
}
|
|
97
|
+
return undefined;
|
|
98
|
+
}
|
|
99
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
100
|
+
// TOOL DEFINITIONS (11 tools)
|
|
101
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
102
|
+
const LIST_PROJECTS_TOOL = {
|
|
103
|
+
name: "list_projects",
|
|
104
|
+
description: "List all projects the authenticated user has access to. " +
|
|
105
|
+
"Use this first to find a projectId, then pass it to other tools.",
|
|
71
106
|
inputSchema: {
|
|
72
107
|
type: "object",
|
|
73
108
|
properties: {
|
|
74
|
-
search: { type: "string", description: "Search
|
|
109
|
+
search: { type: "string", description: "Search by project name or ID" },
|
|
75
110
|
page: { type: "number", description: "Page number (default: 1)" },
|
|
76
111
|
limit: { type: "number", description: "Items per page (default: 20)" },
|
|
77
|
-
filters: { type: "string", description: "JSON filters. Example: {\"status\":[\"active\"],\"priority\":[\"high\"]}" },
|
|
78
112
|
},
|
|
79
113
|
required: [],
|
|
80
114
|
},
|
|
81
115
|
};
|
|
82
|
-
const
|
|
83
|
-
name: "
|
|
84
|
-
description: "Get
|
|
116
|
+
const GET_PROJECT_STRUCTURE_TOOL = {
|
|
117
|
+
name: "get_project_structure",
|
|
118
|
+
description: "Get the structure of a project: folders, labels, and/or team members. " +
|
|
119
|
+
"Useful to discover folder IDs, label names, and member IDs before creating or updating items.",
|
|
85
120
|
inputSchema: {
|
|
86
121
|
type: "object",
|
|
87
122
|
properties: {
|
|
88
|
-
|
|
123
|
+
projectId: { type: "string", description: "Project ID (e.g. PRJ-1001)" },
|
|
124
|
+
section: {
|
|
125
|
+
type: "string",
|
|
126
|
+
enum: ["TESTCASE", "TEST_CYCLE", "TEST_PLAN"],
|
|
127
|
+
description: "Folder section (default: TESTCASE)",
|
|
128
|
+
},
|
|
129
|
+
include: {
|
|
130
|
+
type: "string",
|
|
131
|
+
description: "Comma-separated list of what to include: folders,labels,members (default: all)",
|
|
132
|
+
},
|
|
89
133
|
},
|
|
90
|
-
required: ["
|
|
134
|
+
required: ["projectId"],
|
|
91
135
|
},
|
|
92
136
|
};
|
|
93
|
-
const
|
|
94
|
-
name: "
|
|
95
|
-
description: "
|
|
137
|
+
const SEARCH_TESTCASES_TOOL = {
|
|
138
|
+
name: "search_testcases",
|
|
139
|
+
description: "Search and list test cases in a project. Supports text search, filters, sorting, and pagination.",
|
|
96
140
|
inputSchema: {
|
|
97
141
|
type: "object",
|
|
98
142
|
properties: {
|
|
99
|
-
|
|
143
|
+
projectId: { type: "string", description: "Project ID" },
|
|
144
|
+
search: { type: "string", description: "Search text (title or summary)" },
|
|
145
|
+
filters: {
|
|
146
|
+
type: "string",
|
|
147
|
+
description: 'JSON string of filters. Example: {"status":["active"],"priority":["high","critical"]}',
|
|
148
|
+
},
|
|
149
|
+
page: { type: "number", description: "Page number (default: 1)" },
|
|
150
|
+
limit: { type: "number", description: "Items per page (default: 20)" },
|
|
151
|
+
sortBy: { type: "string", description: "Field to sort by (e.g. created_at, title)" },
|
|
152
|
+
sortOrder: { type: "string", enum: ["asc", "desc"], description: "Sort direction" },
|
|
100
153
|
},
|
|
101
|
-
required: [],
|
|
102
|
-
},
|
|
103
|
-
};
|
|
104
|
-
const GET_LABELS_TOOL = {
|
|
105
|
-
name: "get_labels",
|
|
106
|
-
description: "Get all labels available in the active project for categorizing testcases.",
|
|
107
|
-
inputSchema: {
|
|
108
|
-
type: "object",
|
|
109
|
-
properties: {},
|
|
110
|
-
required: [],
|
|
111
|
-
},
|
|
112
|
-
};
|
|
113
|
-
const GET_MY_PROJECTS_TOOL = {
|
|
114
|
-
name: "get_my_projects",
|
|
115
|
-
description: "IMPORTANT: Call this FIRST. Lists all projects. " +
|
|
116
|
-
"ALWAYS ASK the user which project to use, then call set_active_project with their choice.",
|
|
117
|
-
inputSchema: {
|
|
118
|
-
type: "object",
|
|
119
|
-
properties: {},
|
|
120
|
-
required: [],
|
|
154
|
+
required: ["projectId"],
|
|
121
155
|
},
|
|
122
156
|
};
|
|
123
|
-
const
|
|
124
|
-
name: "
|
|
125
|
-
description: "
|
|
157
|
+
const GET_TESTCASE_TOOL = {
|
|
158
|
+
name: "get_testcase",
|
|
159
|
+
description: "Get full details of a single test case including test steps and attachments.",
|
|
126
160
|
inputSchema: {
|
|
127
161
|
type: "object",
|
|
128
162
|
properties: {
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
priority: { type: "string", description: "low, medium, high, or critical" },
|
|
132
|
-
folder_id: { type: "string", description: "Folder ID from get_folders" },
|
|
133
|
-
test_steps: { type: "string", description: "JSON array: [{\"step\":\"...\",\"expected_result\":\"...\"}]" },
|
|
163
|
+
projectId: { type: "string", description: "Project ID" },
|
|
164
|
+
testcaseId: { type: "string", description: "Test case ID (e.g. TEST-123)" },
|
|
134
165
|
},
|
|
135
|
-
required: ["
|
|
166
|
+
required: ["projectId", "testcaseId"],
|
|
136
167
|
},
|
|
137
168
|
};
|
|
138
|
-
const
|
|
139
|
-
name: "
|
|
140
|
-
description: "Create
|
|
169
|
+
const MANAGE_TESTCASE_TOOL = {
|
|
170
|
+
name: "manage_testcase",
|
|
171
|
+
description: "Create, bulk create, update, or delete test cases. " +
|
|
172
|
+
"Set 'action' to: create, create_bulk, update, or delete.",
|
|
141
173
|
inputSchema: {
|
|
142
174
|
type: "object",
|
|
143
175
|
properties: {
|
|
144
|
-
|
|
176
|
+
projectId: { type: "string", description: "Project ID" },
|
|
177
|
+
action: {
|
|
178
|
+
type: "string",
|
|
179
|
+
enum: ["create", "create_bulk", "update", "delete"],
|
|
180
|
+
description: "Action to perform",
|
|
181
|
+
},
|
|
182
|
+
// create params
|
|
183
|
+
title: { type: "string", description: "[create] Test case title" },
|
|
184
|
+
summary: { type: "string", description: "[create] Description/summary" },
|
|
185
|
+
priority: {
|
|
186
|
+
type: "string",
|
|
187
|
+
description: "[create] Priority: low, medium, high, or critical",
|
|
188
|
+
},
|
|
189
|
+
folder_id: { type: "string", description: "[create] Folder ID to place the test case in" },
|
|
190
|
+
labels: {
|
|
191
|
+
type: "string",
|
|
192
|
+
description: "[create] Comma-separated label names",
|
|
193
|
+
},
|
|
194
|
+
test_steps: {
|
|
195
|
+
type: "string",
|
|
196
|
+
description: '[create] JSON array of test steps: [{"step":"...","expected_result":"..."}]',
|
|
197
|
+
},
|
|
198
|
+
// create_bulk params
|
|
199
|
+
testcases: {
|
|
200
|
+
type: "string",
|
|
201
|
+
description: "[create_bulk] JSON array of test cases, each with title, summary, priority, folder_id, test_steps",
|
|
202
|
+
},
|
|
203
|
+
// update params
|
|
204
|
+
ids: {
|
|
205
|
+
type: "string",
|
|
206
|
+
description: "[update/delete] Comma-separated test case IDs",
|
|
207
|
+
},
|
|
208
|
+
field: {
|
|
209
|
+
type: "string",
|
|
210
|
+
description: "[update] Field name to update (title, summary, priority, status, etc.)",
|
|
211
|
+
},
|
|
212
|
+
value: { type: "string", description: "[update] New value for the field" },
|
|
145
213
|
},
|
|
146
|
-
required: ["
|
|
214
|
+
required: ["projectId", "action"],
|
|
147
215
|
},
|
|
148
216
|
};
|
|
149
|
-
const
|
|
150
|
-
name: "
|
|
151
|
-
description: "
|
|
217
|
+
const MANAGE_FOLDER_TOOL = {
|
|
218
|
+
name: "manage_folder",
|
|
219
|
+
description: "Create, rename, move, or delete folders. Works across all sections (TESTCASE, TEST_CYCLE, TEST_PLAN).",
|
|
152
220
|
inputSchema: {
|
|
153
221
|
type: "object",
|
|
154
222
|
properties: {
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
223
|
+
projectId: { type: "string", description: "Project ID" },
|
|
224
|
+
action: {
|
|
225
|
+
type: "string",
|
|
226
|
+
enum: ["create", "rename", "move", "delete"],
|
|
227
|
+
description: "Action to perform",
|
|
228
|
+
},
|
|
229
|
+
section: {
|
|
230
|
+
type: "string",
|
|
231
|
+
enum: ["TESTCASE", "TEST_CYCLE", "TEST_PLAN"],
|
|
232
|
+
description: "Folder section (default: TESTCASE)",
|
|
233
|
+
},
|
|
234
|
+
name: { type: "string", description: "[create/rename] Folder name (max 50 chars)" },
|
|
235
|
+
folderId: {
|
|
236
|
+
type: "number",
|
|
237
|
+
description: "[rename/move/delete] ID of the folder to modify",
|
|
238
|
+
},
|
|
239
|
+
parentId: {
|
|
240
|
+
type: "number",
|
|
241
|
+
description: "[create] Parent folder ID for subfolder. [move] New parent ID (null for root).",
|
|
242
|
+
},
|
|
158
243
|
},
|
|
159
|
-
required: ["
|
|
244
|
+
required: ["projectId", "action"],
|
|
160
245
|
},
|
|
161
246
|
};
|
|
162
|
-
const
|
|
163
|
-
name: "
|
|
164
|
-
description: "
|
|
247
|
+
const SEARCH_TEST_CYCLES_TOOL = {
|
|
248
|
+
name: "search_test_cycles",
|
|
249
|
+
description: "List and search test cycles in a project with execution progress.",
|
|
165
250
|
inputSchema: {
|
|
166
251
|
type: "object",
|
|
167
252
|
properties: {
|
|
168
|
-
|
|
253
|
+
projectId: { type: "string", description: "Project ID" },
|
|
254
|
+
search: { type: "string", description: "Search by title" },
|
|
255
|
+
folderId: { type: "number", description: "Filter by folder ID" },
|
|
256
|
+
status: { type: "string", description: "Filter by status (open, closed, etc.)" },
|
|
257
|
+
page: { type: "number", description: "Page number (default: 1)" },
|
|
258
|
+
limit: { type: "number", description: "Items per page (default: 20)" },
|
|
169
259
|
},
|
|
170
|
-
required: ["
|
|
260
|
+
required: ["projectId"],
|
|
171
261
|
},
|
|
172
262
|
};
|
|
173
|
-
const
|
|
174
|
-
name: "
|
|
175
|
-
description: "
|
|
263
|
+
const MANAGE_TEST_CYCLE_TOOL = {
|
|
264
|
+
name: "manage_test_cycle",
|
|
265
|
+
description: "Manage test cycles: create, update, delete, get details, list/link/unlink/assign test cases. " +
|
|
266
|
+
"Set 'action' to: create, update, delete, get_details, get_testcases, link_testcases, unlink_testcases, assign_testcases.",
|
|
176
267
|
inputSchema: {
|
|
177
268
|
type: "object",
|
|
178
269
|
properties: {
|
|
179
|
-
|
|
180
|
-
|
|
270
|
+
projectId: { type: "string", description: "Project ID" },
|
|
271
|
+
action: {
|
|
272
|
+
type: "string",
|
|
273
|
+
enum: [
|
|
274
|
+
"create",
|
|
275
|
+
"update",
|
|
276
|
+
"delete",
|
|
277
|
+
"get_details",
|
|
278
|
+
"get_testcases",
|
|
279
|
+
"link_testcases",
|
|
280
|
+
"unlink_testcases",
|
|
281
|
+
"assign_testcases",
|
|
282
|
+
],
|
|
283
|
+
description: "Action to perform",
|
|
284
|
+
},
|
|
285
|
+
// Identifiers
|
|
286
|
+
cycleId: {
|
|
287
|
+
type: "string",
|
|
288
|
+
description: "Test cycle ID (e.g. TCYCLE-1). Required for update/delete/get_details/get_testcases/link/unlink/assign.",
|
|
289
|
+
},
|
|
290
|
+
// create/update fields
|
|
291
|
+
title: { type: "string", description: "[create/update] Cycle title" },
|
|
292
|
+
summary: { type: "string", description: "[create/update] Description" },
|
|
293
|
+
status: {
|
|
294
|
+
type: "string",
|
|
295
|
+
description: "[create/update] Status: open, closed, in progress",
|
|
296
|
+
},
|
|
297
|
+
planned_start_date: {
|
|
298
|
+
type: "string",
|
|
299
|
+
description: "[create/update] Start date (YYYY-MM-DD)",
|
|
300
|
+
},
|
|
301
|
+
planned_end_date: {
|
|
302
|
+
type: "string",
|
|
303
|
+
description: "[create/update] End date (YYYY-MM-DD)",
|
|
304
|
+
},
|
|
305
|
+
folder_id: {
|
|
306
|
+
type: "number",
|
|
307
|
+
description: "[create/update] Folder ID",
|
|
308
|
+
},
|
|
309
|
+
// delete
|
|
310
|
+
ids: {
|
|
311
|
+
type: "string",
|
|
312
|
+
description: "[delete] Comma-separated cycle IDs to delete",
|
|
313
|
+
},
|
|
314
|
+
// link/unlink/assign
|
|
315
|
+
testcase_ids: {
|
|
316
|
+
type: "string",
|
|
317
|
+
description: "[link_testcases/unlink_testcases/assign_testcases] Comma-separated test case IDs",
|
|
318
|
+
},
|
|
319
|
+
assignee_id: {
|
|
320
|
+
type: "number",
|
|
321
|
+
description: "[assign_testcases] User ID to assign",
|
|
322
|
+
},
|
|
323
|
+
// get_testcases params
|
|
324
|
+
search: { type: "string", description: "[get_testcases] Search text" },
|
|
325
|
+
page: { type: "number", description: "[get_testcases] Page number" },
|
|
326
|
+
limit: { type: "number", description: "[get_testcases] Items per page" },
|
|
181
327
|
},
|
|
182
|
-
required: ["
|
|
328
|
+
required: ["projectId", "action"],
|
|
183
329
|
},
|
|
184
330
|
};
|
|
185
|
-
const
|
|
186
|
-
name: "
|
|
187
|
-
description: "
|
|
331
|
+
const EXECUTE_TESTS_TOOL = {
|
|
332
|
+
name: "execute_tests",
|
|
333
|
+
description: "Record test execution results for test cases in a test cycle. " +
|
|
334
|
+
"Pass a single result or an array of results. " +
|
|
335
|
+
"Valid execution statuses: pass, fail, blocked, not_executed, in_progress.",
|
|
188
336
|
inputSchema: {
|
|
189
337
|
type: "object",
|
|
190
338
|
properties: {
|
|
191
|
-
|
|
339
|
+
projectId: { type: "string", description: "Project ID" },
|
|
340
|
+
cycleId: { type: "string", description: "Test cycle ID (e.g. TCYCLE-1)" },
|
|
341
|
+
results: {
|
|
342
|
+
type: "string",
|
|
343
|
+
description: 'JSON array of results: [{"testcase_id":"TEST-1","execution_status":"pass","actual_result":"Worked","environment":"staging"}]. ' +
|
|
344
|
+
"Each object must have testcase_id and execution_status. actual_result and environment are optional.",
|
|
345
|
+
},
|
|
192
346
|
},
|
|
193
|
-
required: ["
|
|
347
|
+
required: ["projectId", "cycleId", "results"],
|
|
194
348
|
},
|
|
195
349
|
};
|
|
196
|
-
const
|
|
197
|
-
name: "
|
|
198
|
-
description: "
|
|
350
|
+
const MANAGE_TEST_PLAN_TOOL = {
|
|
351
|
+
name: "manage_test_plan",
|
|
352
|
+
description: "Manage test plans: create, update, delete, list, get details, link/unlink cycles, get cycles, get test cases. " +
|
|
353
|
+
"Set 'action' to: create, update, delete, list, get_details, link_cycles, unlink_cycles, get_cycles, get_testcases.",
|
|
199
354
|
inputSchema: {
|
|
200
355
|
type: "object",
|
|
201
|
-
properties: {
|
|
202
|
-
|
|
356
|
+
properties: {
|
|
357
|
+
projectId: { type: "string", description: "Project ID" },
|
|
358
|
+
action: {
|
|
359
|
+
type: "string",
|
|
360
|
+
enum: [
|
|
361
|
+
"create",
|
|
362
|
+
"update",
|
|
363
|
+
"delete",
|
|
364
|
+
"list",
|
|
365
|
+
"get_details",
|
|
366
|
+
"link_cycles",
|
|
367
|
+
"unlink_cycles",
|
|
368
|
+
"get_cycles",
|
|
369
|
+
"get_testcases",
|
|
370
|
+
],
|
|
371
|
+
description: "Action to perform",
|
|
372
|
+
},
|
|
373
|
+
// Identifier
|
|
374
|
+
planId: {
|
|
375
|
+
type: "string",
|
|
376
|
+
description: "Test plan ID (e.g. TPLAN-1). Required for update/delete/get_details/link_cycles/unlink_cycles/get_cycles/get_testcases.",
|
|
377
|
+
},
|
|
378
|
+
// create/update fields
|
|
379
|
+
title: { type: "string", description: "[create/update] Plan title" },
|
|
380
|
+
summary: { type: "string", description: "[create/update] Description" },
|
|
381
|
+
status: { type: "string", description: "[create/update] Status" },
|
|
382
|
+
planned_start_date: {
|
|
383
|
+
type: "string",
|
|
384
|
+
description: "[create/update] Start date (YYYY-MM-DD)",
|
|
385
|
+
},
|
|
386
|
+
planned_end_date: {
|
|
387
|
+
type: "string",
|
|
388
|
+
description: "[create/update] End date (YYYY-MM-DD)",
|
|
389
|
+
},
|
|
390
|
+
folder_id: { type: "number", description: "[create/update] Folder ID" },
|
|
391
|
+
// delete
|
|
392
|
+
ids: {
|
|
393
|
+
type: "string",
|
|
394
|
+
description: "[delete] Comma-separated plan IDs to delete",
|
|
395
|
+
},
|
|
396
|
+
// link/unlink cycles
|
|
397
|
+
cycle_ids: {
|
|
398
|
+
type: "string",
|
|
399
|
+
description: "[link_cycles/unlink_cycles] Comma-separated test cycle IDs",
|
|
400
|
+
},
|
|
401
|
+
// list/get_cycles/get_testcases pagination
|
|
402
|
+
search: { type: "string", description: "[list/get_cycles/get_testcases] Search text" },
|
|
403
|
+
page: { type: "number", description: "[list/get_cycles/get_testcases] Page number" },
|
|
404
|
+
limit: { type: "number", description: "[list/get_cycles/get_testcases] Items per page" },
|
|
405
|
+
},
|
|
406
|
+
required: ["projectId", "action"],
|
|
203
407
|
},
|
|
204
408
|
};
|
|
205
|
-
const
|
|
206
|
-
name: "
|
|
207
|
-
description: "
|
|
409
|
+
const GET_REPORT_TOOL = {
|
|
410
|
+
name: "get_report",
|
|
411
|
+
description: "Get reports and analytics for a project. Covers execution, coverage, trends, team, defects, and AI insights. " +
|
|
412
|
+
"Available report types: execution_summary, execution_by_cycle, execution_by_tester, execution_by_priority, " +
|
|
413
|
+
"execution_by_environment, execution_by_folder, requirement_coverage, traceability_matrix, " +
|
|
414
|
+
"failed_requirements, uncovered_requirements, testcase_coverage, unlinked_testcases, " +
|
|
415
|
+
"defects_by_folder, tester_workload, testcase_distribution, " +
|
|
416
|
+
"execution_trend, execution_burnup, execution_burndown, test_creation, cycle_comparison, " +
|
|
417
|
+
"scorecard_by_folder, scorecard_by_tester, created_vs_executed, execution_by_automation, " +
|
|
418
|
+
"defects_by_cycle, defects_by_tester, requirement_coverage_trend, " +
|
|
419
|
+
"flaky_tests, release_readiness, risk_heatmap_folder, risk_heatmap_feature, " +
|
|
420
|
+
"testcase_quality, predictive_failure, smart_prioritization, tester_effectiveness, " +
|
|
421
|
+
"stale_tests, defect_hotspots, cycle_health, suite_optimization, " +
|
|
422
|
+
"requirement_risk_matrix, execution_velocity, project_health.",
|
|
208
423
|
inputSchema: {
|
|
209
424
|
type: "object",
|
|
210
425
|
properties: {
|
|
211
|
-
projectId: { type: "string", description: "Project ID
|
|
426
|
+
projectId: { type: "string", description: "Project ID" },
|
|
427
|
+
report_type: {
|
|
428
|
+
type: "string",
|
|
429
|
+
description: "Report type (see description for full list)",
|
|
430
|
+
},
|
|
431
|
+
test_cycle_ids: {
|
|
432
|
+
type: "string",
|
|
433
|
+
description: "Comma-separated test cycle IDs to filter by",
|
|
434
|
+
},
|
|
435
|
+
test_plan_ids: {
|
|
436
|
+
type: "string",
|
|
437
|
+
description: "Comma-separated test plan IDs to filter by",
|
|
438
|
+
},
|
|
439
|
+
startDate: { type: "string", description: "Start date filter (YYYY-MM-DD)" },
|
|
440
|
+
endDate: { type: "string", description: "End date filter (YYYY-MM-DD)" },
|
|
441
|
+
granularity: {
|
|
442
|
+
type: "string",
|
|
443
|
+
enum: ["day", "week", "month"],
|
|
444
|
+
description: "Granularity for trend reports",
|
|
445
|
+
},
|
|
446
|
+
// cycle_comparison specific
|
|
447
|
+
cycle_ids: {
|
|
448
|
+
type: "string",
|
|
449
|
+
description: "[cycle_comparison] Comma-separated cycle IDs to compare",
|
|
450
|
+
},
|
|
212
451
|
},
|
|
213
|
-
required: ["projectId"],
|
|
452
|
+
required: ["projectId", "report_type"],
|
|
214
453
|
},
|
|
215
454
|
};
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
455
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
456
|
+
// REPORT TYPE → ENDPOINT MAPPING
|
|
457
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
458
|
+
const REPORT_TYPE_MAP = {
|
|
459
|
+
// Execution
|
|
460
|
+
execution_summary: "execution/summary",
|
|
461
|
+
execution_by_cycle: "execution/by-cycle",
|
|
462
|
+
execution_by_tester: "execution/by-tester",
|
|
463
|
+
execution_by_priority: "execution/by-priority",
|
|
464
|
+
execution_by_environment: "execution/by-environment",
|
|
465
|
+
execution_by_folder: "execution/by-folder",
|
|
466
|
+
// Coverage
|
|
467
|
+
requirement_coverage: "coverage/requirement-coverage",
|
|
468
|
+
traceability_matrix: "coverage/traceability-matrix",
|
|
469
|
+
failed_requirements: "coverage/failed-requirements",
|
|
470
|
+
uncovered_requirements: "coverage/uncovered-requirements",
|
|
471
|
+
testcase_coverage: "coverage/testcase-coverage",
|
|
472
|
+
unlinked_testcases: "coverage/unlinked-testcases",
|
|
473
|
+
// Defects
|
|
474
|
+
defects_by_folder: "defects/by-folder",
|
|
475
|
+
// Team
|
|
476
|
+
tester_workload: "team/tester-workload",
|
|
477
|
+
testcase_distribution: "team/testcase-distribution",
|
|
478
|
+
// Trends
|
|
479
|
+
execution_trend: "trends/execution-trend",
|
|
480
|
+
execution_burnup: "trends/execution-burnup",
|
|
481
|
+
execution_burndown: "trends/execution-burndown",
|
|
482
|
+
test_creation: "trends/test-creation",
|
|
483
|
+
cycle_comparison: "trends/cycle-comparison",
|
|
484
|
+
scorecard_by_folder: "trends/scorecard-by-folder",
|
|
485
|
+
scorecard_by_tester: "trends/scorecard-by-tester",
|
|
486
|
+
created_vs_executed: "trends/created-vs-executed",
|
|
487
|
+
execution_by_automation: "trends/execution-by-automation",
|
|
488
|
+
defects_by_cycle: "trends/defects-by-cycle",
|
|
489
|
+
defects_by_tester: "trends/defects-by-tester",
|
|
490
|
+
requirement_coverage_trend: "trends/requirement-coverage-trend",
|
|
491
|
+
// AI Insights
|
|
492
|
+
flaky_tests: "ai/flaky-tests",
|
|
493
|
+
release_readiness: "ai/release-readiness",
|
|
494
|
+
risk_heatmap_folder: "ai/risk-heatmap-folder",
|
|
495
|
+
risk_heatmap_feature: "ai/risk-heatmap-feature",
|
|
496
|
+
testcase_quality: "ai/testcase-quality",
|
|
497
|
+
predictive_failure: "ai/predictive-failure",
|
|
498
|
+
smart_prioritization: "ai/smart-prioritization",
|
|
499
|
+
tester_effectiveness: "ai/tester-effectiveness",
|
|
500
|
+
stale_tests: "ai/stale-tests",
|
|
501
|
+
defect_hotspots: "ai/defect-hotspots",
|
|
502
|
+
cycle_health: "ai/cycle-health",
|
|
503
|
+
suite_optimization: "ai/suite-optimization",
|
|
504
|
+
requirement_risk_matrix: "ai/requirement-risk-matrix",
|
|
505
|
+
execution_velocity: "ai/execution-velocity",
|
|
506
|
+
project_health: "ai/project-health",
|
|
224
507
|
};
|
|
225
|
-
//
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
508
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
509
|
+
// TOOL HANDLERS
|
|
510
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
511
|
+
// 1. list_projects
|
|
512
|
+
async function handleListProjects(args) {
|
|
513
|
+
const data = await callApi("get", "projects/user/my-projects", {
|
|
514
|
+
query: {
|
|
515
|
+
search: p(args, "search"),
|
|
516
|
+
page: p(args, "page"),
|
|
517
|
+
limit: p(args, "limit"),
|
|
518
|
+
},
|
|
519
|
+
});
|
|
520
|
+
return jsonResponse(data);
|
|
521
|
+
}
|
|
522
|
+
// 2. get_project_structure
|
|
523
|
+
async function handleGetProjectStructure(args) {
|
|
524
|
+
const projectId = p(args, "projectId", "project_id");
|
|
525
|
+
if (!projectId)
|
|
526
|
+
throw new Error("projectId is required");
|
|
527
|
+
const section = p(args, "section") || "TESTCASE";
|
|
528
|
+
const includeRaw = p(args, "include") || "folders,labels,members";
|
|
529
|
+
const includes = includeRaw.split(",").map((s) => s.trim().toLowerCase());
|
|
530
|
+
const orgId = await getOrganizationId();
|
|
531
|
+
const result = {};
|
|
532
|
+
const promises = [];
|
|
533
|
+
if (includes.includes("folders")) {
|
|
534
|
+
promises.push(callApi("get", "folders/get-folders", {
|
|
535
|
+
query: { projectId, section },
|
|
536
|
+
}).then((data) => {
|
|
537
|
+
result.folders = data;
|
|
538
|
+
}));
|
|
539
|
+
}
|
|
540
|
+
if (includes.includes("labels")) {
|
|
541
|
+
promises.push(callApi("get", `projects/labels/get-label/${projectId}`).then((data) => {
|
|
542
|
+
result.labels = data;
|
|
543
|
+
}));
|
|
544
|
+
}
|
|
545
|
+
if (includes.includes("members")) {
|
|
546
|
+
promises.push(callApi("get", `projects/${projectId}/team-members`).then((data) => {
|
|
547
|
+
result.members = data;
|
|
548
|
+
}));
|
|
549
|
+
}
|
|
550
|
+
await Promise.all(promises);
|
|
551
|
+
return jsonResponse(result);
|
|
552
|
+
}
|
|
553
|
+
// 3. search_testcases
|
|
554
|
+
async function handleSearchTestcases(args) {
|
|
555
|
+
const projectId = p(args, "projectId", "project_id");
|
|
556
|
+
if (!projectId)
|
|
557
|
+
throw new Error("projectId is required");
|
|
558
|
+
const data = await callApi("get", `projects/testcases/get-testcase/${projectId}`, {
|
|
559
|
+
query: {
|
|
560
|
+
search: p(args, "search"),
|
|
561
|
+
filters: p(args, "filters"),
|
|
562
|
+
page: p(args, "page"),
|
|
563
|
+
limit: p(args, "limit"),
|
|
564
|
+
sortBy: p(args, "sortBy", "sort_by"),
|
|
565
|
+
sortOrder: p(args, "sortOrder", "sort_order"),
|
|
566
|
+
},
|
|
567
|
+
});
|
|
568
|
+
return jsonResponse(data);
|
|
569
|
+
}
|
|
570
|
+
// 4. get_testcase
|
|
571
|
+
async function handleGetTestcase(args) {
|
|
572
|
+
const projectId = p(args, "projectId", "project_id");
|
|
573
|
+
const testcaseId = p(args, "testcaseId", "testcase_id", "id");
|
|
574
|
+
if (!projectId)
|
|
575
|
+
throw new Error("projectId is required");
|
|
576
|
+
if (!testcaseId)
|
|
577
|
+
throw new Error("testcaseId is required");
|
|
578
|
+
const data = await callApi("get", `projects/testcases/get-testcase-detail/${projectId}/${testcaseId}`);
|
|
579
|
+
return jsonResponse(data);
|
|
580
|
+
}
|
|
581
|
+
// 5. manage_testcase
|
|
582
|
+
async function handleManageTestcase(args) {
|
|
583
|
+
const projectId = p(args, "projectId", "project_id");
|
|
584
|
+
const action = p(args, "action");
|
|
585
|
+
if (!projectId)
|
|
586
|
+
throw new Error("projectId is required");
|
|
587
|
+
if (!action)
|
|
588
|
+
throw new Error("action is required");
|
|
589
|
+
const orgId = await getOrganizationId();
|
|
590
|
+
switch (action) {
|
|
591
|
+
case "create": {
|
|
592
|
+
const title = p(args, "title", "name");
|
|
593
|
+
if (!title)
|
|
594
|
+
throw new Error("title is required for create action");
|
|
595
|
+
const testcaseData = { title };
|
|
596
|
+
if (p(args, "summary", "description"))
|
|
597
|
+
testcaseData.summary = p(args, "summary", "description");
|
|
598
|
+
if (p(args, "priority"))
|
|
599
|
+
testcaseData.priority = p(args, "priority");
|
|
600
|
+
if (p(args, "folder_id", "folderId"))
|
|
601
|
+
testcaseData.folder_id = p(args, "folder_id", "folderId");
|
|
602
|
+
const labelsRaw = p(args, "labels");
|
|
603
|
+
if (labelsRaw) {
|
|
604
|
+
testcaseData.labels =
|
|
605
|
+
typeof labelsRaw === "string"
|
|
606
|
+
? labelsRaw.split(",").map((l) => l.trim())
|
|
607
|
+
: labelsRaw;
|
|
608
|
+
}
|
|
609
|
+
// Parse test_steps
|
|
610
|
+
const stepsRaw = p(args, "test_steps", "testSteps", "steps");
|
|
611
|
+
const testSteps = stepsRaw
|
|
612
|
+
? typeof stepsRaw === "string"
|
|
613
|
+
? JSON.parse(stepsRaw)
|
|
614
|
+
: stepsRaw
|
|
615
|
+
: null;
|
|
616
|
+
// Step 1: Create testcase
|
|
617
|
+
const createResult = await callApi("post", `projects/testcases/create-testcase/${projectId}`, { body: testcaseData });
|
|
618
|
+
// Step 2: Add test steps if provided
|
|
619
|
+
const created = createResult?.data || createResult;
|
|
620
|
+
const tcId = created?.id || created?.testcaseId;
|
|
621
|
+
if (testSteps && Array.isArray(testSteps) && testSteps.length > 0 && tcId) {
|
|
622
|
+
await callApi("post", `projects/testcases/teststeps/${projectId}/${tcId}`, {
|
|
623
|
+
body: { steps: testSteps },
|
|
624
|
+
});
|
|
625
|
+
}
|
|
626
|
+
return jsonResponse(createResult);
|
|
270
627
|
}
|
|
271
|
-
|
|
272
|
-
|
|
628
|
+
case "create_bulk": {
|
|
629
|
+
const rawTestcases = p(args, "testcases", "test_cases", "items");
|
|
630
|
+
if (!rawTestcases)
|
|
631
|
+
throw new Error("testcases is required for create_bulk action");
|
|
632
|
+
const testcases = typeof rawTestcases === "string" ? JSON.parse(rawTestcases) : rawTestcases;
|
|
633
|
+
// Separate test_steps for post-creation
|
|
634
|
+
const withSteps = testcases.map((tc) => {
|
|
635
|
+
const normalized = { title: tc.title || tc.name };
|
|
636
|
+
if (tc.summary || tc.description)
|
|
637
|
+
normalized.summary = tc.summary || tc.description;
|
|
638
|
+
if (tc.priority)
|
|
639
|
+
normalized.priority = tc.priority;
|
|
640
|
+
if (tc.folder_id || tc.folderId)
|
|
641
|
+
normalized.folder_id = tc.folder_id || tc.folderId;
|
|
642
|
+
return {
|
|
643
|
+
testcase: normalized,
|
|
644
|
+
test_steps: tc.test_steps || tc.testSteps || tc.steps,
|
|
645
|
+
};
|
|
646
|
+
});
|
|
647
|
+
const cleanTestcases = withSteps.map((t) => t.testcase);
|
|
648
|
+
const bulkResult = await callApi("post", `projects/testcases/create-bulk-testcase/${projectId}`, { body: { testcases: cleanTestcases } });
|
|
649
|
+
// Add test steps for each created testcase
|
|
650
|
+
const results = bulkResult?.results || [];
|
|
651
|
+
let stepsCreated = 0;
|
|
652
|
+
for (let i = 0; i < results.length; i++) {
|
|
653
|
+
const r = results[i];
|
|
654
|
+
const steps = withSteps[i]?.test_steps;
|
|
655
|
+
if (r.status === "success" && steps?.length) {
|
|
656
|
+
const tcId = r.testcaseId || r.id;
|
|
657
|
+
if (tcId) {
|
|
658
|
+
try {
|
|
659
|
+
await callApi("post", `projects/testcases/teststeps/${projectId}/${tcId}`, {
|
|
660
|
+
body: { steps },
|
|
661
|
+
});
|
|
662
|
+
stepsCreated++;
|
|
663
|
+
}
|
|
664
|
+
catch {
|
|
665
|
+
// Non-fatal: steps failed but testcase was created
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
return jsonResponse({ ...bulkResult, steps_created_for: stepsCreated });
|
|
273
671
|
}
|
|
274
|
-
|
|
275
|
-
|
|
672
|
+
case "update": {
|
|
673
|
+
const ids = p(args, "ids", "testcase_ids", "testcaseIds");
|
|
674
|
+
const field = p(args, "field", "field_name", "fieldName");
|
|
675
|
+
const value = p(args, "value", "field_value", "fieldValue", "new_value");
|
|
676
|
+
if (!ids)
|
|
677
|
+
throw new Error("ids is required for update action");
|
|
678
|
+
if (!field)
|
|
679
|
+
throw new Error("field is required for update action");
|
|
680
|
+
if (value === undefined)
|
|
681
|
+
throw new Error("value is required for update action");
|
|
682
|
+
const idList = typeof ids === "string" ? ids.split(",").map((s) => s.trim()) : ids;
|
|
683
|
+
const data = await callApi("patch", `projects/testcases/update-field/${projectId}`, {
|
|
684
|
+
body: { ids: idList, field, value },
|
|
685
|
+
});
|
|
686
|
+
return jsonResponse(data);
|
|
276
687
|
}
|
|
277
|
-
|
|
278
|
-
|
|
688
|
+
case "delete": {
|
|
689
|
+
const ids = p(args, "ids", "testcase_ids", "testcaseIds");
|
|
690
|
+
if (!ids)
|
|
691
|
+
throw new Error("ids is required for delete action");
|
|
692
|
+
const idList = typeof ids === "string" ? ids.split(",").map((s) => s.trim()) : ids;
|
|
693
|
+
const data = await callApi("delete", `projects/testcases/delete-testcase/${projectId}`, {
|
|
694
|
+
body: { ids: idList },
|
|
695
|
+
});
|
|
696
|
+
return jsonResponse(data);
|
|
279
697
|
}
|
|
280
|
-
|
|
281
|
-
|
|
698
|
+
default:
|
|
699
|
+
throw new Error(`Unknown action: ${action}. Use: create, create_bulk, update, delete`);
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
// 6. manage_folder
|
|
703
|
+
async function handleManageFolder(args) {
|
|
704
|
+
const projectId = p(args, "projectId", "project_id");
|
|
705
|
+
const action = p(args, "action");
|
|
706
|
+
if (!projectId)
|
|
707
|
+
throw new Error("projectId is required");
|
|
708
|
+
if (!action)
|
|
709
|
+
throw new Error("action is required");
|
|
710
|
+
const section = p(args, "section") || "TESTCASE";
|
|
711
|
+
const orgId = await getOrganizationId();
|
|
712
|
+
switch (action) {
|
|
713
|
+
case "create": {
|
|
714
|
+
const name = p(args, "name", "folder_name");
|
|
715
|
+
if (!name)
|
|
716
|
+
throw new Error("name is required for create action");
|
|
717
|
+
const body = {
|
|
718
|
+
organizationId: orgId,
|
|
719
|
+
projectId,
|
|
720
|
+
section,
|
|
721
|
+
name,
|
|
722
|
+
};
|
|
723
|
+
const parentId = p(args, "parentId", "parent_id");
|
|
724
|
+
if (parentId)
|
|
725
|
+
body.parentId = parentId;
|
|
726
|
+
const data = await callApi("post", "folders/create-folders", { body, skipOrgId: true });
|
|
727
|
+
return jsonResponse(data);
|
|
282
728
|
}
|
|
283
|
-
|
|
284
|
-
|
|
729
|
+
case "rename": {
|
|
730
|
+
const folderId = p(args, "folderId", "folder_id", "id");
|
|
731
|
+
const name = p(args, "name", "folder_name");
|
|
732
|
+
if (!folderId)
|
|
733
|
+
throw new Error("folderId is required for rename action");
|
|
734
|
+
if (!name)
|
|
735
|
+
throw new Error("name is required for rename action");
|
|
736
|
+
const data = await callApi("patch", `folders/${folderId}`, {
|
|
737
|
+
query: { projectId, section },
|
|
738
|
+
body: { name },
|
|
739
|
+
});
|
|
740
|
+
return jsonResponse(data);
|
|
285
741
|
}
|
|
286
|
-
|
|
287
|
-
|
|
742
|
+
case "move": {
|
|
743
|
+
const folderId = p(args, "folderId", "folder_id", "id");
|
|
744
|
+
if (!folderId)
|
|
745
|
+
throw new Error("folderId is required for move action");
|
|
746
|
+
const parentId = p(args, "parentId", "parent_id");
|
|
747
|
+
const data = await callApi("patch", `folders/${folderId}/move`, {
|
|
748
|
+
query: { projectId, section },
|
|
749
|
+
body: { parentId: parentId ?? null },
|
|
750
|
+
});
|
|
751
|
+
return jsonResponse(data);
|
|
288
752
|
}
|
|
289
|
-
|
|
290
|
-
|
|
753
|
+
case "delete": {
|
|
754
|
+
const folderId = p(args, "folderId", "folder_id", "id");
|
|
755
|
+
if (!folderId)
|
|
756
|
+
throw new Error("folderId is required for delete action");
|
|
757
|
+
const data = await callApi("delete", `folders/delete/${folderId}`, {
|
|
758
|
+
query: { projectId, section },
|
|
759
|
+
});
|
|
760
|
+
return jsonResponse(data);
|
|
291
761
|
}
|
|
292
|
-
|
|
293
|
-
|
|
762
|
+
default:
|
|
763
|
+
throw new Error(`Unknown action: ${action}. Use: create, rename, move, delete`);
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
// 7. search_test_cycles
|
|
767
|
+
async function handleSearchTestCycles(args) {
|
|
768
|
+
const projectId = p(args, "projectId", "project_id");
|
|
769
|
+
if (!projectId)
|
|
770
|
+
throw new Error("projectId is required");
|
|
771
|
+
const data = await callApi("get", `projects/test-cycles/get-test-cycles/${projectId}`, {
|
|
772
|
+
query: {
|
|
773
|
+
search: p(args, "search"),
|
|
774
|
+
folderId: p(args, "folderId", "folder_id"),
|
|
775
|
+
status: p(args, "status"),
|
|
776
|
+
page: p(args, "page"),
|
|
777
|
+
limit: p(args, "limit"),
|
|
778
|
+
},
|
|
779
|
+
});
|
|
780
|
+
return jsonResponse(data);
|
|
781
|
+
}
|
|
782
|
+
// 8. manage_test_cycle
|
|
783
|
+
async function handleManageTestCycle(args) {
|
|
784
|
+
const projectId = p(args, "projectId", "project_id");
|
|
785
|
+
const action = p(args, "action");
|
|
786
|
+
if (!projectId)
|
|
787
|
+
throw new Error("projectId is required");
|
|
788
|
+
if (!action)
|
|
789
|
+
throw new Error("action is required");
|
|
790
|
+
switch (action) {
|
|
791
|
+
case "create": {
|
|
792
|
+
const title = p(args, "title");
|
|
793
|
+
if (!title)
|
|
794
|
+
throw new Error("title is required for create action");
|
|
795
|
+
const body = { title };
|
|
796
|
+
if (p(args, "summary"))
|
|
797
|
+
body.summary = p(args, "summary");
|
|
798
|
+
if (p(args, "status"))
|
|
799
|
+
body.status = p(args, "status");
|
|
800
|
+
if (p(args, "planned_start_date", "plannedStartDate"))
|
|
801
|
+
body.planned_start_date = p(args, "planned_start_date", "plannedStartDate");
|
|
802
|
+
if (p(args, "planned_end_date", "plannedEndDate"))
|
|
803
|
+
body.planned_end_date = p(args, "planned_end_date", "plannedEndDate");
|
|
804
|
+
if (p(args, "folder_id", "folderId"))
|
|
805
|
+
body.folder_id = p(args, "folder_id", "folderId");
|
|
806
|
+
const data = await callApi("post", `projects/test-cycles/create-test-cycle/${projectId}`, { body });
|
|
807
|
+
return jsonResponse(data);
|
|
294
808
|
}
|
|
295
|
-
|
|
296
|
-
|
|
809
|
+
case "update": {
|
|
810
|
+
const cycleId = p(args, "cycleId", "cycle_id", "id");
|
|
811
|
+
if (!cycleId)
|
|
812
|
+
throw new Error("cycleId is required for update action");
|
|
813
|
+
const body = { id: cycleId };
|
|
814
|
+
if (p(args, "title"))
|
|
815
|
+
body.title = p(args, "title");
|
|
816
|
+
if (p(args, "summary"))
|
|
817
|
+
body.summary = p(args, "summary");
|
|
818
|
+
if (p(args, "status"))
|
|
819
|
+
body.status = p(args, "status");
|
|
820
|
+
if (p(args, "planned_start_date", "plannedStartDate"))
|
|
821
|
+
body.planned_start_date = p(args, "planned_start_date", "plannedStartDate");
|
|
822
|
+
if (p(args, "planned_end_date", "plannedEndDate"))
|
|
823
|
+
body.planned_end_date = p(args, "planned_end_date", "plannedEndDate");
|
|
824
|
+
if (p(args, "folder_id", "folderId"))
|
|
825
|
+
body.folder_id = p(args, "folder_id", "folderId");
|
|
826
|
+
const data = await callApi("post", `projects/test-cycles/create-test-cycle/${projectId}`, { body });
|
|
827
|
+
return jsonResponse(data);
|
|
297
828
|
}
|
|
298
|
-
|
|
299
|
-
|
|
829
|
+
case "delete": {
|
|
830
|
+
const ids = p(args, "ids", "cycleId", "cycle_id");
|
|
831
|
+
if (!ids)
|
|
832
|
+
throw new Error("ids (or cycleId) is required for delete action");
|
|
833
|
+
const idList = typeof ids === "string" ? ids.split(",").map((s) => s.trim()) : ids;
|
|
834
|
+
const data = await callApi("delete", `projects/test-cycles/delete-test-cycle/${projectId}`, { body: { ids: idList } });
|
|
835
|
+
return jsonResponse(data);
|
|
300
836
|
}
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
{
|
|
308
|
-
type: "text",
|
|
309
|
-
text: `Error: ${error.message}`,
|
|
310
|
-
},
|
|
311
|
-
],
|
|
312
|
-
isError: true,
|
|
313
|
-
};
|
|
837
|
+
case "get_details": {
|
|
838
|
+
const cycleId = p(args, "cycleId", "cycle_id", "id");
|
|
839
|
+
if (!cycleId)
|
|
840
|
+
throw new Error("cycleId is required for get_details action");
|
|
841
|
+
const data = await callApi("get", `projects/test-cycles/get-test-cycle-details/${projectId}/${cycleId}`);
|
|
842
|
+
return jsonResponse(data);
|
|
314
843
|
}
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
const queryParams = new URLSearchParams();
|
|
326
|
-
queryParams.append("organizationId", organizationId.toString());
|
|
327
|
-
if (args.page !== undefined)
|
|
328
|
-
queryParams.append("page", args.page.toString());
|
|
329
|
-
if (args.limit !== undefined)
|
|
330
|
-
queryParams.append("limit", args.limit.toString());
|
|
331
|
-
if (args.selectAll !== undefined)
|
|
332
|
-
queryParams.append("selectAll", args.selectAll.toString());
|
|
333
|
-
if (args.columns)
|
|
334
|
-
queryParams.append("columns", args.columns);
|
|
335
|
-
if (args.filters)
|
|
336
|
-
queryParams.append("filters", args.filters);
|
|
337
|
-
if (args.search)
|
|
338
|
-
queryParams.append("search", args.search);
|
|
339
|
-
if (args.searchType)
|
|
340
|
-
queryParams.append("searchType", args.searchType);
|
|
341
|
-
if (args.sortBy)
|
|
342
|
-
queryParams.append("sortBy", args.sortBy);
|
|
343
|
-
if (args.sortOrder)
|
|
344
|
-
queryParams.append("sortOrder", args.sortOrder);
|
|
345
|
-
if (args.create_start_date)
|
|
346
|
-
queryParams.append("create_start_date", args.create_start_date);
|
|
347
|
-
if (args.create_end_date)
|
|
348
|
-
queryParams.append("create_end_date", args.create_end_date);
|
|
349
|
-
if (args.updated_start_date)
|
|
350
|
-
queryParams.append("updated_start_date", args.updated_start_date);
|
|
351
|
-
if (args.updated_end_date)
|
|
352
|
-
queryParams.append("updated_end_date", args.updated_end_date);
|
|
353
|
-
// Construct API URL
|
|
354
|
-
const url = `${config.apiBaseUrl}/api/v1/projects/testcases/get-testcase/${projectId}?${queryParams.toString()}`;
|
|
355
|
-
// Prepare headers with PAT token
|
|
356
|
-
// The PAT token is sent as Bearer token and will be validated by TestKase's JwtAuthGuard
|
|
357
|
-
// which checks for 'xyz_' prefix and validates against the PAT database
|
|
358
|
-
const headers = {
|
|
359
|
-
"Content-Type": "application/json",
|
|
360
|
-
"Authorization": `Bearer ${config.patToken}`,
|
|
361
|
-
};
|
|
362
|
-
try {
|
|
363
|
-
// Make API request
|
|
364
|
-
const response = await axios.get(url, { headers });
|
|
365
|
-
// Format the response
|
|
366
|
-
const data = response.data;
|
|
367
|
-
let resultText = `# Testcases for Project ${args.projectId}\n\n`;
|
|
368
|
-
if (data.data && Array.isArray(data.data)) {
|
|
369
|
-
resultText += `**Total Results:** ${data.total || data.data.length}\n`;
|
|
370
|
-
if (data.page)
|
|
371
|
-
resultText += `**Page:** ${data.page}\n`;
|
|
372
|
-
if (data.limit)
|
|
373
|
-
resultText += `**Limit:** ${data.limit}\n`;
|
|
374
|
-
resultText += `\n## Testcases (${data.data.length} items)\n\n`;
|
|
375
|
-
data.data.forEach((testcase, index) => {
|
|
376
|
-
resultText += `### ${index + 1}. ${testcase.title || "Untitled"}\n`;
|
|
377
|
-
resultText += `- **ID:** ${testcase.id}\n`;
|
|
378
|
-
if (testcase.status)
|
|
379
|
-
resultText += `- **Status:** ${testcase.status}\n`;
|
|
380
|
-
if (testcase.priority)
|
|
381
|
-
resultText += `- **Priority:** ${testcase.priority}\n`;
|
|
382
|
-
if (testcase.automated !== undefined)
|
|
383
|
-
resultText += `- **Automated:** ${testcase.automated}\n`;
|
|
384
|
-
if (testcase.summary)
|
|
385
|
-
resultText += `- **Summary:** ${testcase.summary}\n`;
|
|
386
|
-
if (testcase.owner_id)
|
|
387
|
-
resultText += `- **Owner ID:** ${testcase.owner_id}\n`;
|
|
388
|
-
if (testcase.folder_id)
|
|
389
|
-
resultText += `- **Folder ID:** ${testcase.folder_id}\n`;
|
|
390
|
-
if (testcase.created_at)
|
|
391
|
-
resultText += `- **Created:** ${testcase.created_at}\n`;
|
|
392
|
-
if (testcase.updated_at)
|
|
393
|
-
resultText += `- **Updated:** ${testcase.updated_at}\n`;
|
|
394
|
-
if (testcase.labels && Array.isArray(testcase.labels) && testcase.labels.length > 0) {
|
|
395
|
-
resultText += `- **Labels:** ${testcase.labels.join(", ")}\n`;
|
|
396
|
-
}
|
|
397
|
-
if (testcase.features && Array.isArray(testcase.features) && testcase.features.length > 0) {
|
|
398
|
-
resultText += `- **Features:** ${testcase.features.join(", ")}\n`;
|
|
399
|
-
}
|
|
400
|
-
resultText += `\n`;
|
|
844
|
+
case "get_testcases": {
|
|
845
|
+
const cycleId = p(args, "cycleId", "cycle_id", "id");
|
|
846
|
+
if (!cycleId)
|
|
847
|
+
throw new Error("cycleId is required for get_testcases action");
|
|
848
|
+
const data = await callApi("get", `projects/test-cycles/testcases/${projectId}/${cycleId}`, {
|
|
849
|
+
query: {
|
|
850
|
+
search: p(args, "search"),
|
|
851
|
+
page: p(args, "page"),
|
|
852
|
+
limit: p(args, "limit"),
|
|
853
|
+
},
|
|
401
854
|
});
|
|
855
|
+
return jsonResponse(data);
|
|
402
856
|
}
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
857
|
+
case "link_testcases": {
|
|
858
|
+
const cycleId = p(args, "cycleId", "cycle_id", "id");
|
|
859
|
+
const tcIds = p(args, "testcase_ids", "testcaseIds");
|
|
860
|
+
if (!cycleId)
|
|
861
|
+
throw new Error("cycleId is required for link_testcases action");
|
|
862
|
+
if (!tcIds)
|
|
863
|
+
throw new Error("testcase_ids is required for link_testcases action");
|
|
864
|
+
const idList = typeof tcIds === "string" ? tcIds.split(",").map((s) => s.trim()) : tcIds;
|
|
865
|
+
const data = await callApi("post", `projects/test-cycles/testcases/link/${projectId}/${cycleId}`, { body: { testcase_ids: idList } });
|
|
866
|
+
return jsonResponse(data);
|
|
406
867
|
}
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
if (axios.isAxiosError(error)) {
|
|
418
|
-
const axiosError = error;
|
|
419
|
-
let errorMessage = `API request failed: ${axiosError.message}`;
|
|
420
|
-
if (axiosError.response) {
|
|
421
|
-
errorMessage += `\nStatus: ${axiosError.response.status}`;
|
|
422
|
-
errorMessage += `\nResponse: ${JSON.stringify(axiosError.response.data)}`;
|
|
423
|
-
}
|
|
424
|
-
throw new Error(errorMessage);
|
|
868
|
+
case "unlink_testcases": {
|
|
869
|
+
const cycleId = p(args, "cycleId", "cycle_id", "id");
|
|
870
|
+
const tcIds = p(args, "testcase_ids", "testcaseIds");
|
|
871
|
+
if (!cycleId)
|
|
872
|
+
throw new Error("cycleId is required for unlink_testcases action");
|
|
873
|
+
if (!tcIds)
|
|
874
|
+
throw new Error("testcase_ids is required for unlink_testcases action");
|
|
875
|
+
const idList = typeof tcIds === "string" ? tcIds.split(",").map((s) => s.trim()) : tcIds;
|
|
876
|
+
const data = await callApi("delete", `projects/test-cycles/testcases/unlink/${projectId}/${cycleId}`, { body: { testcase_ids: idList } });
|
|
877
|
+
return jsonResponse(data);
|
|
425
878
|
}
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
try {
|
|
443
|
-
const response = await axios.get(url, {
|
|
444
|
-
headers: {
|
|
445
|
-
"Content-Type": "application/json",
|
|
446
|
-
"Authorization": `Bearer ${config.patToken}`,
|
|
447
|
-
},
|
|
448
|
-
});
|
|
449
|
-
const data = response.data;
|
|
450
|
-
return formatResponse(data, `Testcase Detail: ${testcaseId}`);
|
|
451
|
-
}
|
|
452
|
-
catch (error) {
|
|
453
|
-
throw handleApiError(error);
|
|
454
|
-
}
|
|
455
|
-
}
|
|
456
|
-
// Handler for get_folders tool
|
|
457
|
-
async function handleGetFolders(args) {
|
|
458
|
-
validateAuth();
|
|
459
|
-
const projectId = getProjectId(args.projectId);
|
|
460
|
-
const organizationId = await getOrganizationId(args.organizationId);
|
|
461
|
-
const queryParams = new URLSearchParams();
|
|
462
|
-
queryParams.append("organizationId", organizationId.toString());
|
|
463
|
-
queryParams.append("projectId", projectId);
|
|
464
|
-
queryParams.append("section", "TESTCASE");
|
|
465
|
-
if (args.search) {
|
|
466
|
-
queryParams.append("search", args.search);
|
|
467
|
-
}
|
|
468
|
-
const url = `${config.apiBaseUrl}/api/v1/folders/get-folders?${queryParams.toString()}`;
|
|
469
|
-
try {
|
|
470
|
-
const response = await axios.get(url, {
|
|
471
|
-
headers: {
|
|
472
|
-
"Content-Type": "application/json",
|
|
473
|
-
"Authorization": `Bearer ${config.patToken}`,
|
|
474
|
-
},
|
|
475
|
-
});
|
|
476
|
-
const data = response.data;
|
|
477
|
-
return formatResponse(data, `Folders for Project: ${args.projectId}`);
|
|
478
|
-
}
|
|
479
|
-
catch (error) {
|
|
480
|
-
throw handleApiError(error);
|
|
481
|
-
}
|
|
482
|
-
}
|
|
483
|
-
// Handler for get_labels tool
|
|
484
|
-
async function handleGetLabels(args) {
|
|
485
|
-
validateAuth();
|
|
486
|
-
const projectId = getProjectId(args.projectId);
|
|
487
|
-
const organizationId = await getOrganizationId(args.organizationId);
|
|
488
|
-
const queryParams = new URLSearchParams();
|
|
489
|
-
queryParams.append("organizationId", organizationId.toString());
|
|
490
|
-
const url = `${config.apiBaseUrl}/api/v1/projects/labels/get-label/${projectId}?${queryParams.toString()}`;
|
|
491
|
-
try {
|
|
492
|
-
const response = await axios.get(url, {
|
|
493
|
-
headers: {
|
|
494
|
-
"Content-Type": "application/json",
|
|
495
|
-
"Authorization": `Bearer ${config.patToken}`,
|
|
496
|
-
},
|
|
497
|
-
});
|
|
498
|
-
const data = response.data;
|
|
499
|
-
return formatResponse(data, `Labels for Project: ${args.projectId}`);
|
|
500
|
-
}
|
|
501
|
-
catch (error) {
|
|
502
|
-
throw handleApiError(error);
|
|
879
|
+
case "assign_testcases": {
|
|
880
|
+
const cycleId = p(args, "cycleId", "cycle_id", "id");
|
|
881
|
+
const tcIds = p(args, "testcase_ids", "testcaseIds");
|
|
882
|
+
const assigneeId = p(args, "assignee_id", "assigneeId");
|
|
883
|
+
if (!cycleId)
|
|
884
|
+
throw new Error("cycleId is required for assign_testcases action");
|
|
885
|
+
if (!tcIds)
|
|
886
|
+
throw new Error("testcase_ids is required for assign_testcases action");
|
|
887
|
+
if (!assigneeId)
|
|
888
|
+
throw new Error("assignee_id is required for assign_testcases action");
|
|
889
|
+
const idList = typeof tcIds === "string" ? tcIds.split(",").map((s) => s.trim()) : tcIds;
|
|
890
|
+
const data = await callApi("patch", `projects/test-cycles/testcases/assign/${projectId}/${cycleId}`, { body: { testcase_ids: idList, assignee_id: assigneeId } });
|
|
891
|
+
return jsonResponse(data);
|
|
892
|
+
}
|
|
893
|
+
default:
|
|
894
|
+
throw new Error(`Unknown action: ${action}. Use: create, update, delete, get_details, get_testcases, link_testcases, unlink_testcases, assign_testcases`);
|
|
503
895
|
}
|
|
504
896
|
}
|
|
505
|
-
//
|
|
506
|
-
async function
|
|
507
|
-
|
|
508
|
-
const
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
},
|
|
520
|
-
});
|
|
521
|
-
const data = response.data;
|
|
522
|
-
return formatResponse(data, "My Projects");
|
|
897
|
+
// 9. execute_tests
|
|
898
|
+
async function handleExecuteTests(args) {
|
|
899
|
+
const projectId = p(args, "projectId", "project_id");
|
|
900
|
+
const cycleId = p(args, "cycleId", "cycle_id");
|
|
901
|
+
const resultsRaw = p(args, "results");
|
|
902
|
+
if (!projectId)
|
|
903
|
+
throw new Error("projectId is required");
|
|
904
|
+
if (!cycleId)
|
|
905
|
+
throw new Error("cycleId is required");
|
|
906
|
+
if (!resultsRaw)
|
|
907
|
+
throw new Error("results is required");
|
|
908
|
+
const results = typeof resultsRaw === "string" ? JSON.parse(resultsRaw) : resultsRaw;
|
|
909
|
+
if (!Array.isArray(results) || results.length === 0) {
|
|
910
|
+
throw new Error("results must be a non-empty array");
|
|
523
911
|
}
|
|
524
|
-
|
|
525
|
-
|
|
912
|
+
// Single result → use PATCH update-execution endpoint
|
|
913
|
+
if (results.length === 1) {
|
|
914
|
+
const r = results[0];
|
|
915
|
+
const testcaseId = r.testcase_id || r.testcaseId;
|
|
916
|
+
if (!testcaseId)
|
|
917
|
+
throw new Error("testcase_id is required in each result");
|
|
918
|
+
const body = {
|
|
919
|
+
execution_status: r.execution_status || r.executionStatus,
|
|
920
|
+
};
|
|
921
|
+
if (r.actual_result || r.actualResult)
|
|
922
|
+
body.actual_result = r.actual_result || r.actualResult;
|
|
923
|
+
if (r.environment)
|
|
924
|
+
body.environment = r.environment;
|
|
925
|
+
const data = await callApi("patch", `projects/test-cycles/testcases/update-execution/${projectId}/${cycleId}/${testcaseId}`, { body });
|
|
926
|
+
return jsonResponse(data);
|
|
526
927
|
}
|
|
928
|
+
// Multiple results → use POST execute-bulk endpoint
|
|
929
|
+
const bulkResults = results.map((r) => ({
|
|
930
|
+
testcase_id: r.testcase_id || r.testcaseId,
|
|
931
|
+
execution_status: r.execution_status || r.executionStatus,
|
|
932
|
+
...(r.actual_result || r.actualResult
|
|
933
|
+
? { actual_result: r.actual_result || r.actualResult }
|
|
934
|
+
: {}),
|
|
935
|
+
...(r.environment ? { environment: r.environment } : {}),
|
|
936
|
+
}));
|
|
937
|
+
const data = await callApi("post", `projects/test-cycles/execute-bulk/${projectId}/${cycleId}`, { body: { results: bulkResults } });
|
|
938
|
+
return jsonResponse(data);
|
|
527
939
|
}
|
|
528
|
-
//
|
|
529
|
-
async function
|
|
530
|
-
|
|
531
|
-
const
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
940
|
+
// 10. manage_test_plan
|
|
941
|
+
async function handleManageTestPlan(args) {
|
|
942
|
+
const projectId = p(args, "projectId", "project_id");
|
|
943
|
+
const action = p(args, "action");
|
|
944
|
+
if (!projectId)
|
|
945
|
+
throw new Error("projectId is required");
|
|
946
|
+
if (!action)
|
|
947
|
+
throw new Error("action is required");
|
|
948
|
+
switch (action) {
|
|
949
|
+
case "create": {
|
|
950
|
+
const title = p(args, "title");
|
|
951
|
+
if (!title)
|
|
952
|
+
throw new Error("title is required for create action");
|
|
953
|
+
const body = { title };
|
|
954
|
+
if (p(args, "summary"))
|
|
955
|
+
body.summary = p(args, "summary");
|
|
956
|
+
if (p(args, "status"))
|
|
957
|
+
body.status = p(args, "status");
|
|
958
|
+
if (p(args, "planned_start_date", "plannedStartDate"))
|
|
959
|
+
body.planned_start_date = p(args, "planned_start_date", "plannedStartDate");
|
|
960
|
+
if (p(args, "planned_end_date", "plannedEndDate"))
|
|
961
|
+
body.planned_end_date = p(args, "planned_end_date", "plannedEndDate");
|
|
962
|
+
if (p(args, "folder_id", "folderId"))
|
|
963
|
+
body.folder_id = p(args, "folder_id", "folderId");
|
|
964
|
+
const data = await callApi("post", `projects/test-plans/create-test-plan/${projectId}`, { body });
|
|
965
|
+
return jsonResponse(data);
|
|
966
|
+
}
|
|
967
|
+
case "update": {
|
|
968
|
+
const planId = p(args, "planId", "plan_id", "id");
|
|
969
|
+
if (!planId)
|
|
970
|
+
throw new Error("planId is required for update action");
|
|
971
|
+
const body = { id: planId };
|
|
972
|
+
if (p(args, "title"))
|
|
973
|
+
body.title = p(args, "title");
|
|
974
|
+
if (p(args, "summary"))
|
|
975
|
+
body.summary = p(args, "summary");
|
|
976
|
+
if (p(args, "status"))
|
|
977
|
+
body.status = p(args, "status");
|
|
978
|
+
if (p(args, "planned_start_date", "plannedStartDate"))
|
|
979
|
+
body.planned_start_date = p(args, "planned_start_date", "plannedStartDate");
|
|
980
|
+
if (p(args, "planned_end_date", "plannedEndDate"))
|
|
981
|
+
body.planned_end_date = p(args, "planned_end_date", "plannedEndDate");
|
|
982
|
+
if (p(args, "folder_id", "folderId"))
|
|
983
|
+
body.folder_id = p(args, "folder_id", "folderId");
|
|
984
|
+
const data = await callApi("post", `projects/test-plans/create-test-plan/${projectId}`, { body });
|
|
985
|
+
return jsonResponse(data);
|
|
986
|
+
}
|
|
987
|
+
case "delete": {
|
|
988
|
+
const ids = p(args, "ids", "planId", "plan_id");
|
|
989
|
+
if (!ids)
|
|
990
|
+
throw new Error("ids (or planId) is required for delete action");
|
|
991
|
+
const idList = typeof ids === "string" ? ids.split(",").map((s) => s.trim()) : ids;
|
|
992
|
+
const data = await callApi("delete", `projects/test-plans/delete-test-plan/${projectId}`, { body: { ids: idList } });
|
|
993
|
+
return jsonResponse(data);
|
|
994
|
+
}
|
|
995
|
+
case "list": {
|
|
996
|
+
const data = await callApi("get", `projects/test-plans/get-test-plans/${projectId}`, {
|
|
997
|
+
query: {
|
|
998
|
+
search: p(args, "search"),
|
|
999
|
+
page: p(args, "page"),
|
|
1000
|
+
limit: p(args, "limit"),
|
|
1001
|
+
},
|
|
576
1002
|
});
|
|
1003
|
+
return jsonResponse(data);
|
|
577
1004
|
}
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
// Handler for create_bulk_testcase tool (handles test_steps internally)
|
|
585
|
-
async function handleCreateBulkTestcase(args) {
|
|
586
|
-
validateAuth();
|
|
587
|
-
const projectId = getProjectId(args.projectId);
|
|
588
|
-
const organizationId = await getOrganizationId(args.organizationId);
|
|
589
|
-
// Handle different param names AI might use
|
|
590
|
-
const rawTestcases = args.testcases || args.test_cases || args.items;
|
|
591
|
-
if (!rawTestcases)
|
|
592
|
-
throw new Error("testcases is required (JSON array of testcases)");
|
|
593
|
-
const testcases = typeof rawTestcases === 'string' ? JSON.parse(rawTestcases) : rawTestcases;
|
|
594
|
-
// Normalize each testcase and separate test_steps for later processing
|
|
595
|
-
const testcasesWithSteps = testcases.map((tc) => {
|
|
596
|
-
// Normalize field names for each testcase
|
|
597
|
-
const normalized = {
|
|
598
|
-
title: tc.title || tc.name || tc.testcase_title,
|
|
599
|
-
};
|
|
600
|
-
if (tc.summary || tc.description)
|
|
601
|
-
normalized.summary = tc.summary || tc.description;
|
|
602
|
-
if (tc.priority)
|
|
603
|
-
normalized.priority = tc.priority;
|
|
604
|
-
if (tc.folder_id || tc.folderId)
|
|
605
|
-
normalized.folder_id = tc.folder_id || tc.folderId;
|
|
606
|
-
return {
|
|
607
|
-
testcase: normalized,
|
|
608
|
-
test_steps: tc.test_steps || tc.testSteps || tc.steps,
|
|
609
|
-
};
|
|
610
|
-
});
|
|
611
|
-
const queryParams = new URLSearchParams();
|
|
612
|
-
queryParams.append("organizationId", organizationId.toString());
|
|
613
|
-
const url = `${config.apiBaseUrl}/api/v1/projects/testcases/create-bulk-testcase/${projectId}?${queryParams.toString()}`;
|
|
614
|
-
try {
|
|
615
|
-
// Step 1: Create all testcases (without steps)
|
|
616
|
-
const cleanTestcases = testcasesWithSteps.map((t) => t.testcase);
|
|
617
|
-
const response = await axios.post(url, { testcases: cleanTestcases }, {
|
|
618
|
-
headers: { "Content-Type": "application/json", "Authorization": `Bearer ${config.patToken}` },
|
|
619
|
-
});
|
|
620
|
-
const results = response.data?.results || [];
|
|
621
|
-
// Step 2: Create test steps for each successfully created testcase
|
|
622
|
-
let stepsCreated = 0;
|
|
623
|
-
for (let i = 0; i < results.length; i++) {
|
|
624
|
-
const result = results[i];
|
|
625
|
-
const steps = testcasesWithSteps[i]?.test_steps;
|
|
626
|
-
if (result.status === 'success' && steps && Array.isArray(steps) && steps.length > 0) {
|
|
627
|
-
const testcaseId = result.testcaseId || result.id;
|
|
628
|
-
if (testcaseId) {
|
|
629
|
-
try {
|
|
630
|
-
const stepsUrl = `${config.apiBaseUrl}/api/v1/projects/testcases/teststeps/${projectId}/${testcaseId}?organizationId=${organizationId}`;
|
|
631
|
-
await axios.post(stepsUrl, { steps }, {
|
|
632
|
-
headers: { "Content-Type": "application/json", "Authorization": `Bearer ${config.patToken}` },
|
|
633
|
-
});
|
|
634
|
-
stepsCreated++;
|
|
635
|
-
}
|
|
636
|
-
catch (e) {
|
|
637
|
-
// Log but don't fail the whole operation
|
|
638
|
-
console.error(`Failed to create steps for testcase ${testcaseId}`);
|
|
639
|
-
}
|
|
640
|
-
}
|
|
641
|
-
}
|
|
1005
|
+
case "get_details": {
|
|
1006
|
+
const planId = p(args, "planId", "plan_id", "id");
|
|
1007
|
+
if (!planId)
|
|
1008
|
+
throw new Error("planId is required for get_details action");
|
|
1009
|
+
const data = await callApi("get", `projects/test-plans/get-test-plan-details/${projectId}/${planId}`);
|
|
1010
|
+
return jsonResponse(data);
|
|
642
1011
|
}
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
try {
|
|
694
|
-
const response = await axios.delete(url, {
|
|
695
|
-
headers: { "Content-Type": "application/json", "Authorization": `Bearer ${config.patToken}` },
|
|
696
|
-
data: { ids: ids.split(',') },
|
|
697
|
-
});
|
|
698
|
-
return formatResponse(response.data, "Testcase Deleted");
|
|
699
|
-
}
|
|
700
|
-
catch (error) {
|
|
701
|
-
throw handleApiError(error);
|
|
702
|
-
}
|
|
703
|
-
}
|
|
704
|
-
// Handler for create_folder tool
|
|
705
|
-
async function handleCreateFolder(args) {
|
|
706
|
-
validateAuth();
|
|
707
|
-
const projectId = getProjectId(args.projectId);
|
|
708
|
-
const organizationId = await getOrganizationId(args.organizationId);
|
|
709
|
-
const url = `${config.apiBaseUrl}/api/v1/folders/create-folders`;
|
|
710
|
-
// Handle different param names AI might use
|
|
711
|
-
const folderName = args.name || args.folder_name;
|
|
712
|
-
if (!folderName) {
|
|
713
|
-
throw new Error("Folder name is required. Provide 'name' parameter.");
|
|
714
|
-
}
|
|
715
|
-
const parentId = args.parentId || args.parent_id;
|
|
716
|
-
const body = {
|
|
717
|
-
organizationId: organizationId,
|
|
718
|
-
projectId: projectId,
|
|
719
|
-
section: "TESTCASE",
|
|
720
|
-
name: folderName,
|
|
721
|
-
...(parentId && { parentId: parentId }),
|
|
722
|
-
};
|
|
723
|
-
try {
|
|
724
|
-
const response = await axios.post(url, body, {
|
|
725
|
-
headers: { "Content-Type": "application/json", "Authorization": `Bearer ${config.patToken}` },
|
|
726
|
-
});
|
|
727
|
-
return formatResponse(response.data, "Folder Created");
|
|
728
|
-
}
|
|
729
|
-
catch (error) {
|
|
730
|
-
throw handleApiError(error);
|
|
1012
|
+
case "link_cycles": {
|
|
1013
|
+
const planId = p(args, "planId", "plan_id", "id");
|
|
1014
|
+
const cycleIds = p(args, "cycle_ids", "cycleIds");
|
|
1015
|
+
if (!planId)
|
|
1016
|
+
throw new Error("planId is required for link_cycles action");
|
|
1017
|
+
if (!cycleIds)
|
|
1018
|
+
throw new Error("cycle_ids is required for link_cycles action");
|
|
1019
|
+
const idList = typeof cycleIds === "string" ? cycleIds.split(",").map((s) => s.trim()) : cycleIds;
|
|
1020
|
+
const data = await callApi("post", `projects/test-plans/testcycles/link/${projectId}/${planId}`, { body: { test_cycle_ids: idList } });
|
|
1021
|
+
return jsonResponse(data);
|
|
1022
|
+
}
|
|
1023
|
+
case "unlink_cycles": {
|
|
1024
|
+
const planId = p(args, "planId", "plan_id", "id");
|
|
1025
|
+
const cycleIds = p(args, "cycle_ids", "cycleIds");
|
|
1026
|
+
if (!planId)
|
|
1027
|
+
throw new Error("planId is required for unlink_cycles action");
|
|
1028
|
+
if (!cycleIds)
|
|
1029
|
+
throw new Error("cycle_ids is required for unlink_cycles action");
|
|
1030
|
+
const idList = typeof cycleIds === "string" ? cycleIds.split(",").map((s) => s.trim()) : cycleIds;
|
|
1031
|
+
const data = await callApi("delete", `projects/test-plans/testcycles/unlink/${projectId}/${planId}`, { body: { test_cycle_ids: idList } });
|
|
1032
|
+
return jsonResponse(data);
|
|
1033
|
+
}
|
|
1034
|
+
case "get_cycles": {
|
|
1035
|
+
const planId = p(args, "planId", "plan_id", "id");
|
|
1036
|
+
if (!planId)
|
|
1037
|
+
throw new Error("planId is required for get_cycles action");
|
|
1038
|
+
const data = await callApi("get", `projects/test-plans/testcycles/list/${projectId}/${planId}`, {
|
|
1039
|
+
query: {
|
|
1040
|
+
search: p(args, "search"),
|
|
1041
|
+
page: p(args, "page"),
|
|
1042
|
+
limit: p(args, "limit"),
|
|
1043
|
+
},
|
|
1044
|
+
});
|
|
1045
|
+
return jsonResponse(data);
|
|
1046
|
+
}
|
|
1047
|
+
case "get_testcases": {
|
|
1048
|
+
const planId = p(args, "planId", "plan_id", "id");
|
|
1049
|
+
if (!planId)
|
|
1050
|
+
throw new Error("planId is required for get_testcases action");
|
|
1051
|
+
const data = await callApi("get", `projects/test-plans/testcases/list/${projectId}/${planId}`, {
|
|
1052
|
+
query: {
|
|
1053
|
+
search: p(args, "search"),
|
|
1054
|
+
page: p(args, "page"),
|
|
1055
|
+
limit: p(args, "limit"),
|
|
1056
|
+
},
|
|
1057
|
+
});
|
|
1058
|
+
return jsonResponse(data);
|
|
1059
|
+
}
|
|
1060
|
+
default:
|
|
1061
|
+
throw new Error(`Unknown action: ${action}. Use: create, update, delete, list, get_details, link_cycles, unlink_cycles, get_cycles, get_testcases`);
|
|
731
1062
|
}
|
|
732
1063
|
}
|
|
733
|
-
//
|
|
734
|
-
async function
|
|
735
|
-
|
|
736
|
-
const
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
throw new Error("values is required (comma-separated label names)");
|
|
745
|
-
// Parse values from comma-separated string
|
|
746
|
-
const values = typeof rawValues === 'string' ? rawValues.split(',').map((v) => v.trim()) : rawValues;
|
|
747
|
-
try {
|
|
748
|
-
const response = await axios.post(url, { values }, {
|
|
749
|
-
headers: { "Content-Type": "application/json", "Authorization": `Bearer ${config.patToken}` },
|
|
750
|
-
});
|
|
751
|
-
return formatResponse(response.data, "Label Created");
|
|
752
|
-
}
|
|
753
|
-
catch (error) {
|
|
754
|
-
throw handleApiError(error);
|
|
1064
|
+
// 11. get_report
|
|
1065
|
+
async function handleGetReport(args) {
|
|
1066
|
+
const projectId = p(args, "projectId", "project_id");
|
|
1067
|
+
const reportType = p(args, "report_type", "reportType");
|
|
1068
|
+
if (!projectId)
|
|
1069
|
+
throw new Error("projectId is required");
|
|
1070
|
+
if (!reportType)
|
|
1071
|
+
throw new Error("report_type is required");
|
|
1072
|
+
const endpointPath = REPORT_TYPE_MAP[reportType];
|
|
1073
|
+
if (!endpointPath) {
|
|
1074
|
+
throw new Error(`Unknown report_type: ${reportType}. Valid types: ${Object.keys(REPORT_TYPE_MAP).join(", ")}`);
|
|
755
1075
|
}
|
|
1076
|
+
const query = {};
|
|
1077
|
+
// Common filters
|
|
1078
|
+
if (p(args, "test_cycle_ids", "testCycleIds"))
|
|
1079
|
+
query.test_cycle_ids = p(args, "test_cycle_ids", "testCycleIds");
|
|
1080
|
+
if (p(args, "test_plan_ids", "testPlanIds"))
|
|
1081
|
+
query.test_plan_ids = p(args, "test_plan_ids", "testPlanIds");
|
|
1082
|
+
if (p(args, "startDate", "start_date"))
|
|
1083
|
+
query.startDate = p(args, "startDate", "start_date");
|
|
1084
|
+
if (p(args, "endDate", "end_date"))
|
|
1085
|
+
query.endDate = p(args, "endDate", "end_date");
|
|
1086
|
+
if (p(args, "granularity"))
|
|
1087
|
+
query.granularity = p(args, "granularity");
|
|
1088
|
+
// cycle_comparison specific
|
|
1089
|
+
if (p(args, "cycle_ids", "cycleIds"))
|
|
1090
|
+
query.cycle_ids = p(args, "cycle_ids", "cycleIds");
|
|
1091
|
+
const data = await callApi("get", `projects/reports/${endpointPath}/${projectId}`, {
|
|
1092
|
+
query,
|
|
1093
|
+
});
|
|
1094
|
+
return jsonResponse(data);
|
|
756
1095
|
}
|
|
757
|
-
//
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
1096
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
1097
|
+
// SERVER SETUP
|
|
1098
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
1099
|
+
const server = new Server({ name: "testkase-mcp-server", version: "2.1.0" }, { capabilities: { tools: {} } });
|
|
1100
|
+
// List all 11 tools
|
|
1101
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
1102
|
+
tools: [
|
|
1103
|
+
LIST_PROJECTS_TOOL,
|
|
1104
|
+
GET_PROJECT_STRUCTURE_TOOL,
|
|
1105
|
+
SEARCH_TESTCASES_TOOL,
|
|
1106
|
+
GET_TESTCASE_TOOL,
|
|
1107
|
+
MANAGE_TESTCASE_TOOL,
|
|
1108
|
+
MANAGE_FOLDER_TOOL,
|
|
1109
|
+
SEARCH_TEST_CYCLES_TOOL,
|
|
1110
|
+
MANAGE_TEST_CYCLE_TOOL,
|
|
1111
|
+
EXECUTE_TESTS_TOOL,
|
|
1112
|
+
MANAGE_TEST_PLAN_TOOL,
|
|
1113
|
+
GET_REPORT_TOOL,
|
|
1114
|
+
],
|
|
1115
|
+
}));
|
|
1116
|
+
// Route tool calls
|
|
1117
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
1118
|
+
const { name, arguments: args } = request.params;
|
|
761
1119
|
try {
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
1120
|
+
switch (name) {
|
|
1121
|
+
case "list_projects":
|
|
1122
|
+
return await handleListProjects(args);
|
|
1123
|
+
case "get_project_structure":
|
|
1124
|
+
return await handleGetProjectStructure(args);
|
|
1125
|
+
case "search_testcases":
|
|
1126
|
+
return await handleSearchTestcases(args);
|
|
1127
|
+
case "get_testcase":
|
|
1128
|
+
return await handleGetTestcase(args);
|
|
1129
|
+
case "manage_testcase":
|
|
1130
|
+
return await handleManageTestcase(args);
|
|
1131
|
+
case "manage_folder":
|
|
1132
|
+
return await handleManageFolder(args);
|
|
1133
|
+
case "search_test_cycles":
|
|
1134
|
+
return await handleSearchTestCycles(args);
|
|
1135
|
+
case "manage_test_cycle":
|
|
1136
|
+
return await handleManageTestCycle(args);
|
|
1137
|
+
case "execute_tests":
|
|
1138
|
+
return await handleExecuteTests(args);
|
|
1139
|
+
case "manage_test_plan":
|
|
1140
|
+
return await handleManageTestPlan(args);
|
|
1141
|
+
case "get_report":
|
|
1142
|
+
return await handleGetReport(args);
|
|
1143
|
+
default:
|
|
1144
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
1145
|
+
}
|
|
766
1146
|
}
|
|
767
1147
|
catch (error) {
|
|
768
|
-
|
|
769
|
-
}
|
|
770
|
-
}
|
|
771
|
-
// Handler for set_active_project tool
|
|
772
|
-
async function handleSetActiveProject(args) {
|
|
773
|
-
// Handle different param names AI might use
|
|
774
|
-
const projectId = args.projectId || args.project_id;
|
|
775
|
-
if (!projectId) {
|
|
776
|
-
throw new Error("projectId is required");
|
|
777
|
-
}
|
|
778
|
-
setActiveProject(projectId);
|
|
779
|
-
return {
|
|
780
|
-
content: [
|
|
781
|
-
{
|
|
782
|
-
type: "text",
|
|
783
|
-
text: `Active project set to: ${projectId}\n\nYou can now use other tools without specifying projectId.`,
|
|
784
|
-
},
|
|
785
|
-
],
|
|
786
|
-
};
|
|
787
|
-
}
|
|
788
|
-
// Handler for get_active_project tool
|
|
789
|
-
async function handleGetActiveProject() {
|
|
790
|
-
const projectId = cachedProjectId;
|
|
791
|
-
if (!projectId) {
|
|
1148
|
+
const err = error instanceof Error ? error : handleApiError(error);
|
|
792
1149
|
return {
|
|
793
|
-
content: [
|
|
794
|
-
|
|
795
|
-
type: "text",
|
|
796
|
-
text: "No active project is set. Use set_active_project or specify projectId in your next command.",
|
|
797
|
-
},
|
|
798
|
-
],
|
|
1150
|
+
content: [{ type: "text", text: `Error: ${err.message}` }],
|
|
1151
|
+
isError: true,
|
|
799
1152
|
};
|
|
800
1153
|
}
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
{
|
|
804
|
-
type: "text",
|
|
805
|
-
text: `Current active project: ${projectId}`,
|
|
806
|
-
},
|
|
807
|
-
],
|
|
808
|
-
};
|
|
809
|
-
}
|
|
810
|
-
// Helper function to validate authentication
|
|
811
|
-
function validateAuth() {
|
|
812
|
-
if (!config.patToken) {
|
|
813
|
-
throw new Error("Authentication required. Please set TESTKASE_PAT_TOKEN environment variable. " +
|
|
814
|
-
"Generate a Personal Access Token (PAT) from your TestKase account settings.");
|
|
815
|
-
}
|
|
816
|
-
if (!config.patToken.startsWith("xyz_")) {
|
|
817
|
-
throw new Error("Invalid PAT token format. TestKase PAT tokens must start with 'xyz_' prefix. " +
|
|
818
|
-
"Please generate a valid PAT from your TestKase account settings.");
|
|
819
|
-
}
|
|
820
|
-
}
|
|
821
|
-
// Helper function to format API response
|
|
822
|
-
function formatResponse(data, title) {
|
|
823
|
-
let resultText = `# ${title}\n\n`;
|
|
824
|
-
resultText += "```json\n";
|
|
825
|
-
resultText += JSON.stringify(data, null, 2);
|
|
826
|
-
resultText += "\n```\n";
|
|
827
|
-
return {
|
|
828
|
-
content: [
|
|
829
|
-
{
|
|
830
|
-
type: "text",
|
|
831
|
-
text: resultText,
|
|
832
|
-
},
|
|
833
|
-
],
|
|
834
|
-
};
|
|
835
|
-
}
|
|
836
|
-
// Helper function to handle API errors
|
|
837
|
-
function handleApiError(error) {
|
|
838
|
-
if (axios.isAxiosError(error)) {
|
|
839
|
-
const axiosError = error;
|
|
840
|
-
let errorMessage = `API request failed: ${axiosError.message}`;
|
|
841
|
-
if (axiosError.response) {
|
|
842
|
-
errorMessage += `\nStatus: ${axiosError.response.status}`;
|
|
843
|
-
errorMessage += `\nResponse: ${JSON.stringify(axiosError.response.data)}`;
|
|
844
|
-
}
|
|
845
|
-
return new Error(errorMessage);
|
|
846
|
-
}
|
|
847
|
-
return error;
|
|
848
|
-
}
|
|
849
|
-
// Start the server
|
|
1154
|
+
});
|
|
1155
|
+
// Start
|
|
850
1156
|
async function main() {
|
|
851
1157
|
const transport = new StdioServerTransport();
|
|
852
1158
|
await server.connect(transport);
|
|
853
|
-
console.error("TestKase MCP Server running on stdio");
|
|
1159
|
+
console.error("TestKase MCP Server v2.1.0 running on stdio (11 tools)");
|
|
854
1160
|
}
|
|
855
1161
|
main().catch((error) => {
|
|
856
1162
|
console.error("Fatal error in main():", error);
|