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/mcp-client.ts
DELETED
|
@@ -1,605 +0,0 @@
|
|
|
1
|
-
// MCP Client for communicating with MCP servers
|
|
2
|
-
// Supports both stdio (subprocess) and HTTP transports
|
|
3
|
-
// Includes HTTP proxy to expose stdio servers over HTTP for agents
|
|
4
|
-
|
|
5
|
-
import { spawn, serve, type Subprocess, type Server } from "bun";
|
|
6
|
-
|
|
7
|
-
export interface McpTool {
|
|
8
|
-
name: string;
|
|
9
|
-
description?: string;
|
|
10
|
-
inputSchema: Record<string, unknown>;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export interface McpToolCallResult {
|
|
14
|
-
content: McpContentBlock[];
|
|
15
|
-
isError?: boolean;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export interface McpContentBlock {
|
|
19
|
-
type: "text" | "image" | "resource";
|
|
20
|
-
text?: string;
|
|
21
|
-
data?: string; // base64 for images
|
|
22
|
-
mimeType?: string;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
interface JsonRpcRequest {
|
|
26
|
-
jsonrpc: "2.0";
|
|
27
|
-
id: number;
|
|
28
|
-
method: string;
|
|
29
|
-
params?: unknown;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
interface JsonRpcResponse {
|
|
33
|
-
jsonrpc: "2.0";
|
|
34
|
-
id: number;
|
|
35
|
-
result?: unknown;
|
|
36
|
-
error?: {
|
|
37
|
-
code: number;
|
|
38
|
-
message: string;
|
|
39
|
-
data?: unknown;
|
|
40
|
-
};
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
interface InitializeResult {
|
|
44
|
-
protocolVersion: string;
|
|
45
|
-
capabilities: {
|
|
46
|
-
tools?: { listChanged?: boolean };
|
|
47
|
-
resources?: { subscribe?: boolean; listChanged?: boolean };
|
|
48
|
-
prompts?: { listChanged?: boolean };
|
|
49
|
-
};
|
|
50
|
-
serverInfo: {
|
|
51
|
-
name: string;
|
|
52
|
-
version: string;
|
|
53
|
-
};
|
|
54
|
-
instructions?: string;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
interface ToolsListResult {
|
|
58
|
-
tools: McpTool[];
|
|
59
|
-
nextCursor?: string;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
const MCP_PROTOCOL_VERSION = "2024-11-05";
|
|
63
|
-
|
|
64
|
-
// ============ Stdio MCP Client ============
|
|
65
|
-
|
|
66
|
-
interface McpProcess {
|
|
67
|
-
proc: Subprocess;
|
|
68
|
-
initialized: boolean;
|
|
69
|
-
serverInfo: { name: string; version: string } | null;
|
|
70
|
-
requestId: number;
|
|
71
|
-
buffer: string;
|
|
72
|
-
httpServer?: Server; // HTTP proxy server
|
|
73
|
-
httpPort?: number; // Port the HTTP proxy is running on
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
// Store running MCP processes
|
|
77
|
-
const mcpProcesses = new Map<string, McpProcess>();
|
|
78
|
-
|
|
79
|
-
// Mutex for serializing stdio requests (one at a time per process)
|
|
80
|
-
const requestLocks = new Map<string, Promise<void>>();
|
|
81
|
-
|
|
82
|
-
async function withLock<T>(serverId: string, fn: () => Promise<T>): Promise<T> {
|
|
83
|
-
// Wait for any pending request to complete
|
|
84
|
-
while (requestLocks.has(serverId)) {
|
|
85
|
-
await requestLocks.get(serverId);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
let resolve: () => void;
|
|
89
|
-
const lockPromise = new Promise<void>(r => { resolve = r; });
|
|
90
|
-
requestLocks.set(serverId, lockPromise);
|
|
91
|
-
|
|
92
|
-
try {
|
|
93
|
-
return await fn();
|
|
94
|
-
} finally {
|
|
95
|
-
requestLocks.delete(serverId);
|
|
96
|
-
resolve!();
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
export function getMcpProcess(serverId: string): McpProcess | undefined {
|
|
101
|
-
return mcpProcesses.get(serverId);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
export async function startMcpProcess(
|
|
105
|
-
serverId: string,
|
|
106
|
-
command: string[],
|
|
107
|
-
env: Record<string, string> = {},
|
|
108
|
-
httpPort?: number
|
|
109
|
-
): Promise<{ success: boolean; error?: string; port?: number }> {
|
|
110
|
-
// Stop existing process if any
|
|
111
|
-
stopMcpProcess(serverId);
|
|
112
|
-
|
|
113
|
-
try {
|
|
114
|
-
const proc = spawn({
|
|
115
|
-
cmd: command,
|
|
116
|
-
env: { ...process.env as Record<string, string>, ...env },
|
|
117
|
-
stdin: "pipe",
|
|
118
|
-
stdout: "pipe",
|
|
119
|
-
stderr: "pipe",
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
const entry: McpProcess = {
|
|
123
|
-
proc,
|
|
124
|
-
initialized: false,
|
|
125
|
-
serverInfo: null,
|
|
126
|
-
requestId: 0,
|
|
127
|
-
buffer: "",
|
|
128
|
-
};
|
|
129
|
-
|
|
130
|
-
mcpProcesses.set(serverId, entry);
|
|
131
|
-
|
|
132
|
-
// Give it a moment to start
|
|
133
|
-
await new Promise(resolve => setTimeout(resolve, 500));
|
|
134
|
-
|
|
135
|
-
// Check if process is still running
|
|
136
|
-
if (proc.exitCode !== null) {
|
|
137
|
-
const stderr = await new Response(proc.stderr).text();
|
|
138
|
-
mcpProcesses.delete(serverId);
|
|
139
|
-
return { success: false, error: `Process exited: ${stderr || "unknown error"}` };
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
// Start HTTP proxy server if port specified (retry once if port busy from previous process)
|
|
143
|
-
if (httpPort) {
|
|
144
|
-
for (let attempt = 0; attempt < 3; attempt++) {
|
|
145
|
-
try {
|
|
146
|
-
const httpServer = startHttpProxy(serverId, httpPort);
|
|
147
|
-
entry.httpServer = httpServer;
|
|
148
|
-
entry.httpPort = httpPort;
|
|
149
|
-
console.log(`[MCP] HTTP proxy for ${serverId} started on port ${httpPort}`);
|
|
150
|
-
break;
|
|
151
|
-
} catch (err: any) {
|
|
152
|
-
if (err?.code === "EADDRINUSE" && attempt < 2) {
|
|
153
|
-
await new Promise(r => setTimeout(r, 1000));
|
|
154
|
-
continue;
|
|
155
|
-
}
|
|
156
|
-
console.error(`[MCP] Failed to start HTTP proxy for ${serverId}:`, err);
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
return { success: true, port: httpPort };
|
|
162
|
-
} catch (err) {
|
|
163
|
-
return { success: false, error: String(err) };
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
// Start HTTP proxy server that forwards requests to stdio process
|
|
168
|
-
function startHttpProxy(serverId: string, port: number): Server {
|
|
169
|
-
return serve({
|
|
170
|
-
port,
|
|
171
|
-
async fetch(req) {
|
|
172
|
-
const url = new URL(req.url);
|
|
173
|
-
|
|
174
|
-
// CORS headers
|
|
175
|
-
const corsHeaders = {
|
|
176
|
-
"Access-Control-Allow-Origin": "*",
|
|
177
|
-
"Access-Control-Allow-Methods": "POST, OPTIONS",
|
|
178
|
-
"Access-Control-Allow-Headers": "Content-Type, Mcp-Session-Id",
|
|
179
|
-
};
|
|
180
|
-
|
|
181
|
-
// Handle CORS preflight
|
|
182
|
-
if (req.method === "OPTIONS") {
|
|
183
|
-
return new Response(null, { headers: corsHeaders });
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
// Only accept POST to /mcp
|
|
187
|
-
if (req.method !== "POST" || (url.pathname !== "/mcp" && url.pathname !== "/")) {
|
|
188
|
-
return new Response(JSON.stringify({ error: "Not found" }), {
|
|
189
|
-
status: 404,
|
|
190
|
-
headers: { ...corsHeaders, "Content-Type": "application/json" },
|
|
191
|
-
});
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
try {
|
|
195
|
-
const body = await req.json() as JsonRpcRequest;
|
|
196
|
-
|
|
197
|
-
// Forward to stdio process with lock to serialize requests
|
|
198
|
-
const result = await withLock(serverId, async () => {
|
|
199
|
-
return await sendRequestRaw(serverId, body.method, body.params, body.id);
|
|
200
|
-
});
|
|
201
|
-
|
|
202
|
-
return new Response(JSON.stringify(result), {
|
|
203
|
-
headers: { ...corsHeaders, "Content-Type": "application/json" },
|
|
204
|
-
});
|
|
205
|
-
} catch (err) {
|
|
206
|
-
const errorResponse: JsonRpcResponse = {
|
|
207
|
-
jsonrpc: "2.0",
|
|
208
|
-
id: 0,
|
|
209
|
-
error: {
|
|
210
|
-
code: -32603,
|
|
211
|
-
message: err instanceof Error ? err.message : String(err),
|
|
212
|
-
},
|
|
213
|
-
};
|
|
214
|
-
return new Response(JSON.stringify(errorResponse), {
|
|
215
|
-
status: 500,
|
|
216
|
-
headers: { ...corsHeaders, "Content-Type": "application/json" },
|
|
217
|
-
});
|
|
218
|
-
}
|
|
219
|
-
},
|
|
220
|
-
});
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
// Raw request that returns full JSON-RPC response (for proxy use)
|
|
224
|
-
async function sendRequestRaw(
|
|
225
|
-
serverId: string,
|
|
226
|
-
method: string,
|
|
227
|
-
params?: unknown,
|
|
228
|
-
requestId?: number
|
|
229
|
-
): Promise<JsonRpcResponse> {
|
|
230
|
-
const entry = mcpProcesses.get(serverId);
|
|
231
|
-
if (!entry) {
|
|
232
|
-
throw new Error("MCP process not running");
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
const id = requestId ?? ++entry.requestId;
|
|
236
|
-
const request: JsonRpcRequest = {
|
|
237
|
-
jsonrpc: "2.0",
|
|
238
|
-
id,
|
|
239
|
-
method,
|
|
240
|
-
params,
|
|
241
|
-
};
|
|
242
|
-
|
|
243
|
-
const requestLine = JSON.stringify(request) + "\n";
|
|
244
|
-
|
|
245
|
-
// Write to stdin
|
|
246
|
-
entry.proc.stdin.write(requestLine);
|
|
247
|
-
entry.proc.stdin.flush();
|
|
248
|
-
|
|
249
|
-
// Read response from stdout with timeout
|
|
250
|
-
return await readJsonRpcResponse(entry, id, 30000);
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
export function stopMcpProcess(serverId: string): void {
|
|
254
|
-
const entry = mcpProcesses.get(serverId);
|
|
255
|
-
if (entry) {
|
|
256
|
-
// Stop HTTP proxy server first
|
|
257
|
-
if (entry.httpServer) {
|
|
258
|
-
try {
|
|
259
|
-
entry.httpServer.stop();
|
|
260
|
-
console.log(`[MCP] HTTP proxy for ${serverId} stopped`);
|
|
261
|
-
} catch {
|
|
262
|
-
// Ignore stop errors
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
// Kill the stdio process
|
|
266
|
-
try {
|
|
267
|
-
entry.proc.kill();
|
|
268
|
-
} catch {
|
|
269
|
-
// Ignore kill errors
|
|
270
|
-
}
|
|
271
|
-
mcpProcesses.delete(serverId);
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
// Get the HTTP proxy URL for an MCP server
|
|
276
|
-
export function getMcpProxyUrl(serverId: string): string | null {
|
|
277
|
-
const entry = mcpProcesses.get(serverId);
|
|
278
|
-
if (entry?.httpPort) {
|
|
279
|
-
return `http://localhost:${entry.httpPort}/mcp`;
|
|
280
|
-
}
|
|
281
|
-
return null;
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
async function sendRequest(serverId: string, method: string, params?: unknown): Promise<unknown> {
|
|
285
|
-
const entry = mcpProcesses.get(serverId);
|
|
286
|
-
if (!entry) {
|
|
287
|
-
throw new Error("MCP process not running");
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
const id = ++entry.requestId;
|
|
291
|
-
const request: JsonRpcRequest = {
|
|
292
|
-
jsonrpc: "2.0",
|
|
293
|
-
id,
|
|
294
|
-
method,
|
|
295
|
-
params,
|
|
296
|
-
};
|
|
297
|
-
|
|
298
|
-
const requestLine = JSON.stringify(request) + "\n";
|
|
299
|
-
|
|
300
|
-
// Write to stdin (Bun's FileSink has write() directly)
|
|
301
|
-
entry.proc.stdin.write(requestLine);
|
|
302
|
-
entry.proc.stdin.flush();
|
|
303
|
-
|
|
304
|
-
// Read response from stdout with timeout
|
|
305
|
-
const response = await readJsonRpcResponse(entry, id, 30000);
|
|
306
|
-
|
|
307
|
-
if (response.error) {
|
|
308
|
-
throw new Error(`MCP Error ${response.error.code}: ${response.error.message}`);
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
return response.result;
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
async function readJsonRpcResponse(
|
|
315
|
-
entry: McpProcess,
|
|
316
|
-
expectedId: number,
|
|
317
|
-
timeoutMs: number
|
|
318
|
-
): Promise<JsonRpcResponse> {
|
|
319
|
-
const decoder = new TextDecoder();
|
|
320
|
-
const startTime = Date.now();
|
|
321
|
-
|
|
322
|
-
// Initialize buffer if not exists
|
|
323
|
-
if (!entry.buffer) {
|
|
324
|
-
entry.buffer = "";
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
while (Date.now() - startTime < timeoutMs) {
|
|
328
|
-
// Check buffer first for complete lines
|
|
329
|
-
const lines = entry.buffer.split("\n");
|
|
330
|
-
entry.buffer = lines.pop() || ""; // Keep incomplete line in buffer
|
|
331
|
-
|
|
332
|
-
for (const line of lines) {
|
|
333
|
-
if (!line.trim()) continue;
|
|
334
|
-
|
|
335
|
-
try {
|
|
336
|
-
const response = JSON.parse(line) as JsonRpcResponse;
|
|
337
|
-
if (response.id === expectedId) {
|
|
338
|
-
return response;
|
|
339
|
-
}
|
|
340
|
-
// Ignore responses with different IDs (could be notifications)
|
|
341
|
-
} catch {
|
|
342
|
-
// Not valid JSON, continue
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
// Read more data from stdout
|
|
347
|
-
const reader = entry.proc.stdout.getReader();
|
|
348
|
-
try {
|
|
349
|
-
const { value, done } = await Promise.race([
|
|
350
|
-
reader.read(),
|
|
351
|
-
new Promise<{ value: undefined; done: true }>((resolve) =>
|
|
352
|
-
setTimeout(() => resolve({ value: undefined, done: true }), Math.min(1000, timeoutMs - (Date.now() - startTime)))
|
|
353
|
-
),
|
|
354
|
-
]);
|
|
355
|
-
|
|
356
|
-
if (value) {
|
|
357
|
-
entry.buffer += decoder.decode(value, { stream: true });
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
if (done && !value) {
|
|
361
|
-
// Process might have exited
|
|
362
|
-
await new Promise(r => setTimeout(r, 100));
|
|
363
|
-
}
|
|
364
|
-
} finally {
|
|
365
|
-
reader.releaseLock();
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
throw new Error("Timeout waiting for MCP response");
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
async function sendNotification(serverId: string, method: string, params?: unknown): Promise<void> {
|
|
373
|
-
const entry = mcpProcesses.get(serverId);
|
|
374
|
-
if (!entry) return;
|
|
375
|
-
|
|
376
|
-
const notification = {
|
|
377
|
-
jsonrpc: "2.0",
|
|
378
|
-
method,
|
|
379
|
-
params,
|
|
380
|
-
};
|
|
381
|
-
|
|
382
|
-
const notificationLine = JSON.stringify(notification) + "\n";
|
|
383
|
-
|
|
384
|
-
try {
|
|
385
|
-
entry.proc.stdin.write(notificationLine);
|
|
386
|
-
entry.proc.stdin.flush();
|
|
387
|
-
} catch {
|
|
388
|
-
// Ignore notification errors
|
|
389
|
-
}
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
export async function initializeMcpServer(serverId: string): Promise<{ name: string; version: string }> {
|
|
393
|
-
const entry = mcpProcesses.get(serverId);
|
|
394
|
-
if (!entry) {
|
|
395
|
-
throw new Error("MCP process not running");
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
if (entry.initialized && entry.serverInfo) {
|
|
399
|
-
return entry.serverInfo;
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
const result = await sendRequest(serverId, "initialize", {
|
|
403
|
-
protocolVersion: MCP_PROTOCOL_VERSION,
|
|
404
|
-
capabilities: {
|
|
405
|
-
roots: { listChanged: true },
|
|
406
|
-
},
|
|
407
|
-
clientInfo: {
|
|
408
|
-
name: "apteva",
|
|
409
|
-
version: "1.0.0",
|
|
410
|
-
},
|
|
411
|
-
}) as InitializeResult;
|
|
412
|
-
|
|
413
|
-
entry.serverInfo = result.serverInfo;
|
|
414
|
-
entry.initialized = true;
|
|
415
|
-
|
|
416
|
-
// Send initialized notification
|
|
417
|
-
await sendNotification(serverId, "notifications/initialized");
|
|
418
|
-
|
|
419
|
-
return entry.serverInfo;
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
export async function listMcpTools(serverId: string): Promise<McpTool[]> {
|
|
423
|
-
const entry = mcpProcesses.get(serverId);
|
|
424
|
-
if (!entry) {
|
|
425
|
-
throw new Error("MCP process not running");
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
if (!entry.initialized) {
|
|
429
|
-
await initializeMcpServer(serverId);
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
const result = await sendRequest(serverId, "tools/list") as ToolsListResult;
|
|
433
|
-
return result.tools || [];
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
export async function callMcpTool(
|
|
437
|
-
serverId: string,
|
|
438
|
-
toolName: string,
|
|
439
|
-
args: Record<string, unknown> = {}
|
|
440
|
-
): Promise<McpToolCallResult> {
|
|
441
|
-
const entry = mcpProcesses.get(serverId);
|
|
442
|
-
if (!entry) {
|
|
443
|
-
throw new Error("MCP process not running");
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
if (!entry.initialized) {
|
|
447
|
-
await initializeMcpServer(serverId);
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
const result = await sendRequest(serverId, "tools/call", {
|
|
451
|
-
name: toolName,
|
|
452
|
-
arguments: args,
|
|
453
|
-
}) as McpToolCallResult;
|
|
454
|
-
|
|
455
|
-
return result;
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
// ============ HTTP MCP Client (for remote servers) ============
|
|
459
|
-
|
|
460
|
-
export class HttpMcpClient {
|
|
461
|
-
private url: string;
|
|
462
|
-
private headers: Record<string, string>;
|
|
463
|
-
private sessionId: string | null = null;
|
|
464
|
-
private initialized = false;
|
|
465
|
-
private requestId = 0;
|
|
466
|
-
private serverInfo: { name: string; version: string } | null = null;
|
|
467
|
-
|
|
468
|
-
constructor(url: string, headers: Record<string, string> = {}) {
|
|
469
|
-
this.url = url;
|
|
470
|
-
this.headers = headers;
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
private nextId(): number {
|
|
474
|
-
return ++this.requestId;
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
private async doRequest(method: string, params?: unknown): Promise<unknown> {
|
|
478
|
-
const request: JsonRpcRequest = {
|
|
479
|
-
jsonrpc: "2.0",
|
|
480
|
-
id: this.nextId(),
|
|
481
|
-
method,
|
|
482
|
-
params,
|
|
483
|
-
};
|
|
484
|
-
|
|
485
|
-
const headers: Record<string, string> = {
|
|
486
|
-
"Content-Type": "application/json",
|
|
487
|
-
"Accept": "application/json, text/event-stream",
|
|
488
|
-
...this.headers,
|
|
489
|
-
};
|
|
490
|
-
|
|
491
|
-
if (this.sessionId) {
|
|
492
|
-
headers["Mcp-Session-Id"] = this.sessionId;
|
|
493
|
-
}
|
|
494
|
-
|
|
495
|
-
const response = await fetch(this.url, {
|
|
496
|
-
method: "POST",
|
|
497
|
-
headers,
|
|
498
|
-
body: JSON.stringify(request),
|
|
499
|
-
signal: AbortSignal.timeout(30000),
|
|
500
|
-
});
|
|
501
|
-
|
|
502
|
-
if (!response.ok) {
|
|
503
|
-
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
504
|
-
}
|
|
505
|
-
|
|
506
|
-
const newSessionId = response.headers.get("Mcp-Session-Id");
|
|
507
|
-
if (newSessionId) {
|
|
508
|
-
this.sessionId = newSessionId;
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
const contentType = response.headers.get("Content-Type") || "";
|
|
512
|
-
|
|
513
|
-
if (contentType.includes("text/event-stream")) {
|
|
514
|
-
return this.handleSSEResponse(response);
|
|
515
|
-
}
|
|
516
|
-
|
|
517
|
-
const data = await response.json() as JsonRpcResponse;
|
|
518
|
-
|
|
519
|
-
if (data.error) {
|
|
520
|
-
throw new Error(`MCP Error ${data.error.code}: ${data.error.message}`);
|
|
521
|
-
}
|
|
522
|
-
|
|
523
|
-
return data.result;
|
|
524
|
-
}
|
|
525
|
-
|
|
526
|
-
private async handleSSEResponse(response: Response): Promise<unknown> {
|
|
527
|
-
const text = await response.text();
|
|
528
|
-
const lines = text.split("\n");
|
|
529
|
-
|
|
530
|
-
for (const line of lines) {
|
|
531
|
-
if (line.startsWith("data: ")) {
|
|
532
|
-
const data = JSON.parse(line.slice(6)) as JsonRpcResponse;
|
|
533
|
-
if (data.error) {
|
|
534
|
-
throw new Error(`MCP Error ${data.error.code}: ${data.error.message}`);
|
|
535
|
-
}
|
|
536
|
-
return data.result;
|
|
537
|
-
}
|
|
538
|
-
}
|
|
539
|
-
|
|
540
|
-
throw new Error("No data in SSE response");
|
|
541
|
-
}
|
|
542
|
-
|
|
543
|
-
async initialize(): Promise<{ name: string; version: string }> {
|
|
544
|
-
if (this.initialized && this.serverInfo) {
|
|
545
|
-
return this.serverInfo;
|
|
546
|
-
}
|
|
547
|
-
|
|
548
|
-
const result = await this.doRequest("initialize", {
|
|
549
|
-
protocolVersion: MCP_PROTOCOL_VERSION,
|
|
550
|
-
capabilities: {
|
|
551
|
-
roots: { listChanged: true },
|
|
552
|
-
},
|
|
553
|
-
clientInfo: {
|
|
554
|
-
name: "apteva",
|
|
555
|
-
version: "1.0.0",
|
|
556
|
-
},
|
|
557
|
-
}) as InitializeResult;
|
|
558
|
-
|
|
559
|
-
this.serverInfo = result.serverInfo;
|
|
560
|
-
this.initialized = true;
|
|
561
|
-
|
|
562
|
-
return this.serverInfo;
|
|
563
|
-
}
|
|
564
|
-
|
|
565
|
-
async listTools(): Promise<McpTool[]> {
|
|
566
|
-
if (!this.initialized) {
|
|
567
|
-
await this.initialize();
|
|
568
|
-
}
|
|
569
|
-
|
|
570
|
-
const result = await this.doRequest("tools/list") as ToolsListResult;
|
|
571
|
-
return result.tools || [];
|
|
572
|
-
}
|
|
573
|
-
|
|
574
|
-
async callTool(name: string, args: Record<string, unknown> = {}): Promise<McpToolCallResult> {
|
|
575
|
-
if (!this.initialized) {
|
|
576
|
-
await this.initialize();
|
|
577
|
-
}
|
|
578
|
-
|
|
579
|
-
const result = await this.doRequest("tools/call", {
|
|
580
|
-
name,
|
|
581
|
-
arguments: args,
|
|
582
|
-
}) as McpToolCallResult;
|
|
583
|
-
|
|
584
|
-
return result;
|
|
585
|
-
}
|
|
586
|
-
}
|
|
587
|
-
|
|
588
|
-
// Cache for HTTP MCP clients
|
|
589
|
-
const httpClientCache = new Map<string, HttpMcpClient>();
|
|
590
|
-
|
|
591
|
-
export function getHttpMcpClient(url: string, headers: Record<string, string> = {}): HttpMcpClient {
|
|
592
|
-
const cacheKey = `${url}:${JSON.stringify(headers)}`;
|
|
593
|
-
|
|
594
|
-
let client = httpClientCache.get(cacheKey);
|
|
595
|
-
if (!client) {
|
|
596
|
-
client = new HttpMcpClient(url, headers);
|
|
597
|
-
httpClientCache.set(cacheKey, client);
|
|
598
|
-
}
|
|
599
|
-
|
|
600
|
-
return client;
|
|
601
|
-
}
|
|
602
|
-
|
|
603
|
-
export function clearHttpMcpClientCache(): void {
|
|
604
|
-
httpClientCache.clear();
|
|
605
|
-
}
|