@bbearai/mcp-server 0.3.4 → 0.5.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/dist/index-api.js CHANGED
@@ -227,6 +227,33 @@ const tools = [
227
227
  required: ['test_key', 'title', 'steps', 'expected_result'],
228
228
  },
229
229
  },
230
+ {
231
+ name: 'delete_test_cases',
232
+ description: 'Delete one or more test cases. Supports single delete by ID or test_key, or bulk delete with arrays (max 50). WARNING: cascade-deletes associated test_assignments, test_feedback, and ai_test_runs.',
233
+ inputSchema: {
234
+ type: 'object',
235
+ properties: {
236
+ test_case_id: {
237
+ type: 'string',
238
+ description: 'UUID of a single test case to delete',
239
+ },
240
+ test_key: {
241
+ type: 'string',
242
+ description: 'Delete a single test case by test_key (e.g., TC-001)',
243
+ },
244
+ test_case_ids: {
245
+ type: 'array',
246
+ items: { type: 'string' },
247
+ description: 'Array of test case UUIDs to bulk delete (max 50)',
248
+ },
249
+ test_keys: {
250
+ type: 'array',
251
+ items: { type: 'string' },
252
+ description: 'Array of test_keys to bulk delete (max 50)',
253
+ },
254
+ },
255
+ },
256
+ },
230
257
  {
231
258
  name: 'get_qa_tracks',
232
259
  description: 'Get QA tracks for the project',
@@ -319,6 +346,24 @@ async function handleTool(name, args) {
319
346
  priority: args.priority || 'P2',
320
347
  });
321
348
  break;
349
+ case 'delete_test_cases': {
350
+ const ids = args.test_case_ids || (args.test_case_id ? [args.test_case_id] : undefined);
351
+ const keys = args.test_keys || (args.test_key ? [args.test_key] : undefined);
352
+ if (ids) {
353
+ const params = new URLSearchParams();
354
+ ids.forEach((id) => params.append('ids', id));
355
+ result = await apiRequest(`/test-cases?${params.toString()}`, 'DELETE');
356
+ }
357
+ else if (keys) {
358
+ const params = new URLSearchParams();
359
+ keys.forEach((k) => params.append('keys', k));
360
+ result = await apiRequest(`/test-cases?${params.toString()}`, 'DELETE');
361
+ }
362
+ else {
363
+ result = { error: 'Must provide test_case_id, test_key, test_case_ids, or test_keys' };
364
+ }
365
+ break;
366
+ }
322
367
  case 'get_qa_tracks':
323
368
  result = await apiRequest('/qa-tracks');
324
369
  break;
package/dist/index.js CHANGED
@@ -275,6 +275,33 @@ const tools = [
275
275
  },
276
276
  },
277
277
  },
278
+ {
279
+ name: 'delete_test_cases',
280
+ description: 'Delete one or more test cases. Supports single delete by ID or test_key, or bulk delete with arrays (max 50). WARNING: cascade-deletes associated test_assignments, test_feedback, and ai_test_runs. Reports/qa_findings keep their records but test_case_id becomes null.',
281
+ inputSchema: {
282
+ type: 'object',
283
+ properties: {
284
+ test_case_id: {
285
+ type: 'string',
286
+ description: 'UUID of a single test case to delete',
287
+ },
288
+ test_key: {
289
+ type: 'string',
290
+ description: 'Delete a single test case by test_key (e.g., TC-001)',
291
+ },
292
+ test_case_ids: {
293
+ type: 'array',
294
+ items: { type: 'string' },
295
+ description: 'Array of test case UUIDs to bulk delete (max 50)',
296
+ },
297
+ test_keys: {
298
+ type: 'array',
299
+ items: { type: 'string' },
300
+ description: 'Array of test_keys to bulk delete (max 50)',
301
+ },
302
+ },
303
+ },
304
+ },
278
305
  {
279
306
  name: 'list_test_cases',
280
307
  description: 'List all test cases in the project. Returns test_key, title, target_route, and other metadata. Use this to see existing tests before updating them.',
@@ -1197,6 +1224,110 @@ async function updateTestCase(args) {
1197
1224
  updatedFields: Object.keys(updates),
1198
1225
  };
1199
1226
  }
