apteva 0.4.57 → 0.7.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/README.md +216 -54
- package/cli.js +35 -0
- package/install.js +92 -0
- package/package.json +12 -79
- package/LICENSE +0 -63
- package/bin/apteva.js +0 -196
- package/dist/ActivityPage.kxzzb4yc.js +0 -3
- package/dist/ApiDocsPage.zq998hbm.js +0 -4
- package/dist/App.55rea8mn.js +0 -61
- package/dist/App.5ywb23z4.js +0 -53
- package/dist/App.6thds120.js +0 -4
- package/dist/App.9tctxzqm.js +0 -8
- package/dist/App.a8r8ttaz.js +0 -4
- package/dist/App.agsv5bje.js +0 -4
- package/dist/App.cepapqmx.js +0 -4
- package/dist/App.dp041gb3.js +0 -221
- package/dist/App.fds72zb5.js +0 -4
- package/dist/App.fg9qj2dq.js +0 -4
- package/dist/App.ndfejbm9.js +0 -4
- package/dist/App.nxmfmq1h.js +0 -13
- package/dist/App.qdfyt8ba.js +0 -4
- package/dist/App.x2d0ygt6.js +0 -4
- package/dist/App.yt9p4nr3.js +0 -20
- package/dist/App.zn4mw16t.js +0 -1
- package/dist/ConnectionsPage.8r96ryw7.js +0 -3
- package/dist/McpPage.3cwh0gnd.js +0 -3
- package/dist/SettingsPage.ykgdh5ev.js +0 -3
- package/dist/SkillsPage.4np1s65b.js +0 -3
- package/dist/TasksPage.4g08t7p6.js +0 -3
- package/dist/TelemetryPage.72w9pwcp.js +0 -3
- package/dist/TestsPage.z4fk3r7r.js +0 -3
- package/dist/ThreadsPage.63tcajeh.js +0 -3
- package/dist/apteva-kit.css +0 -1
- package/dist/icon.png +0 -0
- package/dist/index.html +0 -16
- package/dist/styles.css +0 -1
- package/scripts/postinstall.mjs +0 -102
- package/src/auth/index.ts +0 -394
- package/src/auth/middleware.ts +0 -213
- package/src/binary.ts +0 -536
- package/src/channels/index.ts +0 -40
- package/src/channels/telegram.ts +0 -311
- package/src/crypto.ts +0 -301
- package/src/db-tests.ts +0 -174
- package/src/db.ts +0 -3133
- package/src/integrations/agentdojo.ts +0 -559
- package/src/integrations/composio.ts +0 -437
- package/src/integrations/index.ts +0 -87
- package/src/integrations/skillsmp.ts +0 -318
- package/src/mcp-client.ts +0 -605
- package/src/mcp-handler.ts +0 -394
- package/src/mcp-platform.ts +0 -2403
- package/src/openapi.ts +0 -2410
- package/src/providers.ts +0 -597
- package/src/routes/api/agent-utils.ts +0 -890
- package/src/routes/api/agents.ts +0 -916
- package/src/routes/api/api-keys.ts +0 -95
- package/src/routes/api/channels.ts +0 -182
- package/src/routes/api/helpers.ts +0 -12
- package/src/routes/api/integrations.ts +0 -639
- package/src/routes/api/mcp.ts +0 -574
- package/src/routes/api/meta-agent.ts +0 -195
- package/src/routes/api/projects.ts +0 -112
- package/src/routes/api/providers.ts +0 -424
- package/src/routes/api/skills.ts +0 -537
- package/src/routes/api/system.ts +0 -333
- package/src/routes/api/telemetry.ts +0 -203
- package/src/routes/api/tests.ts +0 -148
- package/src/routes/api/triggers.ts +0 -518
- package/src/routes/api/users.ts +0 -148
- package/src/routes/api/webhooks.ts +0 -171
- package/src/routes/api.ts +0 -53
- package/src/routes/auth.ts +0 -251
- package/src/routes/share.ts +0 -86
- package/src/routes/static.ts +0 -131
- package/src/server.ts +0 -642
- package/src/test-runner.ts +0 -598
- package/src/triggers/agentdojo.ts +0 -253
- package/src/triggers/composio.ts +0 -264
- package/src/triggers/index.ts +0 -71
- package/src/tui/AgentList.tsx +0 -145
- package/src/tui/App.tsx +0 -102
- package/src/tui/Login.tsx +0 -104
- package/src/tui/api.ts +0 -72
- package/src/tui/index.tsx +0 -7
- package/src/web/App.tsx +0 -455
- package/src/web/components/activity/ActivityPage.tsx +0 -314
- package/src/web/components/activity/index.ts +0 -1
- package/src/web/components/agents/AgentCard.tsx +0 -189
- package/src/web/components/agents/AgentPanel.tsx +0 -2244
- package/src/web/components/agents/AgentsView.tsx +0 -180
- package/src/web/components/agents/CreateAgentModal.tsx +0 -475
- package/src/web/components/agents/index.ts +0 -4
- package/src/web/components/api/ApiDocsPage.tsx +0 -842
- package/src/web/components/auth/CreateAccountStep.tsx +0 -176
- package/src/web/components/auth/LoginPage.tsx +0 -91
- package/src/web/components/auth/index.ts +0 -2
- package/src/web/components/common/Icons.tsx +0 -250
- package/src/web/components/common/LoadingSpinner.tsx +0 -44
- package/src/web/components/common/Modal.tsx +0 -199
- package/src/web/components/common/Select.tsx +0 -97
- package/src/web/components/common/index.ts +0 -20
- package/src/web/components/connections/ConnectionsPage.tsx +0 -54
- package/src/web/components/connections/IntegrationsTab.tsx +0 -170
- package/src/web/components/connections/OverviewTab.tsx +0 -137
- package/src/web/components/connections/TriggersTab.tsx +0 -1346
- package/src/web/components/dashboard/Dashboard.tsx +0 -572
- package/src/web/components/dashboard/index.ts +0 -1
- package/src/web/components/index.ts +0 -21
- package/src/web/components/layout/ErrorBanner.tsx +0 -18
- package/src/web/components/layout/Header.tsx +0 -332
- package/src/web/components/layout/Sidebar.tsx +0 -231
- package/src/web/components/layout/index.ts +0 -3
- package/src/web/components/mcp/IntegrationsPanel.tsx +0 -857
- package/src/web/components/mcp/McpPage.tsx +0 -2515
- package/src/web/components/mcp/index.ts +0 -1
- package/src/web/components/meta-agent/MetaAgent.tsx +0 -245
- package/src/web/components/onboarding/OnboardingWizard.tsx +0 -404
- package/src/web/components/onboarding/index.ts +0 -1
- package/src/web/components/settings/SettingsPage.tsx +0 -2776
- package/src/web/components/settings/index.ts +0 -1
- package/src/web/components/skills/SkillsPage.tsx +0 -1200
- package/src/web/components/tasks/TasksPage.tsx +0 -1116
- package/src/web/components/tasks/index.ts +0 -1
- package/src/web/components/telemetry/TelemetryPage.tsx +0 -1129
- package/src/web/components/tests/TestsPage.tsx +0 -594
- package/src/web/components/threads/ThreadsPage.tsx +0 -315
- package/src/web/context/AuthContext.tsx +0 -242
- package/src/web/context/ProjectContext.tsx +0 -214
- package/src/web/context/TelemetryContext.tsx +0 -299
- package/src/web/context/ThemeContext.tsx +0 -90
- package/src/web/context/UIModeContext.tsx +0 -49
- package/src/web/context/index.ts +0 -12
- package/src/web/hooks/index.ts +0 -3
- package/src/web/hooks/useAgents.ts +0 -115
- package/src/web/hooks/useOnboarding.ts +0 -20
- package/src/web/hooks/useProviders.ts +0 -75
- package/src/web/icon.png +0 -0
- package/src/web/index.html +0 -16
- package/src/web/styles.css +0 -118
- package/src/web/themes.ts +0 -162
- package/src/web/types.ts +0 -298
package/src/routes/api/mcp.ts
DELETED
|
@@ -1,574 +0,0 @@
|
|
|
1
|
-
import { spawn } from "bun";
|
|
2
|
-
import { json } from "./helpers";
|
|
3
|
-
import { McpServerDB, McpServerToolDB, generateId, type McpServer } from "../../db";
|
|
4
|
-
import { getNextPort } from "../../server";
|
|
5
|
-
import {
|
|
6
|
-
startMcpProcess,
|
|
7
|
-
stopMcpProcess,
|
|
8
|
-
initializeMcpServer,
|
|
9
|
-
listMcpTools,
|
|
10
|
-
callMcpTool,
|
|
11
|
-
getMcpProcess,
|
|
12
|
-
getHttpMcpClient,
|
|
13
|
-
} from "../../mcp-client";
|
|
14
|
-
import { handleLocalMcpRequest } from "../../mcp-handler";
|
|
15
|
-
|
|
16
|
-
export async function handleMcpRoutes(
|
|
17
|
-
req: Request,
|
|
18
|
-
path: string,
|
|
19
|
-
method: string,
|
|
20
|
-
): Promise<Response | null> {
|
|
21
|
-
// GET /api/mcp/servers - List MCP servers (optionally filtered by project)
|
|
22
|
-
if (path === "/api/mcp/servers" && method === "GET") {
|
|
23
|
-
const url = new URL(req.url);
|
|
24
|
-
const projectFilter = url.searchParams.get("project"); // "all", "global", or project ID
|
|
25
|
-
const forAgent = url.searchParams.get("forAgent"); // agent's project ID (shows global + project)
|
|
26
|
-
|
|
27
|
-
let servers;
|
|
28
|
-
let queryMode: string;
|
|
29
|
-
if (forAgent !== null) {
|
|
30
|
-
// Get servers available for an agent (global + agent's project)
|
|
31
|
-
// Use Light variant: skips expensive decryption of env/headers
|
|
32
|
-
servers = McpServerDB.findForAgentLight(forAgent || null);
|
|
33
|
-
queryMode = `forAgent=${forAgent}`;
|
|
34
|
-
} else if (projectFilter === "global") {
|
|
35
|
-
servers = McpServerDB.findGlobalLight();
|
|
36
|
-
queryMode = "global";
|
|
37
|
-
} else if (projectFilter && projectFilter !== "all") {
|
|
38
|
-
servers = McpServerDB.findByProjectLight(projectFilter);
|
|
39
|
-
queryMode = `project=${projectFilter}`;
|
|
40
|
-
} else {
|
|
41
|
-
servers = McpServerDB.findAllLight();
|
|
42
|
-
queryMode = "all";
|
|
43
|
-
}
|
|
44
|
-
const agentdojoCount = servers.filter(s => s.source === "agentdojo").length;
|
|
45
|
-
console.log(`[mcp:GET] mode=${queryMode} total=${servers.length} agentdojo=${agentdojoCount}`);
|
|
46
|
-
return json({ servers });
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// GET /api/mcp/registry - Search MCP registry for available servers
|
|
50
|
-
if (path === "/api/mcp/registry" && method === "GET") {
|
|
51
|
-
const url = new URL(req.url);
|
|
52
|
-
const search = url.searchParams.get("search") || "";
|
|
53
|
-
const limit = url.searchParams.get("limit") || "20";
|
|
54
|
-
|
|
55
|
-
try {
|
|
56
|
-
const registryUrl = `https://registry.modelcontextprotocol.io/v0/servers?search=${encodeURIComponent(search)}&limit=${limit}`;
|
|
57
|
-
const res = await fetch(registryUrl);
|
|
58
|
-
if (!res.ok) {
|
|
59
|
-
return json({ error: "Failed to fetch registry" }, 500);
|
|
60
|
-
}
|
|
61
|
-
const data = await res.json();
|
|
62
|
-
|
|
63
|
-
// Transform to simpler format - dedupe by name
|
|
64
|
-
const seen = new Set<string>();
|
|
65
|
-
const servers = (data.servers || [])
|
|
66
|
-
.map((item: any) => {
|
|
67
|
-
const s = item.server;
|
|
68
|
-
const pkg = s.packages?.find((p: any) => p.registryType === "npm");
|
|
69
|
-
const remote = s.remotes?.[0];
|
|
70
|
-
|
|
71
|
-
// Extract a short display name from the full name
|
|
72
|
-
const fullName = s.name || "";
|
|
73
|
-
const shortName = fullName.split("/").pop()?.replace(/-mcp$/, "").replace(/^mcp-/, "") || fullName;
|
|
74
|
-
|
|
75
|
-
return {
|
|
76
|
-
id: fullName,
|
|
77
|
-
name: shortName,
|
|
78
|
-
fullName: fullName,
|
|
79
|
-
description: s.description,
|
|
80
|
-
version: s.version,
|
|
81
|
-
repository: s.repository?.url,
|
|
82
|
-
npmPackage: pkg?.identifier || null,
|
|
83
|
-
remoteUrl: remote?.url || null,
|
|
84
|
-
transport: pkg?.transport?.type || (remote ? "http" : "stdio"),
|
|
85
|
-
};
|
|
86
|
-
})
|
|
87
|
-
.filter((s: any) => {
|
|
88
|
-
// Dedupe by fullName
|
|
89
|
-
if (seen.has(s.fullName)) return false;
|
|
90
|
-
seen.add(s.fullName);
|
|
91
|
-
// Only show servers with npm package or remote URL
|
|
92
|
-
return s.npmPackage || s.remoteUrl;
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
return json({ servers });
|
|
96
|
-
} catch (e) {
|
|
97
|
-
return json({ error: "Failed to search registry" }, 500);
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
// POST /api/mcp/servers - Create/install a new MCP server
|
|
102
|
-
if (path === "/api/mcp/servers" && method === "POST") {
|
|
103
|
-
try {
|
|
104
|
-
const body = await req.json();
|
|
105
|
-
const { name, type, package: pkg, pip_module, command, args, env, url, headers, source, project_id } = body;
|
|
106
|
-
|
|
107
|
-
if (!name) {
|
|
108
|
-
return json({ error: "Name is required" }, 400);
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
const server = McpServerDB.create({
|
|
112
|
-
id: generateId(),
|
|
113
|
-
name,
|
|
114
|
-
type: type || "npm",
|
|
115
|
-
package: pkg || null,
|
|
116
|
-
pip_module: pip_module || null,
|
|
117
|
-
command: command || null,
|
|
118
|
-
args: args || null,
|
|
119
|
-
env: env || {},
|
|
120
|
-
url: url || null,
|
|
121
|
-
headers: headers || {},
|
|
122
|
-
source: source || null,
|
|
123
|
-
project_id: project_id || null,
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
return json({ server }, 201);
|
|
127
|
-
} catch (e) {
|
|
128
|
-
console.error("Create MCP server error:", e);
|
|
129
|
-
return json({ error: "Invalid request body" }, 400);
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
// POST /api/mcp/servers/:id/mcp - JSON-RPC endpoint for local MCP servers
|
|
134
|
-
const mcpJsonRpcMatch = path.match(/^\/api\/mcp\/servers\/([^/]+)\/mcp$/);
|
|
135
|
-
if (mcpJsonRpcMatch && method === "POST") {
|
|
136
|
-
return handleLocalMcpRequest(req, mcpJsonRpcMatch[1]);
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
// GET /api/mcp/servers/:id - Get a specific MCP server
|
|
140
|
-
const mcpServerMatch = path.match(/^\/api\/mcp\/servers\/([^/]+)$/);
|
|
141
|
-
if (mcpServerMatch && method === "GET") {
|
|
142
|
-
const server = McpServerDB.findById(mcpServerMatch[1]);
|
|
143
|
-
if (!server) {
|
|
144
|
-
return json({ error: "MCP server not found" }, 404);
|
|
145
|
-
}
|
|
146
|
-
return json({ server });
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
// PUT /api/mcp/servers/:id - Update an MCP server
|
|
150
|
-
if (mcpServerMatch && method === "PUT") {
|
|
151
|
-
const server = McpServerDB.findById(mcpServerMatch[1]);
|
|
152
|
-
if (!server) {
|
|
153
|
-
return json({ error: "MCP server not found" }, 404);
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
try {
|
|
157
|
-
const body = await req.json();
|
|
158
|
-
const updates: Partial<McpServer> = {};
|
|
159
|
-
|
|
160
|
-
if (body.name !== undefined) updates.name = body.name;
|
|
161
|
-
if (body.type !== undefined) updates.type = body.type;
|
|
162
|
-
if (body.package !== undefined) updates.package = body.package;
|
|
163
|
-
if (body.pip_module !== undefined) updates.pip_module = body.pip_module;
|
|
164
|
-
if (body.command !== undefined) updates.command = body.command;
|
|
165
|
-
if (body.args !== undefined) updates.args = body.args;
|
|
166
|
-
if (body.env !== undefined) updates.env = body.env;
|
|
167
|
-
if (body.url !== undefined) updates.url = body.url;
|
|
168
|
-
if (body.headers !== undefined) updates.headers = body.headers;
|
|
169
|
-
if (body.project_id !== undefined) updates.project_id = body.project_id;
|
|
170
|
-
|
|
171
|
-
const updated = McpServerDB.update(mcpServerMatch[1], updates);
|
|
172
|
-
return json({ server: updated });
|
|
173
|
-
} catch (e) {
|
|
174
|
-
return json({ error: "Invalid request body" }, 400);
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
// DELETE /api/mcp/servers/:id - Delete an MCP server
|
|
179
|
-
if (mcpServerMatch && method === "DELETE") {
|
|
180
|
-
const server = McpServerDB.findById(mcpServerMatch[1]);
|
|
181
|
-
if (!server) {
|
|
182
|
-
return json({ error: "MCP server not found" }, 404);
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
// Stop if running
|
|
186
|
-
if (server.status === "running" && server.type !== "local") {
|
|
187
|
-
stopMcpProcess(server.id);
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
// Delete tools if local server
|
|
191
|
-
if (server.type === "local") {
|
|
192
|
-
McpServerToolDB.deleteByServer(mcpServerMatch[1]);
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
McpServerDB.delete(mcpServerMatch[1]);
|
|
196
|
-
return json({ success: true });
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
// POST /api/mcp/servers/:id/start - Start an MCP server
|
|
200
|
-
const mcpStartMatch = path.match(/^\/api\/mcp\/servers\/([^/]+)\/start$/);
|
|
201
|
-
if (mcpStartMatch && method === "POST") {
|
|
202
|
-
const server = McpServerDB.findById(mcpStartMatch[1]);
|
|
203
|
-
if (!server) {
|
|
204
|
-
return json({ error: "MCP server not found" }, 404);
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
if (server.status === "running") {
|
|
208
|
-
return json({ error: "MCP server already running" }, 400);
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
// Local servers: just flip status and set the MCP endpoint URL
|
|
212
|
-
if (server.type === "local") {
|
|
213
|
-
const updated = McpServerDB.update(server.id, {
|
|
214
|
-
status: "running",
|
|
215
|
-
url: `/api/mcp/servers/${server.id}/mcp`,
|
|
216
|
-
});
|
|
217
|
-
return json({
|
|
218
|
-
server: updated,
|
|
219
|
-
message: "Local MCP server started",
|
|
220
|
-
mcpUrl: `/api/mcp/servers/${server.id}/mcp`,
|
|
221
|
-
});
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
// Determine command to run
|
|
225
|
-
// Helper to substitute $ENV_VAR references with actual values
|
|
226
|
-
const substituteEnvVars = (str: string, env: Record<string, string>): string => {
|
|
227
|
-
return str.replace(/\$([A-Z_][A-Z0-9_]*)/g, (_, varName) => {
|
|
228
|
-
return env[varName] || '';
|
|
229
|
-
});
|
|
230
|
-
};
|
|
231
|
-
|
|
232
|
-
// Validate package/command names to prevent injection
|
|
233
|
-
const SAFE_PACKAGE_RE = /^(@[a-z0-9._-]+\/)?[a-z0-9._-]+(@[a-z0-9^~>=<.*-]+)?(\[[\w,]+\])?$/i;
|
|
234
|
-
|
|
235
|
-
// Parse args safely — split on whitespace but respect quoted strings
|
|
236
|
-
const parseArgs = (raw: string, env: Record<string, string>): string[] => {
|
|
237
|
-
const substituted = substituteEnvVars(raw, env);
|
|
238
|
-
const args: string[] = [];
|
|
239
|
-
const re = /"([^"]*?)"|'([^']*?)'|(\S+)/g;
|
|
240
|
-
let m;
|
|
241
|
-
while ((m = re.exec(substituted)) !== null) {
|
|
242
|
-
args.push(m[1] ?? m[2] ?? m[3]);
|
|
243
|
-
}
|
|
244
|
-
return args;
|
|
245
|
-
};
|
|
246
|
-
|
|
247
|
-
let cmd: string[];
|
|
248
|
-
const serverEnv = server.env || {};
|
|
249
|
-
|
|
250
|
-
if (server.command) {
|
|
251
|
-
// Custom command - substitute env vars in args
|
|
252
|
-
cmd = server.command.split(" ");
|
|
253
|
-
if (server.args) {
|
|
254
|
-
cmd.push(...parseArgs(server.args, serverEnv));
|
|
255
|
-
}
|
|
256
|
-
} else if (server.type === "pip" && server.package) {
|
|
257
|
-
// Python pip package - install first, then run module
|
|
258
|
-
const pipPackage = server.package;
|
|
259
|
-
if (!SAFE_PACKAGE_RE.test(pipPackage)) {
|
|
260
|
-
return json({ error: "Invalid pip package name" }, 400);
|
|
261
|
-
}
|
|
262
|
-
const pipModule = server.pip_module || server.package.split("[")[0]; // Default: package name without extras
|
|
263
|
-
|
|
264
|
-
console.log(`Installing pip package: ${pipPackage}...`);
|
|
265
|
-
const installResult = spawn({
|
|
266
|
-
cmd: ["pip", "install", "--quiet", "--no-scripts", pipPackage],
|
|
267
|
-
env: { ...process.env as Record<string, string>, ...serverEnv },
|
|
268
|
-
stdout: "pipe",
|
|
269
|
-
stderr: "pipe",
|
|
270
|
-
});
|
|
271
|
-
|
|
272
|
-
// Wait for installation to complete
|
|
273
|
-
const exitCode = await installResult.exited;
|
|
274
|
-
if (exitCode !== 0) {
|
|
275
|
-
const stderr = await new Response(installResult.stderr).text();
|
|
276
|
-
return json({ error: `Failed to install pip package: ${stderr || "unknown error"}` }, 500);
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
// Now run the module
|
|
280
|
-
cmd = ["python", "-m", pipModule];
|
|
281
|
-
if (server.args) {
|
|
282
|
-
cmd.push(...parseArgs(server.args, serverEnv));
|
|
283
|
-
}
|
|
284
|
-
} else if (server.package) {
|
|
285
|
-
// npm package - use npx with --ignore-scripts to prevent supply chain attacks
|
|
286
|
-
if (!SAFE_PACKAGE_RE.test(server.package)) {
|
|
287
|
-
return json({ error: "Invalid npm package name" }, 400);
|
|
288
|
-
}
|
|
289
|
-
cmd = ["npx", "--ignore-scripts", "-y", server.package];
|
|
290
|
-
if (server.args) {
|
|
291
|
-
cmd.push(...parseArgs(server.args, serverEnv));
|
|
292
|
-
}
|
|
293
|
-
} else {
|
|
294
|
-
return json({ error: "No command or package specified" }, 400);
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
// Use permanently assigned port from DB, fallback to dynamic
|
|
298
|
-
const port = server.port || await getNextPort();
|
|
299
|
-
|
|
300
|
-
console.log(`Starting MCP server ${server.name}...`);
|
|
301
|
-
console.log(` Command: ${cmd.join(" ")}`);
|
|
302
|
-
console.log(` HTTP proxy: http://localhost:${port}/mcp`);
|
|
303
|
-
|
|
304
|
-
// Start the MCP process with stdio pipes + HTTP proxy
|
|
305
|
-
const result = await startMcpProcess(server.id, cmd, server.env || {}, port);
|
|
306
|
-
|
|
307
|
-
if (!result.success) {
|
|
308
|
-
console.error(`Failed to start MCP server: ${result.error}`);
|
|
309
|
-
return json({ error: `Failed to start: ${result.error}` }, 500);
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
// Update status with the HTTP proxy port
|
|
313
|
-
const updated = McpServerDB.setStatus(server.id, "running", port);
|
|
314
|
-
|
|
315
|
-
return json({
|
|
316
|
-
server: updated,
|
|
317
|
-
message: "MCP server started",
|
|
318
|
-
proxyUrl: `http://localhost:${port}/mcp`,
|
|
319
|
-
});
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
// POST /api/mcp/servers/:id/stop - Stop an MCP server
|
|
323
|
-
const mcpStopMatch = path.match(/^\/api\/mcp\/servers\/([^/]+)\/stop$/);
|
|
324
|
-
if (mcpStopMatch && method === "POST") {
|
|
325
|
-
const server = McpServerDB.findById(mcpStopMatch[1]);
|
|
326
|
-
if (!server) {
|
|
327
|
-
return json({ error: "MCP server not found" }, 404);
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
// Local servers: just flip status
|
|
331
|
-
if (server.type === "local") {
|
|
332
|
-
const updated = McpServerDB.update(server.id, { status: "stopped" });
|
|
333
|
-
return json({ server: updated, message: "Local MCP server stopped" });
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
// Stop the MCP process
|
|
337
|
-
stopMcpProcess(server.id);
|
|
338
|
-
|
|
339
|
-
const updated = McpServerDB.setStatus(server.id, "stopped");
|
|
340
|
-
return json({ server: updated, message: "MCP server stopped" });
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
// GET /api/mcp/servers/:id/tools - List tools from an MCP server
|
|
344
|
-
const mcpToolsMatch = path.match(/^\/api\/mcp\/servers\/([^/]+)\/tools$/);
|
|
345
|
-
if (mcpToolsMatch && method === "GET") {
|
|
346
|
-
const server = McpServerDB.findById(mcpToolsMatch[1]);
|
|
347
|
-
if (!server) {
|
|
348
|
-
return json({ error: "MCP server not found" }, 404);
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
// Local servers: read tools from database
|
|
352
|
-
if (server.type === "local") {
|
|
353
|
-
const tools = McpServerToolDB.findByServer(server.id);
|
|
354
|
-
return json({
|
|
355
|
-
serverInfo: { name: server.name, version: "1.0.0" },
|
|
356
|
-
tools: tools.map((t) => ({
|
|
357
|
-
id: t.id,
|
|
358
|
-
name: t.name,
|
|
359
|
-
description: t.description,
|
|
360
|
-
inputSchema: t.input_schema,
|
|
361
|
-
handler_type: t.handler_type,
|
|
362
|
-
enabled: t.enabled,
|
|
363
|
-
})),
|
|
364
|
-
});
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
// HTTP servers use remote HTTP transport
|
|
368
|
-
if (server.type === "http" && server.url) {
|
|
369
|
-
try {
|
|
370
|
-
const httpClient = getHttpMcpClient(server.url, server.headers || {});
|
|
371
|
-
const serverInfo = await httpClient.initialize();
|
|
372
|
-
const tools = await httpClient.listTools();
|
|
373
|
-
|
|
374
|
-
return json({
|
|
375
|
-
serverInfo,
|
|
376
|
-
tools,
|
|
377
|
-
});
|
|
378
|
-
} catch (err) {
|
|
379
|
-
console.error(`Failed to list HTTP MCP tools: ${err}`);
|
|
380
|
-
return json({ error: `Failed to communicate with MCP server: ${err}` }, 500);
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
// Stdio servers require a running process
|
|
385
|
-
const mcpProcess = getMcpProcess(server.id);
|
|
386
|
-
if (!mcpProcess) {
|
|
387
|
-
return json({ error: "MCP server is not running" }, 400);
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
try {
|
|
391
|
-
const serverInfo = await initializeMcpServer(server.id);
|
|
392
|
-
const tools = await listMcpTools(server.id);
|
|
393
|
-
|
|
394
|
-
return json({
|
|
395
|
-
serverInfo,
|
|
396
|
-
tools,
|
|
397
|
-
});
|
|
398
|
-
} catch (err) {
|
|
399
|
-
console.error(`Failed to list MCP tools: ${err}`);
|
|
400
|
-
return json({ error: `Failed to communicate with MCP server: ${err}` }, 500);
|
|
401
|
-
}
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
// POST /api/mcp/servers/:id/tools - Add a tool to a local MCP server
|
|
405
|
-
if (mcpToolsMatch && method === "POST") {
|
|
406
|
-
const server = McpServerDB.findById(mcpToolsMatch[1]);
|
|
407
|
-
if (!server) {
|
|
408
|
-
return json({ error: "MCP server not found" }, 404);
|
|
409
|
-
}
|
|
410
|
-
if (server.type !== "local") {
|
|
411
|
-
return json({ error: "Tools can only be added to local servers" }, 400);
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
try {
|
|
415
|
-
const body = await req.json();
|
|
416
|
-
if (!body.name || !body.description) {
|
|
417
|
-
return json({ error: "name and description are required" }, 400);
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
// Check for duplicate tool name
|
|
421
|
-
const existing = McpServerToolDB.findByServerAndName(server.id, body.name);
|
|
422
|
-
if (existing) {
|
|
423
|
-
return json({ error: `Tool '${body.name}' already exists on this server` }, 409);
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
const tool = McpServerToolDB.create({
|
|
427
|
-
id: generateId(),
|
|
428
|
-
server_id: server.id,
|
|
429
|
-
name: body.name,
|
|
430
|
-
description: body.description,
|
|
431
|
-
input_schema: body.input_schema || { type: "object", properties: {} },
|
|
432
|
-
handler_type: body.handler_type || "mock",
|
|
433
|
-
mock_response: body.mock_response || null,
|
|
434
|
-
http_config: body.http_config || null,
|
|
435
|
-
code: body.code || null,
|
|
436
|
-
enabled: body.enabled !== false,
|
|
437
|
-
});
|
|
438
|
-
|
|
439
|
-
return json({ tool }, 201);
|
|
440
|
-
} catch (e) {
|
|
441
|
-
console.error("Create tool error:", e);
|
|
442
|
-
return json({ error: "Invalid request body" }, 400);
|
|
443
|
-
}
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
// PUT /api/mcp/servers/:id/tools/:toolId - Update a tool on a local MCP server
|
|
447
|
-
const mcpToolUpdateMatch = path.match(/^\/api\/mcp\/servers\/([^/]+)\/tools\/([^/]+)$/);
|
|
448
|
-
if (mcpToolUpdateMatch && method === "PUT") {
|
|
449
|
-
const server = McpServerDB.findById(mcpToolUpdateMatch[1]);
|
|
450
|
-
if (!server) {
|
|
451
|
-
return json({ error: "MCP server not found" }, 404);
|
|
452
|
-
}
|
|
453
|
-
if (server.type !== "local") {
|
|
454
|
-
return json({ error: "Tools can only be updated on local servers" }, 400);
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
const tool = McpServerToolDB.findById(mcpToolUpdateMatch[2]);
|
|
458
|
-
if (!tool || tool.server_id !== server.id) {
|
|
459
|
-
return json({ error: "Tool not found" }, 404);
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
try {
|
|
463
|
-
const body = await req.json();
|
|
464
|
-
const updated = McpServerToolDB.update(tool.id, {
|
|
465
|
-
...(body.name !== undefined && { name: body.name }),
|
|
466
|
-
...(body.description !== undefined && { description: body.description }),
|
|
467
|
-
...(body.input_schema !== undefined && { input_schema: body.input_schema }),
|
|
468
|
-
...(body.handler_type !== undefined && { handler_type: body.handler_type }),
|
|
469
|
-
...(body.mock_response !== undefined && { mock_response: body.mock_response }),
|
|
470
|
-
...(body.http_config !== undefined && { http_config: body.http_config }),
|
|
471
|
-
...(body.code !== undefined && { code: body.code }),
|
|
472
|
-
...(body.enabled !== undefined && { enabled: body.enabled }),
|
|
473
|
-
});
|
|
474
|
-
return json({ tool: updated });
|
|
475
|
-
} catch (e) {
|
|
476
|
-
return json({ error: "Invalid request body" }, 400);
|
|
477
|
-
}
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
// DELETE /api/mcp/servers/:id/tools/:toolId - Delete a tool from a local MCP server
|
|
481
|
-
if (mcpToolUpdateMatch && method === "DELETE") {
|
|
482
|
-
const server = McpServerDB.findById(mcpToolUpdateMatch[1]);
|
|
483
|
-
if (!server) {
|
|
484
|
-
return json({ error: "MCP server not found" }, 404);
|
|
485
|
-
}
|
|
486
|
-
if (server.type !== "local") {
|
|
487
|
-
return json({ error: "Tools can only be deleted from local servers" }, 400);
|
|
488
|
-
}
|
|
489
|
-
|
|
490
|
-
const tool = McpServerToolDB.findById(mcpToolUpdateMatch[2]);
|
|
491
|
-
if (!tool || tool.server_id !== server.id) {
|
|
492
|
-
return json({ error: "Tool not found" }, 404);
|
|
493
|
-
}
|
|
494
|
-
|
|
495
|
-
McpServerToolDB.delete(tool.id);
|
|
496
|
-
return json({ success: true });
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
// POST /api/mcp/servers/:id/tools/:toolName/call - Call a tool on an MCP server
|
|
500
|
-
const mcpToolCallMatch = path.match(/^\/api\/mcp\/servers\/([^/]+)\/tools\/([^/]+)\/call$/);
|
|
501
|
-
if (mcpToolCallMatch && method === "POST") {
|
|
502
|
-
const server = McpServerDB.findById(mcpToolCallMatch[1]);
|
|
503
|
-
if (!server) {
|
|
504
|
-
return json({ error: "MCP server not found" }, 404);
|
|
505
|
-
}
|
|
506
|
-
|
|
507
|
-
const toolName = decodeURIComponent(mcpToolCallMatch[2]);
|
|
508
|
-
|
|
509
|
-
// Local servers: execute tool handler directly
|
|
510
|
-
if (server.type === "local") {
|
|
511
|
-
const tool = McpServerToolDB.findByServerAndName(server.id, toolName);
|
|
512
|
-
if (!tool) {
|
|
513
|
-
return json({ error: `Tool '${toolName}' not found` }, 404);
|
|
514
|
-
}
|
|
515
|
-
if (!tool.enabled) {
|
|
516
|
-
return json({ error: `Tool '${toolName}' is disabled` }, 400);
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
// Forward to JSON-RPC handler via a synthetic request
|
|
520
|
-
const syntheticReq = new Request(req.url, {
|
|
521
|
-
method: "POST",
|
|
522
|
-
headers: { "Content-Type": "application/json" },
|
|
523
|
-
body: JSON.stringify({
|
|
524
|
-
jsonrpc: "2.0",
|
|
525
|
-
id: 1,
|
|
526
|
-
method: "tools/call",
|
|
527
|
-
params: {
|
|
528
|
-
name: toolName,
|
|
529
|
-
arguments: (await req.json()).arguments || {},
|
|
530
|
-
},
|
|
531
|
-
}),
|
|
532
|
-
});
|
|
533
|
-
const mcpResponse = await handleLocalMcpRequest(syntheticReq, server.id);
|
|
534
|
-
const mcpResult = await mcpResponse.json() as any;
|
|
535
|
-
return json({ result: mcpResult.result });
|
|
536
|
-
}
|
|
537
|
-
|
|
538
|
-
// HTTP servers use remote HTTP transport
|
|
539
|
-
if (server.type === "http" && server.url) {
|
|
540
|
-
try {
|
|
541
|
-
const body = await req.json();
|
|
542
|
-
const args = body.arguments || {};
|
|
543
|
-
|
|
544
|
-
const httpClient = getHttpMcpClient(server.url, server.headers || {});
|
|
545
|
-
const result = await httpClient.callTool(toolName, args);
|
|
546
|
-
|
|
547
|
-
return json({ result });
|
|
548
|
-
} catch (err) {
|
|
549
|
-
console.error(`Failed to call HTTP MCP tool: ${err}`);
|
|
550
|
-
return json({ error: `Failed to call tool: ${err}` }, 500);
|
|
551
|
-
}
|
|
552
|
-
}
|
|
553
|
-
|
|
554
|
-
// Stdio servers require a running process
|
|
555
|
-
const mcpProcess = getMcpProcess(server.id);
|
|
556
|
-
if (!mcpProcess) {
|
|
557
|
-
return json({ error: "MCP server is not running" }, 400);
|
|
558
|
-
}
|
|
559
|
-
|
|
560
|
-
try {
|
|
561
|
-
const body = await req.json();
|
|
562
|
-
const args = body.arguments || {};
|
|
563
|
-
|
|
564
|
-
const result = await callMcpTool(server.id, toolName, args);
|
|
565
|
-
|
|
566
|
-
return json({ result });
|
|
567
|
-
} catch (err) {
|
|
568
|
-
console.error(`Failed to call MCP tool: ${err}`);
|
|
569
|
-
return json({ error: `Failed to call tool: ${err}` }, 500);
|
|
570
|
-
}
|
|
571
|
-
}
|
|
572
|
-
|
|
573
|
-
return null;
|
|
574
|
-
}
|