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.
@@ -273,7 +273,7 @@ export function McpPage() {
273
273
 
274
274
  {/* Hosted Services Tab */}
275
275
  {activeTab === "hosted" && (
276
- <HostedServices onServerAdded={fetchServers} />
276
+ <HostedServices onServerAdded={fetchServers} projectId={currentProjectId} />
277
277
  )}
278
278
 
279
279
  {/* Browse Registry Tab */}
@@ -932,8 +932,9 @@ interface ComposioConfig {
932
932
  createdAt?: string;
933
933
  }
934
934
 
935
- function HostedServices({ onServerAdded }: { onServerAdded?: () => void }) {
935
+ function HostedServices({ onServerAdded, projectId }: { onServerAdded?: () => void; projectId?: string | null }) {
936
936
  const { authFetch } = useAuth();
937
+ const [activeProvider, setActiveProvider] = useState<"composio" | "smithery" | "agentdojo">("composio");
937
938
  const [subTab, setSubTab] = useState<"configs" | "connect">("configs");
938
939
  const [composioConnected, setComposioConnected] = useState(false);
939
940
  const [smitheryConnected, setSmitheryConnected] = useState(false);
@@ -973,12 +974,22 @@ function HostedServices({ onServerAdded }: { onServerAdded?: () => void }) {
973
974
  const composio = providers.find((p: any) => p.id === "composio");
974
975
  const smithery = providers.find((p: any) => p.id === "smithery");
975
976
  const agentdojo = providers.find((p: any) => p.id === "agentdojo");
976
- setComposioConnected(composio?.hasKey || false);
977
- setSmitheryConnected(smithery?.hasKey || false);
978
- setAgentDojoConnected(agentdojo?.hasKey || false);
977
+ const composioHasKey = composio?.hasKey || false;
978
+ const smitheryHasKey = smithery?.hasKey || false;
979
+ const agentdojoHasKey = agentdojo?.hasKey || false;
979
980
 
980
- if (composio?.hasKey) {
981
+ setComposioConnected(composioHasKey);
982
+ setSmitheryConnected(smitheryHasKey);
983
+ setAgentDojoConnected(agentdojoHasKey);
984
+
985
+ // Set initial active provider to first connected one
986
+ if (composioHasKey) {
987
+ setActiveProvider("composio");
981
988
  fetchComposioConfigs();
989
+ } else if (smitheryHasKey) {
990
+ setActiveProvider("smithery");
991
+ } else if (agentdojoHasKey) {
992
+ setActiveProvider("agentdojo");
982
993
  }
983
994
  } catch (e) {
984
995
  console.error("Failed to fetch providers:", e);
@@ -989,7 +1000,8 @@ function HostedServices({ onServerAdded }: { onServerAdded?: () => void }) {
989
1000
  const fetchComposioConfigs = async () => {
990
1001
  setLoadingConfigs(true);
991
1002
  try {
992
- const res = await authFetch("/api/integrations/composio/configs");
1003
+ const projectParam = projectId && projectId !== "unassigned" ? `?project_id=${projectId}` : "";
1004
+ const res = await authFetch(`/api/integrations/composio/configs${projectParam}`);
993
1005
  const data = await res.json();
994
1006
  setComposioConfigs(data.configs || []);
995
1007
  } catch (e) {
@@ -1001,7 +1013,8 @@ function HostedServices({ onServerAdded }: { onServerAdded?: () => void }) {
1001
1013
  const addComposioConfig = async (configId: string) => {
1002
1014
  setAddingConfig(configId);
1003
1015
  try {
1004
- const res = await authFetch(`/api/integrations/composio/configs/${configId}/add`, {
1016
+ const projectParam = projectId && projectId !== "unassigned" ? `?project_id=${projectId}` : "";
1017
+ const res = await authFetch(`/api/integrations/composio/configs/${configId}/add${projectParam}`, {
1005
1018
  method: "POST",
1006
1019
  });
1007
1020
  if (res.ok) {
@@ -1031,6 +1044,7 @@ function HostedServices({ onServerAdded }: { onServerAdded?: () => void }) {
1031
1044
  }
1032
1045
 
1033
1046
  const hasAnyConnection = composioConnected || smitheryConnected || agentDojoConnected;
1047
+ const connectedCount = [composioConnected, smitheryConnected, agentDojoConnected].filter(Boolean).length;
1034
1048
 
1035
1049
  if (!hasAnyConnection) {
1036
1050
  return (
@@ -1053,210 +1067,255 @@ function HostedServices({ onServerAdded }: { onServerAdded?: () => void }) {
1053
1067
  <>
1054
1068
  {AlertDialog}
1055
1069
  <div className="space-y-6">
1056
- {/* Sub-tabs for Composio */}
1057
- {composioConnected && (
1070
+ {/* Provider Tabs - show when multiple providers are connected */}
1071
+ {connectedCount > 1 && (
1058
1072
  <div className="flex gap-1 bg-[#0a0a0a] border border-[#222] rounded-lg p-1 w-fit">
1059
- <button
1060
- onClick={() => setSubTab("configs")}
1061
- className={`px-4 py-2 rounded text-sm font-medium transition ${
1062
- subTab === "configs"
1063
- ? "bg-[#1a1a1a] text-white"
1064
- : "text-[#666] hover:text-[#888]"
1065
- }`}
1066
- >
1067
- MCP Configs
1068
- </button>
1069
- <button
1070
- onClick={() => setSubTab("connect")}
1071
- className={`px-4 py-2 rounded text-sm font-medium transition ${
1072
- subTab === "connect"
1073
- ? "bg-[#1a1a1a] text-white"
1074
- : "text-[#666] hover:text-[#888]"
1075
- }`}
1076
- >
1077
- Connect Apps
1078
- </button>
1079
- </div>
1080
- )}
1081
-
1082
- {/* Connect Apps Tab */}
1083
- {composioConnected && subTab === "connect" && (
1084
- <div>
1085
- <div className="flex items-center justify-between mb-4">
1086
- <div>
1087
- <h2 className="font-medium">Connect Apps via Composio</h2>
1088
- <p className="text-sm text-[#666] mt-1">
1089
- Connect your accounts to enable tools in MCP configs
1090
- </p>
1091
- </div>
1092
- </div>
1093
- <IntegrationsPanel
1094
- providerId="composio"
1095
- onConnectionComplete={() => {
1096
- // Refresh configs after connecting an app
1097
- fetchComposioConfigs();
1098
- }}
1099
- />
1073
+ {composioConnected && (
1074
+ <button
1075
+ onClick={() => { setActiveProvider("composio"); setSubTab("configs"); }}
1076
+ className={`px-4 py-2 rounded text-sm font-medium transition flex items-center gap-2 ${
1077
+ activeProvider === "composio"
1078
+ ? "bg-[#1a1a1a] text-white"
1079
+ : "text-[#666] hover:text-[#888]"
1080
+ }`}
1081
+ >
1082
+ <span className="w-2 h-2 rounded-full bg-purple-500" />
1083
+ Composio
1084
+ </button>
1085
+ )}
1086
+ {smitheryConnected && (
1087
+ <button
1088
+ onClick={() => setActiveProvider("smithery")}
1089
+ className={`px-4 py-2 rounded text-sm font-medium transition flex items-center gap-2 ${
1090
+ activeProvider === "smithery"
1091
+ ? "bg-[#1a1a1a] text-white"
1092
+ : "text-[#666] hover:text-[#888]"
1093
+ }`}
1094
+ >
1095
+ <span className="w-2 h-2 rounded-full bg-blue-500" />
1096
+ Smithery
1097
+ </button>
1098
+ )}
1099
+ {agentDojoConnected && (
1100
+ <button
1101
+ onClick={() => setActiveProvider("agentdojo")}
1102
+ className={`px-4 py-2 rounded text-sm font-medium transition flex items-center gap-2 ${
1103
+ activeProvider === "agentdojo"
1104
+ ? "bg-[#1a1a1a] text-white"
1105
+ : "text-[#666] hover:text-[#888]"
1106
+ }`}
1107
+ >
1108
+ <span className="w-2 h-2 rounded-full bg-green-500" />
1109
+ AgentDojo
1110
+ </button>
1111
+ )}
1100
1112
  </div>
1101
1113
  )}
1102
1114
 
1103
- {/* MCP Configs Tab */}
1104
- {composioConnected && subTab === "configs" && (
1105
- <div>
1106
- <div className="flex items-center justify-between mb-3">
1107
- <div className="flex items-center gap-2">
1108
- <h2 className="font-medium">Composio MCP Configs</h2>
1109
- <span className="text-xs text-green-400">Connected</span>
1110
- </div>
1111
- <div className="flex items-center gap-3">
1115
+ {/* Composio Content */}
1116
+ {composioConnected && (connectedCount === 1 || activeProvider === "composio") && (
1117
+ <>
1118
+ {/* Sub-tabs for Composio */}
1119
+ <div className="flex items-center justify-between">
1120
+ <div className="flex gap-1 bg-[#0a0a0a] border border-[#222] rounded-lg p-1">
1112
1121
  <button
1113
- onClick={fetchComposioConfigs}
1114
- disabled={loadingConfigs}
1115
- className="text-xs text-[#666] hover:text-[#888] transition"
1122
+ onClick={() => setSubTab("configs")}
1123
+ className={`px-4 py-2 rounded text-sm font-medium transition ${
1124
+ subTab === "configs"
1125
+ ? "bg-[#1a1a1a] text-white"
1126
+ : "text-[#666] hover:text-[#888]"
1127
+ }`}
1116
1128
  >
1117
- {loadingConfigs ? "Loading..." : "Refresh"}
1129
+ MCP Configs
1118
1130
  </button>
1119
- <a
1120
- href="https://app.composio.dev/mcp_configs"
1121
- target="_blank"
1122
- rel="noopener noreferrer"
1123
- className="text-xs text-[#666] hover:text-[#f97316] transition"
1131
+ <button
1132
+ onClick={() => setSubTab("connect")}
1133
+ className={`px-4 py-2 rounded text-sm font-medium transition ${
1134
+ subTab === "connect"
1135
+ ? "bg-[#1a1a1a] text-white"
1136
+ : "text-[#666] hover:text-[#888]"
1137
+ }`}
1124
1138
  >
1125
- Create Config →
1126
- </a>
1139
+ Connect Apps
1140
+ </button>
1127
1141
  </div>
1142
+ {connectedCount === 1 && (
1143
+ <div className="flex items-center gap-2 text-xs text-[#666]">
1144
+ <span className="w-2 h-2 rounded-full bg-purple-500" />
1145
+ Composio
1146
+ <span className="text-green-400">Connected</span>
1147
+ </div>
1148
+ )}
1128
1149
  </div>
1129
1150
 
1130
- {loadingConfigs ? (
1131
- <div className="text-center py-6 text-[#666]">Loading configs...</div>
1132
- ) : composioConfigs.length === 0 ? (
1133
- <div className="bg-[#111] border border-[#1a1a1a] rounded-lg p-4 text-center">
1134
- <p className="text-sm text-[#666]">No MCP configs found</p>
1135
- <p className="text-xs text-[#555] mt-2">
1136
- First <button onClick={() => setSubTab("connect")} className="text-[#f97316] hover:text-[#fb923c]">connect some apps</button>, then create a config.
1151
+ {/* Connect Apps Tab */}
1152
+ {subTab === "connect" && (
1153
+ <div>
1154
+ <p className="text-sm text-[#666] mb-4">
1155
+ Connect your accounts to enable tools in MCP configs
1137
1156
  </p>
1138
- <a
1139
- href="https://app.composio.dev/mcp_configs"
1140
- target="_blank"
1141
- rel="noopener noreferrer"
1142
- className="text-xs text-[#f97316] hover:text-[#fb923c] mt-2 inline-block"
1143
- >
1144
- Create in Composio →
1145
- </a>
1157
+ <IntegrationsPanel
1158
+ providerId="composio"
1159
+ projectId={projectId}
1160
+ onConnectionComplete={() => {
1161
+ // Refresh configs after connecting an app
1162
+ fetchComposioConfigs();
1163
+ }}
1164
+ />
1146
1165
  </div>
1147
- ) : (
1148
- <div className="space-y-2">
1149
- {composioConfigs.map((config) => {
1150
- const added = isConfigAdded(config.id);
1151
- const isAdding = addingConfig === config.id;
1152
- return (
1153
- <div
1154
- key={config.id}
1155
- className={`bg-[#111] border rounded-lg p-3 transition flex items-center justify-between ${
1156
- added ? "border-green-500/30" : "border-[#1a1a1a] hover:border-[#333]"
1157
- }`}
1166
+ )}
1167
+
1168
+ {/* MCP Configs Tab */}
1169
+ {subTab === "configs" && (
1170
+ <div>
1171
+ <div className="flex items-center justify-between mb-3">
1172
+ <p className="text-sm text-[#666]">
1173
+ Your MCP configs from Composio
1174
+ </p>
1175
+ <div className="flex items-center gap-3">
1176
+ <button
1177
+ onClick={fetchComposioConfigs}
1178
+ disabled={loadingConfigs}
1179
+ className="text-xs text-[#666] hover:text-[#888] transition"
1158
1180
  >
1159
- <div className="flex-1 min-w-0">
1160
- <div className="flex items-center gap-2">
1161
- <span className="font-medium text-sm">{config.name}</span>
1162
- <span className="text-xs text-[#555]">{config.toolsCount} tools</span>
1163
- {added && (
1164
- <span className="text-xs text-green-400">Added</span>
1165
- )}
1166
- </div>
1167
- {config.toolkits.length > 0 && (
1168
- <div className="flex flex-wrap gap-1 mt-1">
1169
- {config.toolkits.slice(0, 4).map((toolkit) => (
1170
- <span
1171
- key={toolkit}
1172
- className="text-xs bg-[#1a1a1a] text-[#666] px-1.5 py-0.5 rounded"
1181
+ {loadingConfigs ? "Loading..." : "Refresh"}
1182
+ </button>
1183
+ <a
1184
+ href="https://app.composio.dev/mcp_configs"
1185
+ target="_blank"
1186
+ rel="noopener noreferrer"
1187
+ className="text-xs text-[#666] hover:text-[#f97316] transition"
1188
+ >
1189
+ Create Config
1190
+ </a>
1191
+ </div>
1192
+ </div>
1193
+
1194
+ {loadingConfigs ? (
1195
+ <div className="text-center py-6 text-[#666]">Loading configs...</div>
1196
+ ) : composioConfigs.length === 0 ? (
1197
+ <div className="bg-[#111] border border-[#1a1a1a] rounded-lg p-4 text-center">
1198
+ <p className="text-sm text-[#666]">No MCP configs found</p>
1199
+ <p className="text-xs text-[#555] mt-2">
1200
+ First <button onClick={() => setSubTab("connect")} className="text-[#f97316] hover:text-[#fb923c]">connect some apps</button>, then create a config.
1201
+ </p>
1202
+ <a
1203
+ href="https://app.composio.dev/mcp_configs"
1204
+ target="_blank"
1205
+ rel="noopener noreferrer"
1206
+ className="text-xs text-[#f97316] hover:text-[#fb923c] mt-2 inline-block"
1207
+ >
1208
+ Create in Composio →
1209
+ </a>
1210
+ </div>
1211
+ ) : (
1212
+ <div className="space-y-2">
1213
+ {composioConfigs.map((config) => {
1214
+ const added = isConfigAdded(config.id);
1215
+ const isAdding = addingConfig === config.id;
1216
+ return (
1217
+ <div
1218
+ key={config.id}
1219
+ className={`bg-[#111] border rounded-lg p-3 transition flex items-center justify-between ${
1220
+ added ? "border-green-500/30" : "border-[#1a1a1a] hover:border-[#333]"
1221
+ }`}
1222
+ >
1223
+ <div className="flex-1 min-w-0">
1224
+ <div className="flex items-center gap-2">
1225
+ <span className="font-medium text-sm">{config.name}</span>
1226
+ <span className="text-xs text-[#555]">{config.toolsCount} tools</span>
1227
+ {added && (
1228
+ <span className="text-xs text-green-400">Added</span>
1229
+ )}
1230
+ </div>
1231
+ {config.toolkits.length > 0 && (
1232
+ <div className="flex flex-wrap gap-1 mt-1">
1233
+ {config.toolkits.slice(0, 4).map((toolkit) => (
1234
+ <span
1235
+ key={toolkit}
1236
+ className="text-xs bg-[#1a1a1a] text-[#666] px-1.5 py-0.5 rounded"
1237
+ >
1238
+ {toolkit}
1239
+ </span>
1240
+ ))}
1241
+ {config.toolkits.length > 4 && (
1242
+ <span className="text-xs text-[#555]">+{config.toolkits.length - 4}</span>
1243
+ )}
1244
+ </div>
1245
+ )}
1246
+ </div>
1247
+ <div className="flex items-center gap-2 ml-3">
1248
+ {added ? (
1249
+ <span className="text-xs text-[#555] px-2 py-1">In Servers</span>
1250
+ ) : (
1251
+ <button
1252
+ onClick={() => addComposioConfig(config.id)}
1253
+ disabled={isAdding}
1254
+ className="text-xs bg-[#f97316] hover:bg-[#fb923c] text-black px-3 py-1 rounded font-medium transition disabled:opacity-50"
1173
1255
  >
1174
- {toolkit}
1175
- </span>
1176
- ))}
1177
- {config.toolkits.length > 4 && (
1178
- <span className="text-xs text-[#555]">+{config.toolkits.length - 4}</span>
1256
+ {isAdding ? "Adding..." : "Add"}
1257
+ </button>
1179
1258
  )}
1259
+ <a
1260
+ href={`https://app.composio.dev/mcp_configs/${config.id}`}
1261
+ target="_blank"
1262
+ rel="noopener noreferrer"
1263
+ className="text-xs text-[#666] hover:text-[#888] transition"
1264
+ >
1265
+ Edit
1266
+ </a>
1180
1267
  </div>
1181
- )}
1182
- </div>
1183
- <div className="flex items-center gap-2 ml-3">
1184
- {added ? (
1185
- <span className="text-xs text-[#555] px-2 py-1">In Servers</span>
1186
- ) : (
1187
- <button
1188
- onClick={() => addComposioConfig(config.id)}
1189
- disabled={isAdding}
1190
- className="text-xs bg-[#f97316] hover:bg-[#fb923c] text-black px-3 py-1 rounded font-medium transition disabled:opacity-50"
1191
- >
1192
- {isAdding ? "Adding..." : "Add"}
1193
- </button>
1194
- )}
1195
- <a
1196
- href={`https://app.composio.dev/mcp_configs/${config.id}`}
1197
- target="_blank"
1198
- rel="noopener noreferrer"
1199
- className="text-xs text-[#666] hover:text-[#888] transition"
1200
- >
1201
- Edit
1202
- </a>
1203
- </div>
1204
- </div>
1205
- );
1206
- })}
1268
+ </div>
1269
+ );
1270
+ })}
1271
+ </div>
1272
+ )}
1207
1273
  </div>
1208
1274
  )}
1209
- </div>
1275
+ </>
1210
1276
  )}
1211
1277
 
1212
- {/* Smithery - placeholder for when we have API support */}
1213
- {smitheryConnected && (
1278
+ {/* Smithery Content */}
1279
+ {smitheryConnected && (connectedCount === 1 || activeProvider === "smithery") && (
1214
1280
  <div>
1215
- <div className="flex items-center justify-between mb-3">
1216
- <div className="flex items-center gap-2">
1217
- <h2 className="font-medium">Smithery</h2>
1218
- <span className="text-xs text-green-400">Connected</span>
1281
+ {connectedCount === 1 && (
1282
+ <div className="flex items-center gap-2 text-xs text-[#666] mb-4">
1283
+ <span className="w-2 h-2 rounded-full bg-blue-500" />
1284
+ Smithery
1285
+ <span className="text-green-400">Connected</span>
1219
1286
  </div>
1287
+ )}
1288
+ <div className="flex items-center justify-between mb-3">
1289
+ <p className="text-sm text-[#666]">
1290
+ Add MCP servers from the Smithery registry
1291
+ </p>
1220
1292
  <a
1221
1293
  href="https://smithery.ai/servers"
1222
1294
  target="_blank"
1223
1295
  rel="noopener noreferrer"
1224
1296
  className="text-xs text-[#666] hover:text-[#f97316] transition"
1225
1297
  >
1226
- View Servers
1298
+ Browse Smithery
1227
1299
  </a>
1228
1300
  </div>
1229
1301
  <div className="bg-[#111] border border-[#1a1a1a] rounded-lg p-4 text-center">
1230
1302
  <p className="text-sm text-[#666]">
1231
- Smithery servers can be added from the Registry tab.
1303
+ Smithery servers can be added from the <strong>Browse Registry</strong> tab.
1304
+ </p>
1305
+ <p className="text-xs text-[#555] mt-2">
1306
+ Your API key will be used automatically when adding Smithery servers.
1232
1307
  </p>
1233
1308
  </div>
1234
1309
  </div>
1235
1310
  )}
1236
1311
 
1237
- {/* AgentDojo - hosted MCP tools */}
1238
- {agentDojoConnected && (
1239
- <div>
1240
- <div className="flex items-center justify-between mb-3">
1241
- <div className="flex items-center gap-2">
1242
- <h2 className="font-medium">AgentDojo</h2>
1243
- <span className="text-xs text-green-400">Connected</span>
1244
- </div>
1245
- <a
1246
- href="https://agentdojo.com/tools"
1247
- target="_blank"
1248
- rel="noopener noreferrer"
1249
- className="text-xs text-[#666] hover:text-[#f97316] transition"
1250
- >
1251
- Browse Tools →
1252
- </a>
1253
- </div>
1254
- <div className="bg-[#111] border border-[#1a1a1a] rounded-lg p-4 text-center">
1255
- <p className="text-sm text-[#666]">
1256
- AgentDojo integration coming soon. Browse available tools on their platform.
1257
- </p>
1258
- </div>
1259
- </div>
1312
+ {/* AgentDojo Content */}
1313
+ {agentDojoConnected && (connectedCount === 1 || activeProvider === "agentdojo") && (
1314
+ <AgentDojoContent
1315
+ projectId={projectId}
1316
+ onServerAdded={onServerAdded}
1317
+ showProviderBadge={connectedCount === 1}
1318
+ />
1260
1319
  )}
1261
1320
 
1262
1321
  <div className="p-3 bg-[#0a0a0a] border border-[#222] rounded text-xs text-[#666]">
@@ -1269,6 +1328,231 @@ function HostedServices({ onServerAdded }: { onServerAdded?: () => void }) {
1269
1328
  );
1270
1329
  }
1271
1330
 
1331
+ // AgentDojo Content Component
1332
+ interface AgentDojoConfig {
1333
+ id: string;
1334
+ name: string;
1335
+ slug: string;
1336
+ toolkits: string[];
1337
+ toolsCount: number;
1338
+ mcpUrl: string;
1339
+ createdAt?: string;
1340
+ }
1341
+
1342
+ function AgentDojoContent({
1343
+ projectId,
1344
+ onServerAdded,
1345
+ showProviderBadge,
1346
+ }: {
1347
+ projectId?: string | null;
1348
+ onServerAdded?: () => void;
1349
+ showProviderBadge?: boolean;
1350
+ }) {
1351
+ const { authFetch } = useAuth();
1352
+ const [subTab, setSubTab] = useState<"configs" | "toolkits">("configs");
1353
+ const [configs, setConfigs] = useState<AgentDojoConfig[]>([]);
1354
+ const [addedServers, setAddedServers] = useState<Set<string>>(new Set());
1355
+ const [loadingConfigs, setLoadingConfigs] = useState(false);
1356
+ const [addingConfig, setAddingConfig] = useState<string | null>(null);
1357
+ const { alert, AlertDialog } = useAlert();
1358
+
1359
+ const fetchConfigs = async () => {
1360
+ setLoadingConfigs(true);
1361
+ try {
1362
+ const projectParam = projectId && projectId !== "unassigned" ? `?project_id=${projectId}` : "";
1363
+ const [configsRes, serversRes] = await Promise.all([
1364
+ authFetch(`/api/integrations/agentdojo/configs${projectParam}`),
1365
+ authFetch("/api/mcp/servers"),
1366
+ ]);
1367
+ const configsData = await configsRes.json();
1368
+ const serversData = await serversRes.json();
1369
+
1370
+ setConfigs(configsData.configs || []);
1371
+
1372
+ // Track which configs are already added as local servers
1373
+ const agentdojoServerIds = new Set(
1374
+ (serversData.servers || [])
1375
+ .filter((s: any) => s.source === "agentdojo")
1376
+ .map((s: any) => {
1377
+ // Extract config ID from URL or match by name
1378
+ const match = s.url?.match(/\/mcp\/([^/?]+)/);
1379
+ return match ? match[1] : s.name;
1380
+ })
1381
+ );
1382
+ setAddedServers(agentdojoServerIds);
1383
+ } catch (e) {
1384
+ console.error("Failed to fetch AgentDojo configs:", e);
1385
+ }
1386
+ setLoadingConfigs(false);
1387
+ };
1388
+
1389
+ const addConfig = async (configId: string) => {
1390
+ setAddingConfig(configId);
1391
+ try {
1392
+ const projectParam = projectId && projectId !== "unassigned" ? `?project_id=${projectId}` : "";
1393
+ const res = await authFetch(`/api/integrations/agentdojo/configs/${configId}/add${projectParam}`, {
1394
+ method: "POST",
1395
+ });
1396
+ if (res.ok) {
1397
+ const config = configs.find(c => c.id === configId);
1398
+ setAddedServers(prev => new Set([...prev, config?.slug || configId]));
1399
+ onServerAdded?.();
1400
+ } else {
1401
+ const data = await res.json();
1402
+ await alert(data.error || "Failed to add config", { title: "Error", variant: "error" });
1403
+ }
1404
+ } catch (e) {
1405
+ console.error("Failed to add config:", e);
1406
+ }
1407
+ setAddingConfig(null);
1408
+ };
1409
+
1410
+ const isConfigAdded = (config: AgentDojoConfig) => {
1411
+ return addedServers.has(config.slug) || addedServers.has(config.id) || addedServers.has(config.name);
1412
+ };
1413
+
1414
+ useEffect(() => {
1415
+ fetchConfigs();
1416
+ }, [authFetch, projectId]);
1417
+
1418
+ return (
1419
+ <>
1420
+ {AlertDialog}
1421
+ <div>
1422
+ {showProviderBadge && (
1423
+ <div className="flex items-center gap-2 text-xs text-[#666] mb-4">
1424
+ <span className="w-2 h-2 rounded-full bg-green-500" />
1425
+ AgentDojo
1426
+ <span className="text-green-400">Connected</span>
1427
+ </div>
1428
+ )}
1429
+
1430
+ {/* Sub-tabs */}
1431
+ <div className="flex items-center justify-between mb-4">
1432
+ <div className="flex gap-1 bg-[#0a0a0a] border border-[#222] rounded-lg p-1">
1433
+ <button
1434
+ onClick={() => setSubTab("configs")}
1435
+ className={`px-4 py-2 rounded text-sm font-medium transition ${
1436
+ subTab === "configs"
1437
+ ? "bg-[#1a1a1a] text-white"
1438
+ : "text-[#666] hover:text-[#888]"
1439
+ }`}
1440
+ >
1441
+ MCP Servers
1442
+ </button>
1443
+ <button
1444
+ onClick={() => setSubTab("toolkits")}
1445
+ className={`px-4 py-2 rounded text-sm font-medium transition ${
1446
+ subTab === "toolkits"
1447
+ ? "bg-[#1a1a1a] text-white"
1448
+ : "text-[#666] hover:text-[#888]"
1449
+ }`}
1450
+ >
1451
+ Browse Toolkits
1452
+ </button>
1453
+ </div>
1454
+ </div>
1455
+
1456
+ {/* MCP Servers Tab */}
1457
+ {subTab === "configs" && (
1458
+ <div>
1459
+ <div className="flex items-center justify-between mb-3">
1460
+ <p className="text-sm text-[#666]">
1461
+ Your MCP servers from AgentDojo
1462
+ </p>
1463
+ <button
1464
+ onClick={fetchConfigs}
1465
+ disabled={loadingConfigs}
1466
+ className="text-xs text-[#666] hover:text-[#888] transition"
1467
+ >
1468
+ {loadingConfigs ? "Loading..." : "Refresh"}
1469
+ </button>
1470
+ </div>
1471
+
1472
+ {loadingConfigs ? (
1473
+ <div className="text-center py-6 text-[#666]">Loading servers...</div>
1474
+ ) : configs.length === 0 ? (
1475
+ <div className="bg-[#111] border border-[#1a1a1a] rounded-lg p-4 text-center">
1476
+ <p className="text-sm text-[#666]">No MCP servers found</p>
1477
+ <p className="text-xs text-[#555] mt-2">
1478
+ <button onClick={() => setSubTab("toolkits")} className="text-[#f97316] hover:text-[#fb923c]">
1479
+ Browse toolkits
1480
+ </button>
1481
+ {" "}to create a new MCP server.
1482
+ </p>
1483
+ </div>
1484
+ ) : (
1485
+ <div className="space-y-2">
1486
+ {configs.map((config) => {
1487
+ const added = isConfigAdded(config);
1488
+ const isAdding = addingConfig === config.id;
1489
+ return (
1490
+ <div
1491
+ key={config.id}
1492
+ className={`bg-[#111] border rounded-lg p-3 transition flex items-center justify-between ${
1493
+ added ? "border-green-500/30" : "border-[#1a1a1a] hover:border-[#333]"
1494
+ }`}
1495
+ >
1496
+ <div className="flex-1 min-w-0">
1497
+ <div className="flex items-center gap-2">
1498
+ <span className="font-medium text-sm">{config.name}</span>
1499
+ <span className="text-xs text-[#555]">{config.toolsCount} tools</span>
1500
+ {added && (
1501
+ <span className="text-xs text-green-400">Added</span>
1502
+ )}
1503
+ </div>
1504
+ {config.mcpUrl && (
1505
+ <code className="text-xs text-[#555] mt-1 block truncate">
1506
+ {config.mcpUrl}
1507
+ </code>
1508
+ )}
1509
+ {!config.mcpUrl && config.slug && (
1510
+ <code className="text-xs text-[#555] mt-1 block truncate">
1511
+ {config.slug}
1512
+ </code>
1513
+ )}
1514
+ </div>
1515
+ <div className="flex items-center gap-2 ml-3">
1516
+ {added ? (
1517
+ <span className="text-xs text-[#555] px-2 py-1">In Servers</span>
1518
+ ) : (
1519
+ <button
1520
+ onClick={() => addConfig(config.id)}
1521
+ disabled={isAdding}
1522
+ className="text-xs bg-[#f97316] hover:bg-[#fb923c] text-black px-3 py-1 rounded font-medium transition disabled:opacity-50"
1523
+ >
1524
+ {isAdding ? "Adding..." : "Add"}
1525
+ </button>
1526
+ )}
1527
+ </div>
1528
+ </div>
1529
+ );
1530
+ })}
1531
+ </div>
1532
+ )}
1533
+ </div>
1534
+ )}
1535
+
1536
+ {/* Browse Toolkits Tab */}
1537
+ {subTab === "toolkits" && (
1538
+ <div>
1539
+ <p className="text-sm text-[#666] mb-4">
1540
+ Browse available toolkits and create MCP servers
1541
+ </p>
1542
+ <IntegrationsPanel
1543
+ providerId="agentdojo"
1544
+ projectId={projectId}
1545
+ onConnectionComplete={() => {
1546
+ fetchConfigs();
1547
+ }}
1548
+ />
1549
+ </div>
1550
+ )}
1551
+ </div>
1552
+ </>
1553
+ );
1554
+ }
1555
+
1272
1556
  // Parse command and extract credential placeholders
1273
1557
  function parseCommandForCredentials(cmd: string): {
1274
1558
  cleanCommand: string;