1227
+ async function deleteTestCases(args) {
1228
+ // Validate: exactly one input mode
1229
+ const modes = [
1230
+ args.test_case_id,
1231
+ args.test_key,
1232
+ args.test_case_ids,
1233
+ args.test_keys,
1234
+ ].filter(v => v !== undefined && v !== null);
1235
+ if (modes.length === 0) {
1236
+ return { error: 'Must provide one of: test_case_id, test_key, test_case_ids, or test_keys' };
1237
+ }
1238
+ if (modes.length > 1) {
1239
+ return { error: 'Provide only one of: test_case_id, test_key, test_case_ids, or test_keys' };
1240
+ }
1241
+ let idsToDelete = [];
1242
+ // Single ID
1243
+ if (args.test_case_id) {
1244
+ if (!isValidUUID(args.test_case_id)) {
1245
+ return { error: 'Invalid test_case_id format (must be UUID)' };
1246
+ }
1247
+ idsToDelete = [args.test_case_id];
1248
+ }
1249
+ // Single key
1250
+ if (args.test_key) {
1251
+ const { data: existing } = await supabase
1252
+ .from('test_cases')
1253
+ .select('id')
1254
+ .eq('project_id', PROJECT_ID)
1255
+ .eq('test_key', args.test_key)
1256
+ .single();
1257
+ if (!existing) {
1258
+ return { error: `Test case with key "${args.test_key}" not found` };
1259
+ }
1260
+ idsToDelete = [existing.id];
1261
+ }
1262
+ // Bulk IDs
1263
+ if (args.test_case_ids) {
1264
+ if (!Array.isArray(args.test_case_ids) || args.test_case_ids.length === 0) {
1265
+ return { error: 'test_case_ids must be a non-empty array' };
1266
+ }
1267
+ if (args.test_case_ids.length > 50) {
1268
+ return { error: 'Cannot delete more than 50 test cases at once' };
1269
+ }
1270
+ const invalidIds = args.test_case_ids.filter(id => !isValidUUID(id));
1271
+ if (invalidIds.length > 0) {
1272
+ return { error: `Invalid UUID(s): ${invalidIds.join(', ')}` };
1273
+ }
1274
+ idsToDelete = args.test_case_ids;
1275
+ }
1276
+ // Bulk keys
1277
+ if (args.test_keys) {
1278
+ if (!Array.isArray(args.test_keys) || args.test_keys.length === 0) {
1279
+ return { error: 'test_keys must be a non-empty array' };
1280
+ }
1281
+ if (args.test_keys.length > 50) {
1282
+ return { error: 'Cannot delete more than 50 test cases at once' };
1283
+ }
1284
+ const { data: existing, error: lookupError } = await supabase
1285
+ .from('test_cases')
1286
+ .select('id, test_key')
1287
+ .eq('project_id', PROJECT_ID)
1288
+ .in('test_key', args.test_keys);
1289
+ if (lookupError) {
1290
+ return { error: lookupError.message };
1291
+ }
1292
+ const foundKeys = (existing || []).map((tc) => tc.test_key);
1293
+ const notFound = args.test_keys.filter(k => !foundKeys.includes(k));
1294
+ if (notFound.length > 0) {
1295
+ return { error: `Test case(s) not found: ${notFound.join(', ')}` };
1296
+ }
1297
+ idsToDelete = (existing || []).map((tc) => tc.id);
1298
+ }
1299
+ // Pre-fetch details for the response
1300
+ const { data: toDelete } = await supabase
1301
+ .from('test_cases')
1302
+ .select('id, test_key, title')
1303
+ .eq('project_id', PROJECT_ID)
1304
+ .in('id', idsToDelete);
1305
+ if (!toDelete || toDelete.length === 0) {
1306
+ return { error: 'No matching test cases found in this project' };
1307
+ }
1308
+ // Delete
1309
+ const { error: deleteError } = await supabase
1310
+ .from('test_cases')
1311
+ .delete()
1312
+ .eq('project_id', PROJECT_ID)
1313
+ .in('id', idsToDelete);
1314
+ if (deleteError) {
1315
+ return { error: deleteError.message };
1316
+ }
1317
+ return {
1318
+ success: true,
1319
+ deletedCount: toDelete.length,
1320
+ deleted: toDelete.map((tc) => ({
1321
+ id: tc.id,
1322
+ testKey: tc.test_key,
1323
+ title: tc.title,
1324
+ })),
1325
+ message: toDelete.length === 1
1326
+ ? `Test case ${toDelete[0].test_key} deleted successfully`
1327
+ : `${toDelete.length} test cases deleted successfully`,
1328
+ warning: 'Associated test_assignments, test_feedback, and ai_test_runs have been cascade-deleted. Reports and qa_findings referencing these tests now have null test_case_id.',
1329
+ };
1330
+ }
1200
1331
  async function listTestCases(args) {
1201
1332
  let query = supabase
1202
1333
  .from('test_cases')
@@ -3955,6 +4086,9 @@ async function main() {
3955
4086
  case 'update_test_case':
3956
4087
  result = await updateTestCase(args);
3957
4088
  break;
4089
+ case 'delete_test_cases':
4090
+ result = await deleteTestCases(args);
4091
+ break;
3958
4092
  case 'list_test_cases':
3959
4093
  result = await listTestCases(args);
3960
4094
  break;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bbearai/mcp-server",
3
- "version": "0.3.4",
3
+ "version": "0.5.0",
4
4
  "description": "MCP server for BugBear - allows Claude Code to query bug reports",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
package/src/index-api.ts CHANGED
@@ -40,7 +40,7 @@ function validateConfig() {
40
40
  // API helper
41
41
  async function apiRequest(
42
42
  endpoint: string,
43
- method: 'GET' | 'POST' | 'PATCH' = 'GET',
43
+ method: 'GET' | 'POST' | 'PATCH' | 'DELETE' = 'GET',
44
44
  body?: Record<string, unknown>
45
45
  ): Promise<{ data?: unknown; error?: string }> {
46
46
  try {
@@ -241,6 +241,33 @@ const tools = [
241
241
  required: ['test_key', 'title', 'steps', 'expected_result'],
242
242
  },
243
243
  },
244
+ {
245
+ name: 'delete_test_cases',
246
+ description: 'Delete one or more test cases. Supports single delete by ID or test_key, or bulk delete with arrays (max 50). WARNING: cascade-deletes associated test_assignments, test_feedback, and ai_test_runs.',
247
+ inputSchema: {
248
+ type: 'object' as const,
249
+ properties: {
250
+ test_case_id: {
251
+ type: 'string',
252
+ description: 'UUID of a single test case to delete',
253
+ },
254
+ test_key: {
255
+ type: 'string',
256
+ description: 'Delete a single test case by test_key (e.g., TC-001)',
257
+ },
258
+ test_case_ids: {
259
+ type: 'array',
260
+ items: { type: 'string' },
261
+ description: 'Array of test case UUIDs to bulk delete (max 50)',
262
+ },
263
+ test_keys: {
264
+ type: 'array',
265
+ items: { type: 'string' },
266
+ description: 'Array of test_keys to bulk delete (max 50)',
267
+ },
268
+ },
269
+ },
270
+ },
244
271
  {
245
272
  name: 'get_qa_tracks',
246
273
  description: 'Get QA tracks for the project',
@@ -340,6 +367,24 @@ async function handleTool(
340
367
  });
341
368
  break;
342
369
 
370
+ case 'delete_test_cases': {
371
+ const ids = args.test_case_ids as string[] || (args.test_case_id ? [args.test_case_id as string] : undefined);
372
+ const keys = args.test_keys as string[] || (args.test_key ? [args.test_key as string] : undefined);
373
+
374
+ if (ids) {
375
+ const params = new URLSearchParams();
376
+ ids.forEach((id: string) => params.append('ids', id));
377
+ result = await apiRequest(`/test-cases?${params.toString()}`, 'DELETE');
378
+ } else if (keys) {
379
+ const params = new URLSearchParams();
380
+ keys.forEach((k: string) => params.append('keys', k));
381
+ result = await apiRequest(`/test-cases?${params.toString()}`, 'DELETE');
382
+ } else {
383
+ result = { error: 'Must provide test_case_id, test_key, test_case_ids, or test_keys' };
384
+ }
385
+ break;
386
+ }
387
+
343
388
  case 'get_qa_tracks':
344
389
  result = await apiRequest('/qa-tracks');
345
390
  break;
package/src/index.ts CHANGED
@@ -292,6 +292,33 @@ const tools = [
292
292
  },
293
293
  },
294
294
  },
295
+ {
296
+ name: 'delete_test_cases',
297
+ description: 'Delete one or more test cases. Supports single delete by ID or test_key, or bulk delete with arrays (max 50). WARNING: cascade-deletes associated test_assignments, test_feedback, and ai_test_runs. Reports/qa_findings keep their records but test_case_id becomes null.',
298
+ inputSchema: {
299
+ type: 'object' as const,
300
+ properties: {
301
+ test_case_id: {
302
+ type: 'string',
303
+ description: 'UUID of a single test case to delete',
304
+ },
305
+ test_key: {
306
+ type: 'string',
307
+ description: 'Delete a single test case by test_key (e.g., TC-001)',
308
+ },
309
+ test_case_ids: {
310
+ type: 'array',
311
+ items: { type: 'string' },
312
+ description: 'Array of test case UUIDs to bulk delete (max 50)',
313
+ },
314
+ test_keys: {
315
+ type: 'array',
316
+ items: { type: 'string' },
317
+ description: 'Array of test_keys to bulk delete (max 50)',
318
+ },
319
+ },
320
+ },
321
+ },
295
322
  {
296
323
  name: 'list_test_cases',
297
324
  description: 'List all test cases in the project. Returns test_key, title, target_route, and other metadata. Use this to see existing tests before updating them.',
@@ -1281,6 +1308,130 @@ async function updateTestCase(args: {
1281
1308
  };
1282
1309
  }
1283
1310
 
1311
+ async function deleteTestCases(args: {
1312
+ test_case_id?: string;
1313
+ test_key?: string;
1314
+ test_case_ids?: string[];
1315
+ test_keys?: string[];
1316
+ }) {
1317
+ // Validate: exactly one input mode
1318
+ const modes = [
1319
+ args.test_case_id,
1320
+ args.test_key,
1321
+ args.test_case_ids,
1322
+ args.test_keys,
1323
+ ].filter(v => v !== undefined && v !== null);
1324
+
1325
+ if (modes.length === 0) {
1326
+ return { error: 'Must provide one of: test_case_id, test_key, test_case_ids, or test_keys' };
1327
+ }
1328
+ if (modes.length > 1) {
1329
+ return { error: 'Provide only one of: test_case_id, test_key, test_case_ids, or test_keys' };
1330
+ }
1331
+
1332
+ let idsToDelete: string[] = [];
1333
+
1334
+ // Single ID
1335
+ if (args.test_case_id) {
1336
+ if (!isValidUUID(args.test_case_id)) {
1337
+ return { error: 'Invalid test_case_id format (must be UUID)' };
1338
+ }
1339
+ idsToDelete = [args.test_case_id];
1340
+ }
1341
+
1342
+ // Single key
1343
+ if (args.test_key) {
1344
+ const { data: existing } = await supabase
1345
+ .from('test_cases')
1346
+ .select('id')
1347
+ .eq('project_id', PROJECT_ID)
1348
+ .eq('test_key', args.test_key)
1349
+ .single();
1350
+
1351
+ if (!existing) {
1352
+ return { error: `Test case with key "${args.test_key}" not found` };
1353
+ }
1354
+ idsToDelete = [existing.id];
1355
+ }
1356
+
1357
+ // Bulk IDs
1358
+ if (args.test_case_ids) {
1359
+ if (!Array.isArray(args.test_case_ids) || args.test_case_ids.length === 0) {
1360
+ return { error: 'test_case_ids must be a non-empty array' };
1361
+ }
1362
+ if (args.test_case_ids.length > 50) {
1363
+ return { error: 'Cannot delete more than 50 test cases at once' };
1364
+ }
1365
+ const invalidIds = args.test_case_ids.filter(id => !isValidUUID(id));
1366
+ if (invalidIds.length > 0) {
1367
+ return { error: `Invalid UUID(s): ${invalidIds.join(', ')}` };
1368
+ }
1369
+ idsToDelete = args.test_case_ids;
1370
+ }
1371
+
1372
+ // Bulk keys
1373
+ if (args.test_keys) {
1374
+ if (!Array.isArray(args.test_keys) || args.test_keys.length === 0) {
1375
+ return { error: 'test_keys must be a non-empty array' };
1376
+ }
1377
+ if (args.test_keys.length > 50) {
1378
+ return { error: 'Cannot delete more than 50 test cases at once' };
1379
+ }
1380
+ const { data: existing, error: lookupError } = await supabase
1381
+ .from('test_cases')
1382
+ .select('id, test_key')
1383
+ .eq('project_id', PROJECT_ID)
1384
+ .in('test_key', args.test_keys);
1385
+
1386
+ if (lookupError) {
1387
+ return { error: lookupError.message };
1388
+ }
1389
+
1390
+ const foundKeys = (existing || []).map((tc: any) => tc.test_key);
1391
+ const notFound = args.test_keys.filter(k => !foundKeys.includes(k));
1392
+ if (notFound.length > 0) {
1393
+ return { error: `Test case(s) not found: ${notFound.join(', ')}` };
1394
+ }
1395
+ idsToDelete = (existing || []).map((tc: any) => tc.id);
1396
+ }
1397
+
1398
+ // Pre-fetch details for the response
1399
+ const { data: toDelete } = await supabase
1400
+ .from('test_cases')
1401
+ .select('id, test_key, title')
1402
+ .eq('project_id', PROJECT_ID)
1403
+ .in('id', idsToDelete);
1404
+
1405
+ if (!toDelete || toDelete.length === 0) {
1406
+ return { error: 'No matching test cases found in this project' };
1407
+ }
1408
+
1409
+ // Delete
1410
+ const { error: deleteError } = await supabase
1411
+ .from('test_cases')
1412
+ .delete()
1413
+ .eq('project_id', PROJECT_ID)
1414
+ .in('id', idsToDelete);
1415
+
1416
+ if (deleteError) {
1417
+ return { error: deleteError.message };
1418
+ }
1419
+
1420
+ return {
1421
+ success: true,
1422
+ deletedCount: toDelete.length,
1423
+ deleted: toDelete.map((tc: any) => ({
1424
+ id: tc.id,
1425
+ testKey: tc.test_key,
1426
+ title: tc.title,
1427
+ })),
1428
+ message: toDelete.length === 1
1429
+ ? `Test case ${toDelete[0].test_key} deleted successfully`
1430
+ : `${toDelete.length} test cases deleted successfully`,
1431
+ warning: 'Associated test_assignments, test_feedback, and ai_test_runs have been cascade-deleted. Reports and qa_findings referencing these tests now have null test_case_id.',
1432
+ };
1433
+ }
1434
+
1284
1435
  async function listTestCases(args: {
1285
1436
  track?: string;
1286
1437
  priority?: string;
@@ -4527,6 +4678,9 @@ async function main() {
4527
4678
  case 'update_test_case':
4528
4679
  result = await updateTestCase(args as any);
4529
4680
  break;
4681
+ case 'delete_test_cases':
4682
+ result = await deleteTestCases(args as any);
4683
+ break;
4530
4684
  case 'list_test_cases':
4531
4685
  result = await listTestCases(args as any);
4532
4686
  break;