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.
- package/dist/App.y11xqt9m.js +227 -0
- package/dist/index.html +1 -1
- package/dist/styles.css +1 -1
- package/package.json +1 -1
- package/src/db.ts +93 -19
- package/src/integrations/agentdojo.ts +350 -0
- package/src/openapi.ts +195 -0
- package/src/providers.ts +78 -7
- package/src/routes/api/agent-utils.ts +638 -0
- package/src/routes/api/agents.ts +743 -0
- package/src/routes/api/helpers.ts +12 -0
- package/src/routes/api/integrations.ts +608 -0
- package/src/routes/api/mcp.ts +377 -0
- package/src/routes/api/meta-agent.ts +145 -0
- package/src/routes/api/projects.ts +95 -0
- package/src/routes/api/providers.ts +269 -0
- package/src/routes/api/skills.ts +538 -0
- package/src/routes/api/system.ts +215 -0
- package/src/routes/api/telemetry.ts +142 -0
- package/src/routes/api/users.ts +148 -0
- package/src/routes/api.ts +32 -3474
- package/src/server.ts +1 -1
- package/src/web/components/api/ApiDocsPage.tsx +259 -0
- package/src/web/components/mcp/IntegrationsPanel.tsx +15 -8
- package/src/web/components/mcp/McpPage.tsx +458 -174
- package/src/web/components/settings/SettingsPage.tsx +275 -36
- package/src/web/components/skills/SkillsPage.tsx +330 -1
- package/src/web/components/tasks/TasksPage.tsx +187 -58
- package/src/web/context/TelemetryContext.tsx +14 -1
- package/src/web/hooks/useAgents.ts +9 -0
- package/src/web/types.ts +22 -4
- package/dist/App.mbp9atpm.js +0 -227
|
@@ -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
|
+
}
|