apteva 0.4.3 → 0.4.5

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.
@@ -0,0 +1,12 @@
1
+ export function json(data: unknown, status = 200): Response {
2
+ return new Response(JSON.stringify(data), {
3
+ status,
4
+ headers: { "Content-Type": "application/json" },
5
+ });
6
+ }
7
+
8
+ export const isDev = process.env.NODE_ENV !== "production";
9
+
10
+ export function debug(...args: unknown[]) {
11
+ if (isDev) console.log("[api]", ...args);
12
+ }
@@ -0,0 +1,608 @@
1
+ import { json } from "./helpers";
2
+ import { McpServerDB, generateId } from "../../db";
3
+ import { ProviderKeys } from "../../providers";
4
+ import { getProvider, getProviderIds, registerProvider } from "../../integrations";
5
+ import { ComposioProvider } from "../../integrations/composio";
6
+ import {
7
+ AgentDojoProvider,
8
+ listServers as listAgentDojoServers,
9
+ createServer as createAgentDojoServer,
10
+ getServer as getAgentDojoServer,
11
+ deleteServer as deleteAgentDojoServer,
12
+ } from "../../integrations/agentdojo";
13
+ import type { AuthContext } from "../../auth/middleware";
14
+
15
+ // Register integration providers on module load
16
+ registerProvider(ComposioProvider);
17
+ registerProvider(AgentDojoProvider);
18
+
19
+ export async function handleIntegrationRoutes(
20
+ req: Request,
21
+ path: string,
22
+ method: string,
23
+ authContext?: AuthContext,
24
+ ): Promise<Response | null> {
25
+ const user = authContext?.user;
26
+
27
+ // ============ Generic Integration Providers ============
28
+
29
+ // GET /api/integrations/providers - List available integration providers
30
+ if (path === "/api/integrations/providers" && method === "GET") {
31
+ const providerIds = getProviderIds();
32
+ const providers = providerIds.map(id => {
33
+ const provider = getProvider(id);
34
+ const hasKey = !!ProviderKeys.getDecrypted(id);
35
+ return {
36
+ id,
37
+ name: provider?.name || id,
38
+ connected: hasKey,
39
+ };
40
+ });
41
+ return json({ providers });
42
+ }
43
+
44
+ // GET /api/integrations/:provider/apps - List available apps from a provider
45
+ const appsMatch = path.match(/^\/api\/integrations\/([^/]+)\/apps$/);
46
+ if (appsMatch && method === "GET") {
47
+ const providerId = appsMatch[1];
48
+ const provider = getProvider(providerId);
49
+ if (!provider) {
50
+ return json({ error: `Unknown provider: ${providerId}` }, 404);
51
+ }
52
+
53
+ const apiKey = ProviderKeys.getDecrypted(providerId);
54
+ if (!apiKey) {
55
+ return json({ error: `${provider.name} API key not configured`, apps: [] }, 200);
56
+ }
57
+
58
+ try {
59
+ const apps = await provider.listApps(apiKey);
60
+ return json({ apps });
61
+ } catch (e) {
62
+ console.error(`Failed to list apps from ${providerId}:`, e);
63
+ return json({ error: "Failed to fetch apps" }, 500);
64
+ }
65
+ }
66
+
67
+ // GET /api/integrations/:provider/connected - List user's connected accounts
68
+ const connectedMatch = path.match(/^\/api\/integrations\/([^/]+)\/connected$/);
69
+ if (connectedMatch && method === "GET") {
70
+ const providerId = connectedMatch[1];
71
+ const provider = getProvider(providerId);
72
+ if (!provider) {
73
+ return json({ error: `Unknown provider: ${providerId}` }, 404);
74
+ }
75
+
76
+ const apiKey = ProviderKeys.getDecrypted(providerId);
77
+ if (!apiKey) {
78
+ return json({ error: `${provider.name} API key not configured`, accounts: [] }, 200);
79
+ }
80
+
81
+ // Use Apteva user ID as the entity ID for the provider
82
+ const userId = user?.id || "default";
83
+
84
+ try {
85
+ const accounts = await provider.listConnectedAccounts(apiKey, userId);
86
+ return json({ accounts });
87
+ } catch (e) {
88
+ console.error(`Failed to list connected accounts from ${providerId}:`, e);
89
+ return json({ error: "Failed to fetch connected accounts" }, 500);
90
+ }
91
+ }
92
+
93
+ // POST /api/integrations/:provider/connect - Initiate connection (OAuth or API Key)
94
+ const connectMatch = path.match(/^\/api\/integrations\/([^/]+)\/connect$/);
95
+ if (connectMatch && method === "POST") {
96
+ const providerId = connectMatch[1];
97
+ const provider = getProvider(providerId);
98
+ if (!provider) {
99
+ return json({ error: `Unknown provider: ${providerId}` }, 404);
100
+ }
101
+
102
+ const apiKey = ProviderKeys.getDecrypted(providerId);
103
+ if (!apiKey) {
104
+ return json({ error: `${provider.name} API key not configured` }, 401);
105
+ }
106
+
107
+ try {
108
+ const body = await req.json();
109
+ const { appSlug, redirectUrl, credentials } = body;
110
+
111
+ if (!appSlug) {
112
+ return json({ error: "appSlug is required" }, 400);
113
+ }
114
+
115
+ // Use Apteva user ID as the entity ID
116
+ const userId = user?.id || "default";
117
+
118
+ // Default redirect URL back to our integrations page
119
+ const callbackUrl = redirectUrl || `http://localhost:${process.env.PORT || 4280}/mcp?tab=hosted&connected=${appSlug}`;
120
+
121
+ const result = await provider.initiateConnection(apiKey, userId, appSlug, callbackUrl, credentials);
122
+ return json(result);
123
+ } catch (e) {
124
+ console.error(`Failed to initiate connection for ${providerId}:`, e);
125
+ return json({ error: `Failed to initiate connection: ${e}` }, 500);
126
+ }
127
+ }
128
+
129
+ // GET /api/integrations/:provider/connection/:id - Check connection status
130
+ const connectionStatusMatch = path.match(/^\/api\/integrations\/([^/]+)\/connection\/([^/]+)$/);
131
+ if (connectionStatusMatch && method === "GET") {
132
+ const providerId = connectionStatusMatch[1];
133
+ const connectionId = connectionStatusMatch[2];
134
+ const provider = getProvider(providerId);
135
+ if (!provider) {
136
+ return json({ error: `Unknown provider: ${providerId}` }, 404);
137
+ }
138
+
139
+ const apiKey = ProviderKeys.getDecrypted(providerId);
140
+ if (!apiKey) {
141
+ return json({ error: `${provider.name} API key not configured` }, 401);
142
+ }
143
+
144
+ try {
145
+ const connection = await provider.getConnectionStatus(apiKey, connectionId);
146
+ if (!connection) {
147
+ return json({ error: "Connection not found" }, 404);
148
+ }
149
+ return json({ connection });
150
+ } catch (e) {
151
+ console.error(`Failed to get connection status:`, e);
152
+ return json({ error: "Failed to get connection status" }, 500);
153
+ }
154
+ }
155
+
156
+ // DELETE /api/integrations/:provider/connection/:id - Disconnect/revoke
157
+ if (connectionStatusMatch && method === "DELETE") {
158
+ const providerId = connectionStatusMatch[1];
159
+ const connectionId = connectionStatusMatch[2];
160
+ const provider = getProvider(providerId);
161
+ if (!provider) {
162
+ return json({ error: `Unknown provider: ${providerId}` }, 404);
163
+ }
164
+
165
+ const apiKey = ProviderKeys.getDecrypted(providerId);
166
+ if (!apiKey) {
167
+ return json({ error: `${provider.name} API key not configured` }, 401);
168
+ }
169
+
170
+ try {
171
+ const success = await provider.disconnect(apiKey, connectionId);
172
+ return json({ success });
173
+ } catch (e) {
174
+ console.error(`Failed to disconnect:`, e);
175
+ return json({ error: "Failed to disconnect" }, 500);
176
+ }
177
+ }
178
+
179
+ // ============ Composio-Specific Routes ============
180
+
181
+ // GET /api/integrations/composio/configs - List Composio MCP configs
182
+ if (path === "/api/integrations/composio/configs" && method === "GET") {
183
+ const url = new URL(req.url);
184
+ const projectId = url.searchParams.get("project_id") || null;
185
+ const apiKey = ProviderKeys.getDecryptedForProject("composio", projectId);
186
+ if (!apiKey) {
187
+ return json({ error: "Composio API key not configured", configs: [] }, 200);
188
+ }
189
+
190
+ try {
191
+ const res = await fetch("https://backend.composio.dev/api/v3/mcp/servers?limit=50", {
192
+ headers: {
193
+ "x-api-key": apiKey,
194
+ "Content-Type": "application/json",
195
+ },
196
+ });
197
+
198
+ if (!res.ok) {
199
+ const text = await res.text();
200
+ console.error("Composio API error:", res.status, text);
201
+ return json({ error: "Failed to fetch Composio configs" }, 500);
202
+ }
203
+
204
+ const data = await res.json();
205
+
206
+ // Transform to our format
207
+ const configs = (data.items || data.servers || []).map((item: any) => ({
208
+ id: item.id,
209
+ name: item.name || item.id,
210
+ toolkits: item.toolkits || item.apps || [],
211
+ toolsCount: item.toolsCount || item.tools?.length || 0,
212
+ createdAt: item.createdAt || item.created_at,
213
+ }));
214
+
215
+ return json({ configs });
216
+ } catch (e) {
217
+ console.error("Composio fetch error:", e);
218
+ return json({ error: "Failed to connect to Composio" }, 500);
219
+ }
220
+ }
221
+
222
+ // GET /api/integrations/composio/configs/:id - Get single Composio config details
223
+ const composioConfigMatch = path.match(/^\/api\/integrations\/composio\/configs\/([^/]+)$/);
224
+ if (composioConfigMatch && method === "GET") {
225
+ const configId = composioConfigMatch[1];
226
+ const url = new URL(req.url);
227
+ const projectId = url.searchParams.get("project_id") || null;
228
+ const apiKey = ProviderKeys.getDecryptedForProject("composio", projectId);
229
+ if (!apiKey) {
230
+ return json({ error: "Composio API key not configured" }, 401);
231
+ }
232
+
233
+ try {
234
+ const res = await fetch(`https://backend.composio.dev/api/v3/mcp/${configId}`, {
235
+ headers: {
236
+ "x-api-key": apiKey,
237
+ "Content-Type": "application/json",
238
+ },
239
+ });
240
+
241
+ if (!res.ok) {
242
+ return json({ error: "Config not found" }, 404);
243
+ }
244
+
245
+ const data = await res.json();
246
+ return json({
247
+ config: {
248
+ id: data.id,
249
+ name: data.name || data.id,
250
+ toolkits: data.toolkits || data.apps || [],
251
+ tools: data.tools || [],
252
+ },
253
+ });
254
+ } catch (e) {
255
+ return json({ error: "Failed to fetch config" }, 500);
256
+ }
257
+ }
258
+
259
+ // POST /api/integrations/composio/configs/:id/add - Add a Composio config as an MCP server
260
+ const composioAddMatch = path.match(/^\/api\/integrations\/composio\/configs\/([^/]+)\/add$/);
261
+ if (composioAddMatch && method === "POST") {
262
+ const configId = composioAddMatch[1];
263
+ const url = new URL(req.url);
264
+ const projectId = url.searchParams.get("project_id") || null;
265
+ const apiKey = ProviderKeys.getDecryptedForProject("composio", projectId);
266
+ if (!apiKey) {
267
+ return json({ error: "Composio API key not configured" }, 401);
268
+ }
269
+
270
+ try {
271
+ // Fetch config details from Composio to get the name and mcp_url
272
+ const res = await fetch(`https://backend.composio.dev/api/v3/mcp/${configId}`, {
273
+ headers: {
274
+ "x-api-key": apiKey,
275
+ "Content-Type": "application/json",
276
+ },
277
+ });
278
+
279
+ if (!res.ok) {
280
+ const errText = await res.text();
281
+ console.error("Failed to fetch Composio MCP config:", errText);
282
+ return json({ error: "Failed to fetch MCP config from Composio" }, 400);
283
+ }
284
+
285
+ const data = await res.json();
286
+ const configName = data.name || `composio-${configId.slice(0, 8)}`;
287
+ const mcpUrl = data.mcp_url;
288
+ const authConfigIds = data.auth_config_ids || [];
289
+ const serverInstanceCount = data.server_instance_count || 0;
290
+
291
+ if (!mcpUrl) {
292
+ return json({ error: "MCP config does not have a URL" }, 400);
293
+ }
294
+
295
+ // Get user_id from connected accounts for this auth config
296
+ const { createMcpServerInstance, getUserIdForAuthConfig } = await import("../../integrations/composio");
297
+ let userId: string | null = null;
298
+
299
+ if (authConfigIds.length > 0) {
300
+ userId = await getUserIdForAuthConfig(apiKey, authConfigIds[0]);
301
+
302
+ // Create server instance if none exists
303
+ if (serverInstanceCount === 0 && userId) {
304
+ const instance = await createMcpServerInstance(apiKey, configId, userId);
305
+ if (instance) {
306
+ console.log(`Created server instance for user ${userId} on server ${configId}`);
307
+ }
308
+ }
309
+ }
310
+
311
+ // Append user_id to mcp_url for authentication
312
+ const mcpUrlWithUser = userId
313
+ ? `${mcpUrl}?user_id=${encodeURIComponent(userId)}`
314
+ : mcpUrl;
315
+
316
+ // Check if already exists (match by config ID in URL)
317
+ const existing = McpServerDB.findAll().find(
318
+ s => s.source === "composio" && s.url?.includes(configId)
319
+ );
320
+ if (existing) {
321
+ return json({ server: existing, message: "Server already exists" });
322
+ }
323
+
324
+ // Create the MCP server entry with user_id in URL
325
+ const server = McpServerDB.create({
326
+ id: generateId(),
327
+ name: configName,
328
+ type: "http",
329
+ package: null,
330
+ command: null,
331
+ args: null,
332
+ pip_module: null,
333
+ env: {},
334
+ url: mcpUrlWithUser,
335
+ headers: { "x-api-key": apiKey },
336
+ source: "composio",
337
+ project_id: null,
338
+ });
339
+
340
+ return json({ server, message: "Server added successfully" });
341
+ } catch (e) {
342
+ console.error("Failed to add Composio config:", e);
343
+ return json({ error: "Failed to add Composio config" }, 500);
344
+ }
345
+ }
346
+
347
+ // POST /api/integrations/composio/configs - Create a new MCP config from connected app
348
+ if (path === "/api/integrations/composio/configs" && method === "POST") {
349
+ const url = new URL(req.url);
350
+ const projectId = url.searchParams.get("project_id") || null;
351
+ const apiKey = ProviderKeys.getDecryptedForProject("composio", projectId);
352
+ if (!apiKey) {
353
+ return json({ error: "Composio API key not configured" }, 401);
354
+ }
355
+
356
+ try {
357
+ const body = await req.json();
358
+ const { name, toolkitSlug, authConfigId } = body;
359
+
360
+ if (!name || !toolkitSlug) {
361
+ return json({ error: "name and toolkitSlug are required" }, 400);
362
+ }
363
+
364
+ // If authConfigId not provided, find it from the toolkit
365
+ let configId = authConfigId;
366
+ if (!configId) {
367
+ const { getAuthConfigForToolkit } = await import("../../integrations/composio");
368
+ configId = await getAuthConfigForToolkit(apiKey, toolkitSlug);
369
+ if (!configId) {
370
+ return json({ error: `No auth config found for ${toolkitSlug}. Make sure you have connected this app first.` }, 400);
371
+ }
372
+ }
373
+
374
+ // Create MCP server in Composio
375
+ const { createMcpServer, createMcpServerInstance, getUserIdForAuthConfig } = await import("../../integrations/composio");
376
+ const mcpServer = await createMcpServer(apiKey, name, [configId]);
377
+
378
+ if (!mcpServer) {
379
+ return json({ error: "Failed to create MCP config" }, 500);
380
+ }
381
+
382
+ // Create server instance for the user who has the connected account
383
+ const userId = await getUserIdForAuthConfig(apiKey, configId);
384
+ if (userId) {
385
+ const instance = await createMcpServerInstance(apiKey, mcpServer.id, userId);
386
+ if (!instance) {
387
+ console.warn(`Created MCP server but failed to create instance for user ${userId}`);
388
+ }
389
+ }
390
+
391
+ // Append user_id to mcp_url for authentication
392
+ const mcpUrlWithUser = userId
393
+ ? `${mcpServer.mcpUrl}?user_id=${encodeURIComponent(userId)}`
394
+ : mcpServer.mcpUrl;
395
+
396
+ return json({
397
+ config: {
398
+ id: mcpServer.id,
399
+ name: mcpServer.name,
400
+ toolkits: mcpServer.toolkits,
401
+ mcpUrl: mcpUrlWithUser,
402
+ allowedTools: mcpServer.allowedTools,
403
+ userId,
404
+ },
405
+ }, 201);
406
+ } catch (e: any) {
407
+ console.error("Failed to create Composio MCP config:", e);
408
+ return json({ error: e.message || "Failed to create MCP config" }, 500);
409
+ }
410
+ }
411
+
412
+ // DELETE /api/integrations/composio/configs/:id - Delete a Composio MCP config
413
+ if (composioConfigMatch && method === "DELETE") {
414
+ const configId = composioConfigMatch[1];
415
+ const url = new URL(req.url);
416
+ const projectId = url.searchParams.get("project_id") || null;
417
+ const apiKey = ProviderKeys.getDecryptedForProject("composio", projectId);
418
+ if (!apiKey) {
419
+ return json({ error: "Composio API key not configured" }, 401);
420
+ }
421
+
422
+ try {
423
+ const { deleteMcpServer } = await import("../../integrations/composio");
424
+ const success = await deleteMcpServer(apiKey, configId);
425
+ if (!success) {
426
+ return json({ error: "Failed to delete MCP config" }, 500);
427
+ }
428
+ return json({ success: true });
429
+ } catch (e) {
430
+ console.error("Failed to delete Composio config:", e);
431
+ return json({ error: "Failed to delete MCP config" }, 500);
432
+ }
433
+ }
434
+
435
+ // ============ AgentDojo-Specific Routes ============
436
+
437
+ // GET /api/integrations/agentdojo/configs - List AgentDojo MCP servers (configs)
438
+ if (path === "/api/integrations/agentdojo/configs" && method === "GET") {
439
+ const url = new URL(req.url);
440
+ const projectId = url.searchParams.get("project_id") || null;
441
+ const apiKey = ProviderKeys.getDecryptedForProject("agentdojo", projectId);
442
+ if (!apiKey) {
443
+ return json({ error: "AgentDojo API key not configured", configs: [] }, 200);
444
+ }
445
+
446
+ try {
447
+ const servers = await listAgentDojoServers(apiKey, true);
448
+ const configs = servers.map(s => ({
449
+ id: s.id,
450
+ name: s.name,
451
+ slug: s.slug,
452
+ toolkits: [], // Could be extracted from tools
453
+ toolsCount: s.tools?.length || 0,
454
+ mcpUrl: s.url,
455
+ createdAt: s.createdAt,
456
+ }));
457
+ return json({ configs });
458
+ } catch (e) {
459
+ console.error("AgentDojo fetch error:", e);
460
+ return json({ error: "Failed to connect to AgentDojo" }, 500);
461
+ }
462
+ }
463
+
464
+ // GET /api/integrations/agentdojo/configs/:id - Get single AgentDojo config details
465
+ const agentdojoConfigMatch = path.match(/^\/api\/integrations\/agentdojo\/configs\/([^/]+)$/);
466
+ if (agentdojoConfigMatch && method === "GET") {
467
+ const configId = agentdojoConfigMatch[1];
468
+ const url = new URL(req.url);
469
+ const projectId = url.searchParams.get("project_id") || null;
470
+ const apiKey = ProviderKeys.getDecryptedForProject("agentdojo", projectId);
471
+ if (!apiKey) {
472
+ return json({ error: "AgentDojo API key not configured" }, 401);
473
+ }
474
+
475
+ try {
476
+ const server = await getAgentDojoServer(apiKey, configId);
477
+ if (!server) {
478
+ return json({ error: "Config not found" }, 404);
479
+ }
480
+ return json({
481
+ config: {
482
+ id: server.id,
483
+ name: server.name,
484
+ slug: server.slug,
485
+ mcpUrl: server.url,
486
+ tools: server.tools || [],
487
+ },
488
+ });
489
+ } catch (e) {
490
+ return json({ error: "Failed to fetch config" }, 500);
491
+ }
492
+ }
493
+
494
+ // POST /api/integrations/agentdojo/configs/:id/add - Add an AgentDojo config as a local MCP server
495
+ const agentdojoAddMatch = path.match(/^\/api\/integrations\/agentdojo\/configs\/([^/]+)\/add$/);
496
+ if (agentdojoAddMatch && method === "POST") {
497
+ const configId = agentdojoAddMatch[1];
498
+ const url = new URL(req.url);
499
+ const projectId = url.searchParams.get("project_id") || null;
500
+ const apiKey = ProviderKeys.getDecryptedForProject("agentdojo", projectId);
501
+ if (!apiKey) {
502
+ return json({ error: "AgentDojo API key not configured" }, 401);
503
+ }
504
+
505
+ try {
506
+ const server = await getAgentDojoServer(apiKey, configId);
507
+ if (!server) {
508
+ return json({ error: "Config not found" }, 404);
509
+ }
510
+
511
+ // Check if already exists
512
+ const existing = McpServerDB.findAll().find(
513
+ s => s.source === "agentdojo" && (s.url?.includes(server.slug) || s.url?.includes(configId))
514
+ );
515
+ if (existing) {
516
+ return json({ server: existing, message: "Server already exists" });
517
+ }
518
+
519
+ // Create the MCP server entry
520
+ const mcpServer = McpServerDB.create({
521
+ id: generateId(),
522
+ name: server.name,
523
+ type: "http",
524
+ package: null,
525
+ command: null,
526
+ args: null,
527
+ pip_module: null,
528
+ env: {},
529
+ url: server.url,
530
+ headers: { "X-API-Key": apiKey },
531
+ source: "agentdojo",
532
+ project_id: projectId && projectId !== "unassigned" ? projectId : null,
533
+ });
534
+
535
+ return json({ server: mcpServer, message: "Server added successfully" });
536
+ } catch (e) {
537
+ console.error("Failed to add AgentDojo config:", e);
538
+ return json({ error: "Failed to add AgentDojo config" }, 500);
539
+ }
540
+ }
541
+
542
+ // POST /api/integrations/agentdojo/configs - Create a new MCP server from toolkit
543
+ if (path === "/api/integrations/agentdojo/configs" && method === "POST") {
544
+ const url = new URL(req.url);
545
+ const projectId = url.searchParams.get("project_id") || null;
546
+ const apiKey = ProviderKeys.getDecryptedForProject("agentdojo", projectId);
547
+ if (!apiKey) {
548
+ return json({ error: "AgentDojo API key not configured" }, 401);
549
+ }
550
+
551
+ try {
552
+ const body = await req.json();
553
+ const { name, toolkitSlug, toolkits } = body;
554
+
555
+ if (!name) {
556
+ return json({ error: "name is required" }, 400);
557
+ }
558
+
559
+ // Accept either toolkitSlug (single) or toolkits (array)
560
+ const toolkitList = toolkits || (toolkitSlug ? [toolkitSlug] : []);
561
+ if (toolkitList.length === 0) {
562
+ return json({ error: "toolkitSlug or toolkits is required" }, 400);
563
+ }
564
+
565
+ const server = await createAgentDojoServer(apiKey, name, toolkitList);
566
+ if (!server) {
567
+ return json({ error: "Failed to create MCP config" }, 500);
568
+ }
569
+
570
+ return json({
571
+ config: {
572
+ id: server.id,
573
+ name: server.name,
574
+ slug: server.slug,
575
+ mcpUrl: server.url,
576
+ tools: server.tools || [],
577
+ },
578
+ }, 201);
579
+ } catch (e: any) {
580
+ console.error("Failed to create AgentDojo MCP config:", e);
581
+ return json({ error: e.message || "Failed to create MCP config" }, 500);
582
+ }
583
+ }
584
+
585
+ // DELETE /api/integrations/agentdojo/configs/:id - Delete an AgentDojo MCP config
586
+ if (agentdojoConfigMatch && method === "DELETE") {
587
+ const configId = agentdojoConfigMatch[1];
588
+ const url = new URL(req.url);
589
+ const projectId = url.searchParams.get("project_id") || null;
590
+ const apiKey = ProviderKeys.getDecryptedForProject("agentdojo", projectId);
591
+ if (!apiKey) {
592
+ return json({ error: "AgentDojo API key not configured" }, 401);
593
+ }
594
+
595
+ try {
596
+ const success = await deleteAgentDojoServer(apiKey, configId);
597
+ if (!success) {
598
+ return json({ error: "Failed to delete MCP config" }, 500);
599
+ }
600
+ return json({ success: true });
601
+ } catch (e) {
602
+ console.error("Failed to delete AgentDojo config:", e);
603
+ return json({ error: "Failed to delete MCP config" }, 500);
604
+ }
605
+ }
606
+
607
+ return null;
608
+ }