@testkase/mcp-server 2.0.4 → 2.0.9

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/build/index.js CHANGED
@@ -8,6 +8,61 @@ const config = {
8
8
  apiBaseUrl: process.env.TESTKASE_API_BASE_URL || "https://apiqa.testkase.com",
9
9
  patToken: process.env.TESTKASE_PAT_TOKEN,
10
10
  };
11
+ // Cached organization data (fetched once from PAT token)
12
+ let cachedOrganizationId = null;
13
+ // Cached project ID (set when user works on a project)
14
+ let cachedProjectId = null;
15
+ // Helper to get organization ID (fetches and caches on first call)
16
+ async function getOrganizationId(providedOrgId) {
17
+ // If explicitly provided, use it
18
+ if (providedOrgId) {
19
+ return providedOrgId;
20
+ }
21
+ // If cached, return cached value
22
+ if (cachedOrganizationId !== null) {
23
+ return cachedOrganizationId;
24
+ }
25
+ // Fetch from API using PAT token
26
+ validateAuth();
27
+ 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 orgData = response.data?.data || response.data;
35
+ if (orgData?.organization_id) {
36
+ const orgId = orgData.organization_id;
37
+ cachedOrganizationId = orgId;
38
+ return orgId;
39
+ }
40
+ throw new Error("Could not determine organization from PAT token");
41
+ }
42
+ catch (error) {
43
+ throw new Error("Failed to fetch organization. Please provide organizationId or check your PAT token.");
44
+ }
45
+ }
46
+ // Helper to get project ID (uses cached value if available)
47
+ function getProjectId(providedProjectId) {
48
+ // If explicitly provided, cache it and use it
49
+ if (providedProjectId) {
50
+ cachedProjectId = providedProjectId;
51
+ return providedProjectId;
52
+ }
53
+ // If cached, return cached value
54
+ if (cachedProjectId !== null) {
55
+ return cachedProjectId;
56
+ }
57
+ throw new Error("No active project set. You MUST first: 1) Call get_my_projects to list projects, " +
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.");
61
+ }
62
+ // Helper to set active project (for explicit project switching)
63
+ function setActiveProject(projectId) {
64
+ cachedProjectId = projectId;
65
+ }
11
66
  // Tool definitions
12
67
  const GET_TESTCASES_TOOL = {
13
68
  name: "get_testcases",
@@ -20,11 +75,11 @@ const GET_TESTCASES_TOOL = {
20
75
  properties: {
21
76
  projectId: {
22
77
  type: "string",
23
- description: "The unique identifier of the project (UUID format)",
78
+ description: "Project ID (optional if active project is set)",
24
79
  },
25
80
  organizationId: {
26
81
  type: "number",
27
- description: "The organization ID that owns the project",
82
+ description: "Organization ID (optional - auto-detected from PAT token)",
28
83
  },
29
84
  page: {
30
85
  type: "number",
@@ -89,7 +144,7 @@ const GET_TESTCASES_TOOL = {
89
144
  description: "Filter by update end date (ISO 8601 format: YYYY-MM-DD)",
90
145
  },
91
146
  },
92
- required: ["projectId", "organizationId"],
147
+ required: [],
93
148
  },
94
149
  };
95
150
  const GET_TESTCASE_DETAIL_TOOL = {
@@ -101,31 +156,7 @@ const GET_TESTCASE_DETAIL_TOOL = {
101
156
  properties: {
102
157
  projectId: {
103
158
  type: "string",
104
- description: "The unique identifier of the project",
105
- },
106
- testcaseId: {
107
- type: "string",
108
- description: "The unique identifier of the testcase",
109
- },
110
- organizationId: {
111
- type: "number",
112
- description: "The organization ID that owns the project",
113
- },
114
- },
115
- required: ["projectId", "testcaseId", "organizationId"],
116
- },
117
- };
118
- const GET_TESTCASE_HISTORY_TOOL = {
119
- name: "get_testcase_history",
120
- description: "Get the complete history of changes made to a testcase. " +
121
- "Returns a list of all modifications with timestamps, user information, and changed fields. " +
122
- "Useful for tracking testcase evolution and audit trails.",
123
- inputSchema: {
124
- type: "object",
125
- properties: {
126
- projectId: {
127
- type: "string",
128
- description: "The unique identifier of the project",
159
+ description: "Project ID (optional if active project is set)",
129
160
  },
130
161
  testcaseId: {
131
162
  type: "string",
@@ -133,10 +164,10 @@ const GET_TESTCASE_HISTORY_TOOL = {
133
164
  },
134
165
  organizationId: {
135
166
  type: "number",
136
- description: "The organization ID that owns the project",
167
+ description: "Organization ID (optional - auto-detected from PAT token)",
137
168
  },
138
169
  },
139
- required: ["projectId", "testcaseId", "organizationId"],
170
+ required: ["testcaseId"],
140
171
  },
141
172
  };
142
173
  const GET_FOLDERS_TOOL = {
@@ -149,23 +180,24 @@ const GET_FOLDERS_TOOL = {
149
180
  properties: {
150
181
  organizationId: {
151
182
  type: "number",
152
- description: "The organization ID",
183
+ description: "Organization ID (optional - auto-detected from PAT token)",
153
184
  },
154
185
  projectId: {
155
186
  type: "string",
156
- description: "The unique identifier of the project",
187
+ description: "Project ID (optional if active project is set)",
157
188
  },
158
189
  section: {
159
190
  type: "string",
160
191
  enum: ["TESTCASE", "TEST_CYCLE", "TEST_PLAN", "REQUIREMENT"],
161
- description: "Folder section type (TESTCASE, TEST_CYCLE, TEST_PLAN, or REQUIREMENT)",
192
+ description: "Folder section type (default: TESTCASE)",
193
+ default: "TESTCASE",
162
194
  },
163
195
  search: {
164
196
  type: "string",
165
197
  description: "Optional: Search term to filter folders (min 3 characters)",
166
198
  },
167
199
  },
168
- required: ["organizationId", "projectId", "section"],
200
+ required: [],
169
201
  },
170
202
  };
171
203
  const GET_LABELS_TOOL = {
@@ -178,49 +210,23 @@ const GET_LABELS_TOOL = {
178
210
  properties: {
179
211
  projectId: {
180
212
  type: "string",
181
- description: "The unique identifier of the project",
213
+ description: "Project ID (optional if active project is set)",
182
214
  },
183
215
  organizationId: {
184
216
  type: "number",
185
- description: "The organization ID",
217
+ description: "Organization ID (optional - auto-detected from PAT token)",
186
218
  },
187
219
  },
188
- required: ["projectId", "organizationId"],
189
- },
190
- };
191
- const GET_ISSUES_TOOL = {
192
- name: "get_issues",
193
- description: "Get all issues/defects reported in a project. " +
194
- "Returns a list of issues with their status, priority, and linked testcases. " +
195
- "Useful for tracking bugs and defects found during testing.",
196
- inputSchema: {
197
- type: "object",
198
- properties: {
199
- projectId: {
200
- type: "string",
201
- description: "The unique identifier of the project",
202
- },
203
- organizationId: {
204
- type: "number",
205
- description: "The organization ID",
206
- },
207
- page: {
208
- type: "number",
209
- description: "Page number for pagination (default: 1)",
210
- },
211
- limit: {
212
- type: "number",
213
- description: "Number of items per page (default: 20)",
214
- },
215
- },
216
- required: ["projectId", "organizationId"],
220
+ required: [],
217
221
  },
218
222
  };
219
223
  const GET_MY_PROJECTS_TOOL = {
220
224
  name: "get_my_projects",
221
- description: "Get all projects that the current user (PAT token owner) has access to across all organizations. " +
222
- "Returns project details including name, ID, organization, and team members. " +
223
- "Useful for discovering available projects and their basic information.",
225
+ description: "IMPORTANT: Call this tool FIRST before any other TestKase operation. " +
226
+ "Lists all projects the user has access to. " +
227
+ "After getting results, ALWAYS ASK the user which project they want to work with. " +
228
+ "Do NOT automatically select a project - let the user choose. " +
229
+ "Then use set_active_project with the user's chosen project ID.",
224
230
  inputSchema: {
225
231
  type: "object",
226
232
  properties: {
@@ -242,28 +248,18 @@ const CREATE_TESTCASE_TOOL = {
242
248
  inputSchema: {
243
249
  type: "object",
244
250
  properties: {
245
- projectId: { type: "string", description: "Project ID" },
246
- organizationId: { type: "number", description: "Organization ID" },
251
+ projectId: { type: "string", description: "Project ID (optional if active project is set)" },
252
+ organizationId: { type: "number", description: "Organization ID (optional - auto-detected from PAT token)" },
247
253
  title: { type: "string", description: "Test case title" },
248
254
  summary: { type: "string", description: "Test case summary/description" },
249
255
  priority: { type: "string", description: "Priority (low, medium, high, critical)" },
250
256
  status: { type: "string", description: "Status" },
251
257
  folder_id: { type: "string", description: "Folder ID" },
252
- labels: { type: "array", items: { type: "string" }, description: "Label IDs" },
258
+ labels: { type: "string", description: "Comma-separated label IDs" },
253
259
  automated: { type: "boolean", description: "Is automated test" },
254
- test_steps: {
255
- type: "array",
256
- items: {
257
- type: "object",
258
- properties: {
259
- step: { type: "string", description: "Step description" },
260
- expected_result: { type: "string", description: "Expected result" }
261
- }
262
- },
263
- description: "Array of test steps"
264
- },
260
+ test_steps: { type: "string", description: "JSON string of test steps array. Each step: {step: string, expected_result: string}" },
265
261
  },
266
- required: ["projectId", "organizationId", "title"],
262
+ required: ["title"],
267
263
  },
268
264
  };
269
265
  const CREATE_BULK_TESTCASE_TOOL = {
@@ -272,11 +268,11 @@ const CREATE_BULK_TESTCASE_TOOL = {
272
268
  inputSchema: {
273
269
  type: "object",
274
270
  properties: {
275
- projectId: { type: "string", description: "Project ID" },
276
- organizationId: { type: "number", description: "Organization ID" },
271
+ projectId: { type: "string", description: "Project ID (optional if active project is set)" },
272
+ organizationId: { type: "number", description: "Organization ID (optional - auto-detected from PAT token)" },
277
273
  testcases: { type: "string", description: "JSON string of testcase array" },
278
274
  },
279
- required: ["projectId", "organizationId", "testcases"],
275
+ required: ["testcases"],
280
276
  },
281
277
  };
282
278
  const UPDATE_TESTCASE_FIELD_TOOL = {
@@ -285,13 +281,13 @@ const UPDATE_TESTCASE_FIELD_TOOL = {
285
281
  inputSchema: {
286
282
  type: "object",
287
283
  properties: {
288
- projectId: { type: "string", description: "Project ID" },
289
- organizationId: { type: "number", description: "Organization ID" },
284
+ projectId: { type: "string", description: "Project ID (optional if active project is set)" },
285
+ organizationId: { type: "number", description: "Organization ID (optional - auto-detected from PAT token)" },
290
286
  ids: { type: "string", description: "Comma-separated testcase IDs" },
291
287
  field: { type: "string", description: "Field name to update" },
292
288
  value: { type: "string", description: "New field value" },
293
289
  },
294
- required: ["projectId", "organizationId", "ids", "field", "value"],
290
+ required: ["ids", "field", "value"],
295
291
  },
296
292
  };
297
293
  const DELETE_TESTCASE_TOOL = {
@@ -300,133 +296,31 @@ const DELETE_TESTCASE_TOOL = {
300
296
  inputSchema: {
301
297
  type: "object",
302
298
  properties: {
303
- projectId: { type: "string", description: "Project ID" },
304
- organizationId: { type: "number", description: "Organization ID" },
299
+ projectId: { type: "string", description: "Project ID (optional if active project is set)" },
300
+ organizationId: { type: "number", description: "Organization ID (optional - auto-detected from PAT token)" },
305
301
  ids: { type: "string", description: "Comma-separated testcase IDs to delete" },
306
302
  },
307
- required: ["projectId", "organizationId", "ids"],
308
- },
309
- };
310
- const CLONE_TESTCASE_TOOL = {
311
- name: "clone_testcase",
312
- description: "Clone an existing test case with all its steps",
313
- inputSchema: {
314
- type: "object",
315
- properties: {
316
- projectId: { type: "string", description: "Project ID" },
317
- id: { type: "string", description: "Test case ID to clone" },
318
- organizationId: { type: "number", description: "Organization ID" },
319
- },
320
- required: ["projectId", "id", "organizationId"],
321
- },
322
- };
323
- const CREATE_TEST_STEP_TOOL = {
324
- name: "create_test_step",
325
- description: "Create or update test steps for a test case",
326
- inputSchema: {
327
- type: "object",
328
- properties: {
329
- projectId: { type: "string", description: "Project ID" },
330
- testcaseId: { type: "string", description: "Test case ID" },
331
- organizationId: { type: "number", description: "Organization ID" },
332
- steps: { type: "string", description: "JSON string of test steps array" },
333
- },
334
- required: ["projectId", "testcaseId", "organizationId", "steps"],
335
- },
336
- };
337
- const DELETE_TEST_STEP_TOOL = {
338
- name: "delete_test_step",
339
- description: "Delete test steps from a test case",
340
- inputSchema: {
341
- type: "object",
342
- properties: {
343
- testcaseId: { type: "string", description: "Test case ID" },
344
- organizationId: { type: "number", description: "Organization ID" },
345
- stepIds: { type: "string", description: "Comma-separated step IDs to delete" },
346
- },
347
- required: ["testcaseId", "organizationId", "stepIds"],
348
- },
349
- };
350
- const DOWNLOAD_TESTCASES_TOOL = {
351
- name: "download_testcases",
352
- description: "Download test cases as CSV file (returns download URL or data)",
353
- inputSchema: {
354
- type: "object",
355
- properties: {
356
- projectId: { type: "string", description: "Project ID" },
357
- organizationId: { type: "number", description: "Organization ID" },
358
- filters: { type: "string", description: "Optional filters as JSON string" },
359
- },
360
- required: ["projectId", "organizationId"],
361
- },
362
- };
363
- const IMPORT_TESTCASES_TOOL = {
364
- name: "import_testcases",
365
- description: "Import test cases from a CSV file",
366
- inputSchema: {
367
- type: "object",
368
- properties: {
369
- projectId: { type: "string", description: "Project ID" },
370
- organizationId: { type: "number", description: "Organization ID" },
371
- file: { type: "string", description: "File data or path" },
372
- },
373
- required: ["projectId", "organizationId", "file"],
303
+ required: ["ids"],
374
304
  },
375
305
  };
376
306
  const CREATE_FOLDER_TOOL = {
377
307
  name: "create_folder",
378
- description: "Create a new folder in a project",
308
+ description: "Create a new folder in a project for testcases",
379
309
  inputSchema: {
380
310
  type: "object",
381
311
  properties: {
382
- organizationId: { type: "number", description: "Organization ID" },
383
- projectId: { type: "string", description: "Project ID" },
312
+ organizationId: { type: "number", description: "Organization ID (optional - auto-detected from PAT token)" },
313
+ projectId: { type: "string", description: "Project ID (optional if active project is set)" },
384
314
  section: {
385
315
  type: "string",
386
316
  enum: ["TESTCASE", "TEST_CYCLE", "TEST_PLAN", "REQUIREMENT"],
387
- description: "Folder section type (TESTCASE, TEST_CYCLE, TEST_PLAN, or REQUIREMENT)",
317
+ description: "Folder section type (default: TESTCASE)",
318
+ default: "TESTCASE",
388
319
  },
389
320
  name: { type: "string", description: "Name of the new folder (max 50 characters)" },
390
321
  parentId: { type: "number", description: "Parent folder ID for creating subfolder (optional)" },
391
322
  },
392
- required: ["organizationId", "projectId", "section", "name"],
393
- },
394
- };
395
- const DELETE_FOLDER_TOOL = {
396
- name: "delete_folder",
397
- description: "Delete a folder and its subfolders",
398
- inputSchema: {
399
- type: "object",
400
- properties: {
401
- id: { type: "number", description: "Folder ID to delete" },
402
- organizationId: { type: "number", description: "Organization ID" },
403
- projectId: { type: "string", description: "Project ID" },
404
- section: {
405
- type: "string",
406
- enum: ["TESTCASE", "TEST_CYCLE", "TEST_PLAN", "REQUIREMENT"],
407
- description: "Folder section type (TESTCASE, TEST_CYCLE, TEST_PLAN, or REQUIREMENT)",
408
- },
409
- },
410
- required: ["id", "organizationId", "projectId", "section"],
411
- },
412
- };
413
- const UPDATE_FOLDER_TOOL = {
414
- name: "update_folder",
415
- description: "Rename or update folder details",
416
- inputSchema: {
417
- type: "object",
418
- properties: {
419
- id: { type: "number", description: "Folder ID" },
420
- organizationId: { type: "number", description: "Organization ID" },
421
- projectId: { type: "string", description: "Project ID" },
422
- section: {
423
- type: "string",
424
- enum: ["TESTCASE", "TEST_CYCLE", "TEST_PLAN", "REQUIREMENT"],
425
- description: "Folder section type (TESTCASE, TEST_CYCLE, TEST_PLAN, or REQUIREMENT)",
426
- },
427
- name: { type: "string", description: "New folder name" },
428
- },
429
- required: ["id", "organizationId", "projectId", "section", "name"],
323
+ required: ["name"],
430
324
  },
431
325
  };
432
326
  const CREATE_LABEL_TOOL = {
@@ -435,11 +329,11 @@ const CREATE_LABEL_TOOL = {
435
329
  inputSchema: {
436
330
  type: "object",
437
331
  properties: {
438
- projectId: { type: "string", description: "Project ID" },
439
- organizationId: { type: "number", description: "Organization ID" },
440
- values: { type: "array", items: { type: "string" }, description: "Array of label names" },
332
+ projectId: { type: "string", description: "Project ID (optional if active project is set)" },
333
+ organizationId: { type: "number", description: "Organization ID (optional - auto-detected from PAT token)" },
334
+ values: { type: "string", description: "Comma-separated label names to create" },
441
335
  },
442
- required: ["projectId", "organizationId", "values"],
336
+ required: ["values"],
443
337
  },
444
338
  };
445
339
  const GET_USER_PROFILE_TOOL = {
@@ -451,87 +345,29 @@ const GET_USER_PROFILE_TOOL = {
451
345
  required: [],
452
346
  },
453
347
  };
454
- const GET_ORGANIZATION_TOOL = {
455
- name: "get_organization",
456
- description: "Get current user organization details",
457
- inputSchema: {
458
- type: "object",
459
- properties: {},
460
- required: [],
461
- },
462
- };
463
- const GET_MAPPED_ISSUES_TOOL = {
464
- name: "get_mapped_issues",
465
- description: "Get mapped issues for a specific test case from integration platforms",
466
- inputSchema: {
467
- type: "object",
468
- properties: {
469
- organization_id: { type: "number", description: "Organization ID" },
470
- project_id: { type: "string", description: "Project ID" },
471
- integration_type: { type: "string", description: "Integration type (requirement/defect)" },
472
- testcase_id: { type: "string", description: "Test case ID" },
473
- },
474
- required: ["organization_id", "project_id", "integration_type", "testcase_id"],
475
- },
476
- };
477
- const SEARCH_ISSUES_TOOL = {
478
- name: "search_issues",
479
- description: "Search issues from integrated platform (Jira, GitHub, GitLab)",
480
- inputSchema: {
481
- type: "object",
482
- properties: {
483
- project_id: { type: "string", description: "Project ID" },
484
- integration_type: { type: "string", description: "Integration type (requirement/defect)" },
485
- search: { type: "string", description: "Search query string" },
486
- page: { type: "number", description: "Page number" },
487
- limit: { type: "number", description: "Items per page" },
488
- },
489
- required: ["project_id", "integration_type"],
490
- },
491
- };
492
- const MAP_ISSUES_TOOL = {
493
- name: "map_issues",
494
- description: "Map integration issues to a TestKase requirement/defect",
348
+ const SET_ACTIVE_PROJECT_TOOL = {
349
+ name: "set_active_project",
350
+ description: "Set the active project AFTER the user has chosen one from get_my_projects results. " +
351
+ "NEVER call this without first asking the user which project they want. " +
352
+ "Once set, other tools will use this project automatically.",
495
353
  inputSchema: {
496
354
  type: "object",
497
355
  properties: {
498
- organization_id: { type: "number", description: "Organization ID" },
499
- project_id: { type: "string", description: "Project ID" },
500
- testcase_id: { type: "string", description: "Test case ID" },
501
- integration_type: { type: "string", description: "Integration type (requirement/defect)" },
502
- issues: { type: "string", description: "JSON string of issue IDs to map" },
503
- },
504
- required: ["organization_id", "project_id", "testcase_id", "integration_type", "issues"],
505
- },
506
- };
507
- const GENERATE_TESTCASES_TOOL = {
508
- name: "generate_testcases",
509
- description: "Generate test cases using AI from requirement text",
510
- inputSchema: {
511
- type: "object",
512
- properties: {
513
- project_id: { type: "string", description: "Project ID" },
514
- requirement: { type: "string", description: "Requirement text" },
515
- issue_id: { type: "string", description: "Issue ID (optional)" },
516
- selected_fields: { type: "string", description: "Comma-separated field names" },
356
+ projectId: {
357
+ type: "string",
358
+ description: "The project ID to set as active (e.g., PRJ-1234)",
359
+ },
517
360
  },
518
- required: ["project_id"],
361
+ required: [],
519
362
  },
520
363
  };
521
- const CREATE_ISSUE_TOOL = {
522
- name: "create_issue",
523
- description: "Create or update a requirement or defect issue",
364
+ const GET_ACTIVE_PROJECT_TOOL = {
365
+ name: "get_active_project",
366
+ description: "Get the currently active project ID that is being used for operations",
524
367
  inputSchema: {
525
368
  type: "object",
526
- properties: {
527
- projectId: { type: "string", description: "Project ID" },
528
- organizationId: { type: "number", description: "Organization ID" },
529
- type: { type: "string", description: "Issue type (REQUIREMENT or DEFECT)" },
530
- title: { type: "string", description: "Issue title" },
531
- description: { type: "string", description: "Issue description" },
532
- id: { type: "string", description: "Issue ID (if updating)" },
533
- },
534
- required: ["projectId", "organizationId", "type", "title"],
369
+ properties: {},
370
+ required: [],
535
371
  },
536
372
  };
537
373
  // Create server instance
@@ -543,37 +379,24 @@ const server = new Server({
543
379
  tools: {},
544
380
  },
545
381
  });
546
- // List available tools
382
+ // List available tools (14 tools)
547
383
  server.setRequestHandler(ListToolsRequestSchema, async () => {
548
384
  return {
549
385
  tools: [
386
+ GET_MY_PROJECTS_TOOL,
387
+ SET_ACTIVE_PROJECT_TOOL,
388
+ GET_ACTIVE_PROJECT_TOOL,
550
389
  GET_TESTCASES_TOOL,
551
390
  GET_TESTCASE_DETAIL_TOOL,
552
- GET_TESTCASE_HISTORY_TOOL,
553
- GET_FOLDERS_TOOL,
554
- GET_LABELS_TOOL,
555
- GET_ISSUES_TOOL,
556
- GET_MY_PROJECTS_TOOL,
557
391
  CREATE_TESTCASE_TOOL,
558
392
  CREATE_BULK_TESTCASE_TOOL,
559
393
  UPDATE_TESTCASE_FIELD_TOOL,
560
394
  DELETE_TESTCASE_TOOL,
561
- CLONE_TESTCASE_TOOL,
562
- CREATE_TEST_STEP_TOOL,
563
- DELETE_TEST_STEP_TOOL,
564
- DOWNLOAD_TESTCASES_TOOL,
565
- IMPORT_TESTCASES_TOOL,
395
+ GET_FOLDERS_TOOL,
566
396
  CREATE_FOLDER_TOOL,
567
- DELETE_FOLDER_TOOL,
568
- UPDATE_FOLDER_TOOL,
397
+ GET_LABELS_TOOL,
569
398
  CREATE_LABEL_TOOL,
570
399
  GET_USER_PROFILE_TOOL,
571
- GET_ORGANIZATION_TOOL,
572
- GET_MAPPED_ISSUES_TOOL,
573
- SEARCH_ISSUES_TOOL,
574
- MAP_ISSUES_TOOL,
575
- GENERATE_TESTCASES_TOOL,
576
- CREATE_ISSUE_TOOL,
577
400
  ],
578
401
  };
579
402
  });
@@ -581,26 +404,20 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
581
404
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
582
405
  const { name, arguments: args } = request.params;
583
406
  try {
584
- if (name === "get_testcases") {
585
- return await handleGetTestcases(args);
586
- }
587
- else if (name === "get_testcase_detail") {
588
- return await handleGetTestcaseDetail(args);
407
+ if (name === "get_my_projects") {
408
+ return await handleGetMyProjects(args);
589
409
  }
590
- else if (name === "get_testcase_history") {
591
- return await handleGetTestcaseHistory(args);
410
+ else if (name === "set_active_project") {
411
+ return await handleSetActiveProject(args);
592
412
  }
593
- else if (name === "get_folders") {
594
- return await handleGetFolders(args);
413
+ else if (name === "get_active_project") {
414
+ return await handleGetActiveProject();
595
415
  }
596
- else if (name === "get_labels") {
597
- return await handleGetLabels(args);
598
- }
599
- else if (name === "get_issues") {
600
- return await handleGetIssues(args);
416
+ else if (name === "get_testcases") {
417
+ return await handleGetTestcases(args);
601
418
  }
602
- else if (name === "get_my_projects") {
603
- return await handleGetMyProjects(args);
419
+ else if (name === "get_testcase_detail") {
420
+ return await handleGetTestcaseDetail(args);
604
421
  }
605
422
  else if (name === "create_testcase") {
606
423
  return await handleCreateTestcase(args);
@@ -614,29 +431,14 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
614
431
  else if (name === "delete_testcase") {
615
432
  return await handleDeleteTestcase(args);
616
433
  }
617
- else if (name === "clone_testcase") {
618
- return await handleCloneTestcase(args);
619
- }
620
- else if (name === "create_test_step") {
621
- return await handleCreateTestStep(args);
622
- }
623
- else if (name === "delete_test_step") {
624
- return await handleDeleteTestStep(args);
625
- }
626
- else if (name === "download_testcases") {
627
- return await handleDownloadTestcases(args);
628
- }
629
- else if (name === "import_testcases") {
630
- return await handleImportTestcases(args);
434
+ else if (name === "get_folders") {
435
+ return await handleGetFolders(args);
631
436
  }
632
437
  else if (name === "create_folder") {
633
438
  return await handleCreateFolder(args);
634
439
  }
635
- else if (name === "delete_folder") {
636
- return await handleDeleteFolder(args);
637
- }
638
- else if (name === "update_folder") {
639
- return await handleUpdateFolder(args);
440
+ else if (name === "get_labels") {
441
+ return await handleGetLabels(args);
640
442
  }
641
443
  else if (name === "create_label") {
642
444
  return await handleCreateLabel(args);
@@ -644,24 +446,6 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
644
446
  else if (name === "get_user_profile") {
645
447
  return await handleGetUserProfile(args);
646
448
  }
647
- else if (name === "get_organization") {
648
- return await handleGetOrganization(args);
649
- }
650
- else if (name === "get_mapped_issues") {
651
- return await handleGetMappedIssues(args);
652
- }
653
- else if (name === "search_issues") {
654
- return await handleSearchIssues(args);
655
- }
656
- else if (name === "map_issues") {
657
- return await handleMapIssues(args);
658
- }
659
- else if (name === "generate_testcases") {
660
- return await handleGenerateTestcases(args);
661
- }
662
- else if (name === "create_issue") {
663
- return await handleCreateIssue(args);
664
- }
665
449
  throw new Error(`Unknown tool: ${name}`);
666
450
  }
667
451
  catch (error) {
@@ -681,26 +465,13 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
681
465
  });
682
466
  // Handler for get_testcases tool
683
467
  async function handleGetTestcases(args) {
684
- // Validate required parameters
685
- if (!args.projectId) {
686
- throw new Error("projectId is required");
687
- }
688
- if (!args.organizationId) {
689
- throw new Error("organizationId is required");
690
- }
691
- // Check for authentication
692
- if (!config.patToken) {
693
- throw new Error("Authentication required. Please set TESTKASE_PAT_TOKEN environment variable. " +
694
- "Generate a Personal Access Token (PAT) from your TestKase account settings.");
695
- }
696
- // Validate PAT token format
697
- if (!config.patToken.startsWith("xyz_")) {
698
- throw new Error("Invalid PAT token format. TestKase PAT tokens must start with 'xyz_' prefix. " +
699
- "Please generate a valid PAT from your TestKase account settings.");
700
- }
468
+ validateAuth();
469
+ // Get project and organization IDs (from args or cached)
470
+ const projectId = getProjectId(args.projectId);
471
+ const organizationId = await getOrganizationId(args.organizationId);
701
472
  // Build query parameters
702
473
  const queryParams = new URLSearchParams();
703
- queryParams.append("organizationId", args.organizationId.toString());
474
+ queryParams.append("organizationId", organizationId.toString());
704
475
  if (args.page !== undefined)
705
476
  queryParams.append("page", args.page.toString());
706
477
  if (args.limit !== undefined)
@@ -728,7 +499,7 @@ async function handleGetTestcases(args) {
728
499
  if (args.updated_end_date)
729
500
  queryParams.append("updated_end_date", args.updated_end_date);
730
501
  // Construct API URL
731
- const url = `${config.apiBaseUrl}/api/v1/projects/testcases/get-testcase/${args.projectId}?${queryParams.toString()}`;
502
+ const url = `${config.apiBaseUrl}/api/v1/projects/testcases/get-testcase/${projectId}?${queryParams.toString()}`;
732
503
  // Prepare headers with PAT token
733
504
  // The PAT token is sent as Bearer token and will be validated by TestKase's JwtAuthGuard
734
505
  // which checks for 'xyz_' prefix and validates against the PAT database
@@ -806,9 +577,11 @@ async function handleGetTestcases(args) {
806
577
  // Handler for get_testcase_detail tool
807
578
  async function handleGetTestcaseDetail(args) {
808
579
  validateAuth();
580
+ const projectId = getProjectId(args.projectId);
581
+ const organizationId = await getOrganizationId(args.organizationId);
809
582
  const queryParams = new URLSearchParams();
810
- queryParams.append("organizationId", args.organizationId.toString());
811
- const url = `${config.apiBaseUrl}/api/v1/projects/testcases/get-testcase-detail/${args.projectId}/${args.testcaseId}?${queryParams.toString()}`;
583
+ queryParams.append("organizationId", organizationId.toString());
584
+ const url = `${config.apiBaseUrl}/api/v1/projects/testcases/get-testcase-detail/${projectId}/${args.testcaseId}?${queryParams.toString()}`;
812
585
  try {
813
586
  const response = await axios.get(url, {
814
587
  headers: {
@@ -823,33 +596,21 @@ async function handleGetTestcaseDetail(args) {
823
596
  throw handleApiError(error);
824
597
  }
825
598
  }
826
- // Handler for get_testcase_history tool
827
- async function handleGetTestcaseHistory(args) {
828
- validateAuth();
829
- const queryParams = new URLSearchParams();
830
- queryParams.append("organizationId", args.organizationId.toString());
831
- const url = `${config.apiBaseUrl}/api/v1/projects/testcases/get-testcase-history/${args.projectId}/${args.testcaseId}?${queryParams.toString()}`;
832
- try {
833
- const response = await axios.get(url, {
834
- headers: {
835
- "Content-Type": "application/json",
836
- "Authorization": `Bearer ${config.patToken}`,
837
- },
838
- });
839
- const data = response.data;
840
- return formatResponse(data, `Testcase History: ${args.testcaseId}`);
841
- }
842
- catch (error) {
843
- throw handleApiError(error);
844
- }
845
- }
846
599
  // Handler for get_folders tool
847
600
  async function handleGetFolders(args) {
848
601
  validateAuth();
602
+ const projectId = getProjectId(args.projectId);
603
+ const organizationId = await getOrganizationId(args.organizationId);
604
+ // Validate and normalize section - default to TESTCASE
605
+ const validSections = ["TESTCASE", "TEST_CYCLE", "TEST_PLAN", "REQUIREMENT"];
606
+ let section = (args.section || "TESTCASE").toUpperCase();
607
+ if (!validSections.includes(section)) {
608
+ section = "TESTCASE"; // Default if invalid
609
+ }
849
610
  const queryParams = new URLSearchParams();
850
- queryParams.append("organizationId", args.organizationId.toString());
851
- queryParams.append("projectId", args.projectId);
852
- queryParams.append("section", args.section);
611
+ queryParams.append("organizationId", organizationId.toString());
612
+ queryParams.append("projectId", projectId);
613
+ queryParams.append("section", section);
853
614
  if (args.search) {
854
615
  queryParams.append("search", args.search);
855
616
  }
@@ -871,9 +632,11 @@ async function handleGetFolders(args) {
871
632
  // Handler for get_labels tool
872
633
  async function handleGetLabels(args) {
873
634
  validateAuth();
635
+ const projectId = getProjectId(args.projectId);
636
+ const organizationId = await getOrganizationId(args.organizationId);
874
637
  const queryParams = new URLSearchParams();
875
- queryParams.append("organizationId", args.organizationId.toString());
876
- const url = `${config.apiBaseUrl}/api/v1/projects/labels/get-label/${args.projectId}?${queryParams.toString()}`;
638
+ queryParams.append("organizationId", organizationId.toString());
639
+ const url = `${config.apiBaseUrl}/api/v1/projects/labels/get-label/${projectId}?${queryParams.toString()}`;
877
640
  try {
878
641
  const response = await axios.get(url, {
879
642
  headers: {
@@ -888,32 +651,6 @@ async function handleGetLabels(args) {
888
651
  throw handleApiError(error);
889
652
  }
890
653
  }
891
- // Handler for get_issues tool
892
- async function handleGetIssues(args) {
893
- validateAuth();
894
- const queryParams = new URLSearchParams();
895
- queryParams.append("organizationId", args.organizationId.toString());
896
- if (args.type)
897
- queryParams.append("type", args.type);
898
- if (args.page)
899
- queryParams.append("page", args.page.toString());
900
- if (args.limit)
901
- queryParams.append("limit", args.limit.toString());
902
- const url = `${config.apiBaseUrl}/api/v1/projects/issues/get-issues/${args.projectId}?${queryParams.toString()}`;
903
- try {
904
- const response = await axios.get(url, {
905
- headers: {
906
- "Content-Type": "application/json",
907
- "Authorization": `Bearer ${config.patToken}`,
908
- },
909
- });
910
- const data = response.data;
911
- return formatResponse(data, `Issues for Project: ${args.projectId}`);
912
- }
913
- catch (error) {
914
- throw handleApiError(error);
915
- }
916
- }
917
654
  // Handler for get_my_projects tool
918
655
  async function handleGetMyProjects(args) {
919
656
  validateAuth();
@@ -937,34 +674,86 @@ async function handleGetMyProjects(args) {
937
674
  throw handleApiError(error);
938
675
  }
939
676
  }
940
- // Handler for create_testcase tool
677
+ // Handler for create_testcase tool (handles test_steps internally)
941
678
  async function handleCreateTestcase(args) {
942
679
  validateAuth();
680
+ const projectId = getProjectId(args.projectId);
681
+ const organizationId = await getOrganizationId(args.organizationId);
682
+ // Extract and parse test_steps (JSON string) before sending to API
683
+ const { test_steps: test_steps_raw, labels: labels_raw, ...testcaseData } = args;
684
+ // Parse test_steps from JSON string
685
+ const test_steps = test_steps_raw ? (typeof test_steps_raw === 'string' ? JSON.parse(test_steps_raw) : test_steps_raw) : null;
686
+ // Parse labels from comma-separated string
687
+ if (labels_raw) {
688
+ testcaseData.labels = typeof labels_raw === 'string' ? labels_raw.split(',').map((l) => l.trim()) : labels_raw;
689
+ }
943
690
  const queryParams = new URLSearchParams();
944
- queryParams.append("organizationId", args.organizationId.toString());
945
- const url = `${config.apiBaseUrl}/api/v1/projects/testcases/create-testcase/${args.projectId}?${queryParams.toString()}`;
691
+ queryParams.append("organizationId", organizationId.toString());
692
+ const url = `${config.apiBaseUrl}/api/v1/projects/testcases/create-testcase/${projectId}?${queryParams.toString()}`;
946
693
  try {
947
- const response = await axios.post(url, args, {
694
+ // Step 1: Create testcase
695
+ const response = await axios.post(url, testcaseData, {
948
696
  headers: { "Content-Type": "application/json", "Authorization": `Bearer ${config.patToken}` },
949
697
  });
950
- return formatResponse(response.data, "Testcase Created");
698
+ const createdTestcase = response.data?.data || response.data;
699
+ const testcaseId = createdTestcase?.id || createdTestcase?.testcaseId;
700
+ // Step 2: If test_steps provided, create them
701
+ if (test_steps && Array.isArray(test_steps) && test_steps.length > 0 && testcaseId) {
702
+ const stepsUrl = `${config.apiBaseUrl}/api/v1/projects/testcases/teststeps/${projectId}/${testcaseId}?organizationId=${organizationId}`;
703
+ await axios.post(stepsUrl, { steps: test_steps }, {
704
+ headers: { "Content-Type": "application/json", "Authorization": `Bearer ${config.patToken}` },
705
+ });
706
+ }
707
+ return formatResponse(response.data, "Testcase Created" + (test_steps?.length ? ` with ${test_steps.length} test steps` : ""));
951
708
  }
952
709
  catch (error) {
953
710
  throw handleApiError(error);
954
711
  }
955
712
  }
956
- // Handler for create_bulk_testcase tool
713
+ // Handler for create_bulk_testcase tool (handles test_steps internally)
957
714
  async function handleCreateBulkTestcase(args) {
958
715
  validateAuth();
959
- const queryParams = new URLSearchParams();
960
- queryParams.append("organizationId", args.organizationId.toString());
961
- const url = `${config.apiBaseUrl}/api/v1/projects/testcases/create-bulk-testcase/${args.projectId}?${queryParams.toString()}`;
716
+ const projectId = getProjectId(args.projectId);
717
+ const organizationId = await getOrganizationId(args.organizationId);
962
718
  const testcases = typeof args.testcases === 'string' ? JSON.parse(args.testcases) : args.testcases;
719
+ // Separate test_steps from each testcase for later processing
720
+ const testcasesWithSteps = testcases.map((tc) => ({
721
+ testcase: { ...tc, test_steps: undefined },
722
+ test_steps: tc.test_steps
723
+ }));
724
+ const queryParams = new URLSearchParams();
725
+ queryParams.append("organizationId", organizationId.toString());
726
+ const url = `${config.apiBaseUrl}/api/v1/projects/testcases/create-bulk-testcase/${projectId}?${queryParams.toString()}`;
963
727
  try {
964
- const response = await axios.post(url, { testcases }, {
728
+ // Step 1: Create all testcases (without steps)
729
+ const cleanTestcases = testcasesWithSteps.map((t) => t.testcase);
730
+ const response = await axios.post(url, { testcases: cleanTestcases }, {
965
731
  headers: { "Content-Type": "application/json", "Authorization": `Bearer ${config.patToken}` },
966
732
  });
967
- return formatResponse(response.data, "Bulk Testcases Created");
733
+ const results = response.data?.results || [];
734
+ // Step 2: Create test steps for each successfully created testcase
735
+ let stepsCreated = 0;
736
+ for (let i = 0; i < results.length; i++) {
737
+ const result = results[i];
738
+ const steps = testcasesWithSteps[i]?.test_steps;
739
+ if (result.status === 'success' && steps && Array.isArray(steps) && steps.length > 0) {
740
+ const testcaseId = result.testcaseId || result.id;
741
+ if (testcaseId) {
742
+ try {
743
+ const stepsUrl = `${config.apiBaseUrl}/api/v1/projects/testcases/teststeps/${projectId}/${testcaseId}?organizationId=${organizationId}`;
744
+ await axios.post(stepsUrl, { steps }, {
745
+ headers: { "Content-Type": "application/json", "Authorization": `Bearer ${config.patToken}` },
746
+ });
747
+ stepsCreated++;
748
+ }
749
+ catch (e) {
750
+ // Log but don't fail the whole operation
751
+ console.error(`Failed to create steps for testcase ${testcaseId}`);
752
+ }
753
+ }
754
+ }
755
+ }
756
+ return formatResponse(response.data, `Bulk Testcases Created (${stepsCreated} with test steps)`);
968
757
  }
969
758
  catch (error) {
970
759
  throw handleApiError(error);
@@ -973,9 +762,11 @@ async function handleCreateBulkTestcase(args) {
973
762
  // Handler for update_testcase_field tool
974
763
  async function handleUpdateTestcaseField(args) {
975
764
  validateAuth();
765
+ const projectId = getProjectId(args.projectId);
766
+ const organizationId = await getOrganizationId(args.organizationId);
976
767
  const queryParams = new URLSearchParams();
977
- queryParams.append("organizationId", args.organizationId.toString());
978
- const url = `${config.apiBaseUrl}/api/v1/projects/testcases/update-field/${args.projectId}?${queryParams.toString()}`;
768
+ queryParams.append("organizationId", organizationId.toString());
769
+ const url = `${config.apiBaseUrl}/api/v1/projects/testcases/update-field/${projectId}?${queryParams.toString()}`;
979
770
  try {
980
771
  const response = await axios.patch(url, {
981
772
  ids: args.ids.split(','),
@@ -993,9 +784,11 @@ async function handleUpdateTestcaseField(args) {
993
784
  // Handler for delete_testcase tool
994
785
  async function handleDeleteTestcase(args) {
995
786
  validateAuth();
787
+ const projectId = getProjectId(args.projectId);
788
+ const organizationId = await getOrganizationId(args.organizationId);
996
789
  const queryParams = new URLSearchParams();
997
- queryParams.append("organizationId", args.organizationId.toString());
998
- const url = `${config.apiBaseUrl}/api/v1/projects/testcases/delete-testcase/${args.projectId}?${queryParams.toString()}`;
790
+ queryParams.append("organizationId", organizationId.toString());
791
+ const url = `${config.apiBaseUrl}/api/v1/projects/testcases/delete-testcase/${projectId}?${queryParams.toString()}`;
999
792
  try {
1000
793
  const response = await axios.delete(url, {
1001
794
  headers: { "Content-Type": "application/json", "Authorization": `Bearer ${config.patToken}` },
@@ -1007,98 +800,22 @@ async function handleDeleteTestcase(args) {
1007
800
  throw handleApiError(error);
1008
801
  }
1009
802
  }
1010
- // Handler for clone_testcase tool
1011
- async function handleCloneTestcase(args) {
1012
- validateAuth();
1013
- const queryParams = new URLSearchParams();
1014
- queryParams.append("organizationId", args.organizationId.toString());
1015
- const url = `${config.apiBaseUrl}/api/v1/projects/testcases/clone/${args.projectId}/${args.id}?${queryParams.toString()}`;
1016
- try {
1017
- const response = await axios.post(url, {}, {
1018
- headers: { "Content-Type": "application/json", "Authorization": `Bearer ${config.patToken}` },
1019
- });
1020
- return formatResponse(response.data, "Testcase Cloned");
1021
- }
1022
- catch (error) {
1023
- throw handleApiError(error);
1024
- }
1025
- }
1026
- // Handler for create_test_step tool
1027
- async function handleCreateTestStep(args) {
1028
- validateAuth();
1029
- const queryParams = new URLSearchParams();
1030
- queryParams.append("organizationId", args.organizationId.toString());
1031
- const url = `${config.apiBaseUrl}/api/v1/projects/testcases/teststeps/${args.projectId}/${args.testcaseId}?${queryParams.toString()}`;
1032
- const steps = typeof args.steps === 'string' ? JSON.parse(args.steps) : args.steps;
1033
- try {
1034
- const response = await axios.post(url, { steps }, {
1035
- headers: { "Content-Type": "application/json", "Authorization": `Bearer ${config.patToken}` },
1036
- });
1037
- return formatResponse(response.data, "Test Steps Created");
1038
- }
1039
- catch (error) {
1040
- throw handleApiError(error);
1041
- }
1042
- }
1043
- // Handler for delete_test_step tool
1044
- async function handleDeleteTestStep(args) {
1045
- validateAuth();
1046
- const queryParams = new URLSearchParams();
1047
- queryParams.append("organizationId", args.organizationId.toString());
1048
- const url = `${config.apiBaseUrl}/api/v1/projects/testcases/teststeps/${args.testcaseId}?${queryParams.toString()}`;
1049
- try {
1050
- const response = await axios.delete(url, {
1051
- headers: { "Content-Type": "application/json", "Authorization": `Bearer ${config.patToken}` },
1052
- data: { stepIds: args.stepIds.split(',') },
1053
- });
1054
- return formatResponse(response.data, "Test Steps Deleted");
1055
- }
1056
- catch (error) {
1057
- throw handleApiError(error);
1058
- }
1059
- }
1060
- // Handler for download_testcases tool
1061
- async function handleDownloadTestcases(args) {
1062
- validateAuth();
1063
- const queryParams = new URLSearchParams();
1064
- queryParams.append("organizationId", args.organizationId.toString());
1065
- if (args.filters)
1066
- queryParams.append("filters", args.filters);
1067
- const url = `${config.apiBaseUrl}/api/v1/projects/testcases/download/${args.projectId}?${queryParams.toString()}`;
1068
- try {
1069
- const response = await axios.get(url, {
1070
- headers: { "Content-Type": "application/json", "Authorization": `Bearer ${config.patToken}` },
1071
- });
1072
- return formatResponse(response.data, "Testcases Download");
1073
- }
1074
- catch (error) {
1075
- throw handleApiError(error);
1076
- }
1077
- }
1078
- // Handler for import_testcases tool
1079
- async function handleImportTestcases(args) {
1080
- validateAuth();
1081
- const queryParams = new URLSearchParams();
1082
- queryParams.append("organizationId", args.organizationId.toString());
1083
- const url = `${config.apiBaseUrl}/api/v1/projects/testcases/import/${args.projectId}?${queryParams.toString()}`;
1084
- try {
1085
- const response = await axios.post(url, { file: args.file }, {
1086
- headers: { "Content-Type": "application/json", "Authorization": `Bearer ${config.patToken}` },
1087
- });
1088
- return formatResponse(response.data, "Testcases Imported");
1089
- }
1090
- catch (error) {
1091
- throw handleApiError(error);
1092
- }
1093
- }
1094
803
  // Handler for create_folder tool
1095
804
  async function handleCreateFolder(args) {
1096
805
  validateAuth();
806
+ const projectId = getProjectId(args.projectId);
807
+ const organizationId = await getOrganizationId(args.organizationId);
1097
808
  const url = `${config.apiBaseUrl}/api/v1/folders/create-folders`;
809
+ // Validate and normalize section - default to TESTCASE
810
+ const validSections = ["TESTCASE", "TEST_CYCLE", "TEST_PLAN", "REQUIREMENT"];
811
+ let section = (args.section || "TESTCASE").toUpperCase();
812
+ if (!validSections.includes(section)) {
813
+ section = "TESTCASE";
814
+ }
1098
815
  const body = {
1099
- organizationId: args.organizationId,
1100
- projectId: args.projectId,
1101
- section: args.section,
816
+ organizationId: organizationId,
817
+ projectId: projectId,
818
+ section: section,
1102
819
  name: args.name,
1103
820
  ...(args.parentId && { parentId: args.parentId }),
1104
821
  };
@@ -1112,50 +829,18 @@ async function handleCreateFolder(args) {
1112
829
  throw handleApiError(error);
1113
830
  }
1114
831
  }
1115
- // Handler for delete_folder tool
1116
- async function handleDeleteFolder(args) {
1117
- validateAuth();
1118
- const queryParams = new URLSearchParams();
1119
- queryParams.append("organizationId", args.organizationId.toString());
1120
- queryParams.append("projectId", args.projectId);
1121
- queryParams.append("section", args.section);
1122
- const url = `${config.apiBaseUrl}/api/v1/folders/delete/${args.id}?${queryParams.toString()}`;
1123
- try {
1124
- const response = await axios.delete(url, {
1125
- headers: { "Content-Type": "application/json", "Authorization": `Bearer ${config.patToken}` },
1126
- });
1127
- return formatResponse(response.data, "Folder Deleted");
1128
- }
1129
- catch (error) {
1130
- throw handleApiError(error);
1131
- }
1132
- }
1133
- // Handler for update_folder tool
1134
- async function handleUpdateFolder(args) {
1135
- validateAuth();
1136
- const queryParams = new URLSearchParams();
1137
- queryParams.append("organizationId", args.organizationId.toString());
1138
- queryParams.append("projectId", args.projectId);
1139
- queryParams.append("section", args.section);
1140
- const url = `${config.apiBaseUrl}/api/v1/folders/${args.id}?${queryParams.toString()}`;
1141
- try {
1142
- const response = await axios.patch(url, { name: args.name }, {
1143
- headers: { "Content-Type": "application/json", "Authorization": `Bearer ${config.patToken}` },
1144
- });
1145
- return formatResponse(response.data, "Folder Updated");
1146
- }
1147
- catch (error) {
1148
- throw handleApiError(error);
1149
- }
1150
- }
1151
832
  // Handler for create_label tool
1152
833
  async function handleCreateLabel(args) {
1153
834
  validateAuth();
835
+ const projectId = getProjectId(args.projectId);
836
+ const organizationId = await getOrganizationId(args.organizationId);
1154
837
  const queryParams = new URLSearchParams();
1155
- queryParams.append("organizationId", args.organizationId.toString());
1156
- const url = `${config.apiBaseUrl}/api/v1/projects/labels/create/${args.projectId}?${queryParams.toString()}`;
838
+ queryParams.append("organizationId", organizationId.toString());
839
+ const url = `${config.apiBaseUrl}/api/v1/projects/labels/create/${projectId}?${queryParams.toString()}`;
840
+ // Parse values from comma-separated string
841
+ const values = typeof args.values === 'string' ? args.values.split(',').map((v) => v.trim()) : args.values;
1157
842
  try {
1158
- const response = await axios.post(url, { values: args.values }, {
843
+ const response = await axios.post(url, { values }, {
1159
844
  headers: { "Content-Type": "application/json", "Authorization": `Bearer ${config.patToken}` },
1160
845
  });
1161
846
  return formatResponse(response.data, "Label Created");
@@ -1178,106 +863,42 @@ async function handleGetUserProfile(args) {
1178
863
  throw handleApiError(error);
1179
864
  }
1180
865
  }
1181
- // Handler for get_organization tool
1182
- async function handleGetOrganization(args) {
1183
- validateAuth();
1184
- const url = `${config.apiBaseUrl}/api/v1/organizations/my-organization`;
1185
- try {
1186
- const response = await axios.get(url, {
1187
- headers: { "Content-Type": "application/json", "Authorization": `Bearer ${config.patToken}` },
1188
- });
1189
- return formatResponse(response.data, "Organization Details");
1190
- }
1191
- catch (error) {
1192
- throw handleApiError(error);
1193
- }
1194
- }
1195
- // Handler for get_mapped_issues tool
1196
- async function handleGetMappedIssues(args) {
1197
- validateAuth();
1198
- const url = `${config.apiBaseUrl}/api/v1/${args.organization_id}/${args.project_id}/${args.integration_type}/testcases/${args.testcase_id}/issues`;
1199
- try {
1200
- const response = await axios.get(url, {
1201
- headers: { "Content-Type": "application/json", "Authorization": `Bearer ${config.patToken}` },
1202
- });
1203
- return formatResponse(response.data, "Mapped Issues");
1204
- }
1205
- catch (error) {
1206
- throw handleApiError(error);
1207
- }
1208
- }
1209
- // Handler for search_issues tool
1210
- async function handleSearchIssues(args) {
1211
- validateAuth();
1212
- const queryParams = new URLSearchParams();
1213
- if (args.search)
1214
- queryParams.append("search", args.search);
1215
- if (args.page)
1216
- queryParams.append("page", args.page.toString());
1217
- if (args.limit)
1218
- queryParams.append("limit", args.limit.toString());
1219
- const url = `${config.apiBaseUrl}/api/v1/${args.project_id}/${args.integration_type}/issues/search?${queryParams.toString()}`;
1220
- try {
1221
- const response = await axios.get(url, {
1222
- headers: { "Content-Type": "application/json", "Authorization": `Bearer ${config.patToken}` },
1223
- });
1224
- return formatResponse(response.data, "Search Issues");
1225
- }
1226
- catch (error) {
1227
- throw handleApiError(error);
1228
- }
1229
- }
1230
- // Handler for map_issues tool
1231
- async function handleMapIssues(args) {
1232
- validateAuth();
1233
- const url = `${config.apiBaseUrl}/api/v1/${args.organization_id}/${args.project_id}/${args.testcase_id}/${args.integration_type}/issues/map`;
1234
- const issues = typeof args.issues === 'string' ? JSON.parse(args.issues) : args.issues;
1235
- try {
1236
- const response = await axios.post(url, { issues }, {
1237
- headers: { "Content-Type": "application/json", "Authorization": `Bearer ${config.patToken}` },
1238
- });
1239
- return formatResponse(response.data, "Issues Mapped");
1240
- }
1241
- catch (error) {
1242
- throw handleApiError(error);
1243
- }
1244
- }
1245
- // Handler for generate_testcases tool
1246
- async function handleGenerateTestcases(args) {
1247
- validateAuth();
1248
- const queryParams = new URLSearchParams();
1249
- if (args.requirement)
1250
- queryParams.append("requirement", args.requirement);
1251
- if (args.issue_id)
1252
- queryParams.append("issue_id", args.issue_id);
1253
- if (args.selected_fields)
1254
- queryParams.append("selected_fields", args.selected_fields);
1255
- const url = `${config.apiBaseUrl}/api/v1/${args.project_id}/testcases/generate?${queryParams.toString()}`;
1256
- try {
1257
- const response = await axios.get(url, {
1258
- headers: { "Content-Type": "application/json", "Authorization": `Bearer ${config.patToken}` },
1259
- });
1260
- return formatResponse(response.data, "Generated Testcases");
1261
- }
1262
- catch (error) {
1263
- throw handleApiError(error);
866
+ // Handler for set_active_project tool
867
+ async function handleSetActiveProject(args) {
868
+ if (!args.projectId) {
869
+ throw new Error("projectId is required");
1264
870
  }
871
+ setActiveProject(args.projectId);
872
+ return {
873
+ content: [
874
+ {
875
+ type: "text",
876
+ text: `Active project set to: ${args.projectId}\n\nYou can now use other tools without specifying projectId.`,
877
+ },
878
+ ],
879
+ };
1265
880
  }
1266
- // Handler for create_issue tool
1267
- async function handleCreateIssue(args) {
1268
- validateAuth();
1269
- const queryParams = new URLSearchParams();
1270
- queryParams.append("organizationId", args.organizationId.toString());
1271
- const url = `${config.apiBaseUrl}/api/v1/projects/issues/create-issues/${args.projectId}?${queryParams.toString()}`;
1272
- try {
1273
- const response = await axios.post(url, args, {
1274
- headers: { "Content-Type": "application/json", "Authorization": `Bearer ${config.patToken}` },
1275
- });
1276
- return formatResponse(response.data, "Issue Created/Updated");
1277
- }
1278
- catch (error) {
1279
- throw handleApiError(error);
881
+ // Handler for get_active_project tool
882
+ async function handleGetActiveProject() {
883
+ const projectId = cachedProjectId;
884
+ if (!projectId) {
885
+ return {
886
+ content: [
887
+ {
888
+ type: "text",
889
+ text: "No active project is set. Use set_active_project or specify projectId in your next command.",
890
+ },
891
+ ],
892
+ };
1280
893
  }
894
+ return {
895
+ content: [
896
+ {
897
+ type: "text",
898
+ text: `Current active project: ${projectId}`,
899
+ },
900
+ ],
901
+ };
1281
902
  }
1282
903
  // Helper function to validate authentication
1283
904
  function validateAuth() {