@johpaz/hive-sdk 0.0.3 → 0.0.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/agents/index.js +15379 -28
- package/dist/channels/index.js +7365 -7
- package/dist/cron/index.d.ts +1 -1
- package/dist/cron/index.js +2447 -24
- package/dist/database/index.js +5711 -22
- package/dist/ethics/index.js +5708 -4
- package/dist/index.d.ts +1 -1
- package/dist/index.js +18093 -176
- package/dist/mcp/index.js +698 -3
- package/dist/skills/index.js +3237 -4
- package/dist/tools/index.d.ts +1 -1
- package/dist/tools/index.js +15251 -85
- package/package.json +21 -7
- package/src/cron/index.ts +6 -4
- package/src/index.ts +6 -4
- package/src/tools/index.ts +7 -4
package/dist/mcp/index.js
CHANGED
|
@@ -1,7 +1,702 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
function __accessProp(key) {
|
|
7
|
+
return this[key];
|
|
8
|
+
}
|
|
9
|
+
var __toCommonJS = (from) => {
|
|
10
|
+
var entry = (__moduleCache ??= new WeakMap).get(from), desc;
|
|
11
|
+
if (entry)
|
|
12
|
+
return entry;
|
|
13
|
+
entry = __defProp({}, "__esModule", { value: true });
|
|
14
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
15
|
+
for (var key of __getOwnPropNames(from))
|
|
16
|
+
if (!__hasOwnProp.call(entry, key))
|
|
17
|
+
__defProp(entry, key, {
|
|
18
|
+
get: __accessProp.bind(from, key),
|
|
19
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
__moduleCache.set(from, entry);
|
|
23
|
+
return entry;
|
|
24
|
+
};
|
|
25
|
+
var __moduleCache;
|
|
26
|
+
var __returnValue = (v) => v;
|
|
27
|
+
function __exportSetter(name, newValue) {
|
|
28
|
+
this[name] = __returnValue.bind(null, newValue);
|
|
29
|
+
}
|
|
30
|
+
var __export = (target, all) => {
|
|
31
|
+
for (var name in all)
|
|
32
|
+
__defProp(target, name, {
|
|
33
|
+
get: all[name],
|
|
34
|
+
enumerable: true,
|
|
35
|
+
configurable: true,
|
|
36
|
+
set: __exportSetter.bind(all, name)
|
|
37
|
+
});
|
|
38
|
+
};
|
|
39
|
+
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
40
|
+
var __require = import.meta.require;
|
|
41
|
+
|
|
42
|
+
// ../mcp/src/manager.ts
|
|
43
|
+
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
44
|
+
|
|
45
|
+
// ../mcp/src/logger.ts
|
|
46
|
+
class Logger {
|
|
47
|
+
context;
|
|
48
|
+
level = "info";
|
|
49
|
+
handler = null;
|
|
50
|
+
constructor(context, handler = null) {
|
|
51
|
+
this.context = context;
|
|
52
|
+
this.handler = handler;
|
|
53
|
+
}
|
|
54
|
+
setHandler(handler) {
|
|
55
|
+
this.handler = handler;
|
|
56
|
+
}
|
|
57
|
+
log(level, message, data) {
|
|
58
|
+
if (this.handler) {
|
|
59
|
+
this.handler(level, this.context, message, data);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
debug(message, data) {
|
|
64
|
+
this.log("debug", message, data);
|
|
65
|
+
}
|
|
66
|
+
info(message, data) {
|
|
67
|
+
this.log("info", message, data);
|
|
68
|
+
}
|
|
69
|
+
warn(message, data) {
|
|
70
|
+
this.log("warn", message, data);
|
|
71
|
+
}
|
|
72
|
+
error(message, data) {
|
|
73
|
+
this.log("error", message, data);
|
|
74
|
+
}
|
|
75
|
+
child(context) {
|
|
76
|
+
return new Logger(`${this.context}:${context}`, this.handler);
|
|
77
|
+
}
|
|
78
|
+
setLevel(level) {
|
|
79
|
+
this.level = level;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
var logger = new Logger("mcp");
|
|
83
|
+
|
|
84
|
+
// ../mcp/src/manager.ts
|
|
85
|
+
import * as path from "path";
|
|
86
|
+
|
|
87
|
+
// ../mcp/src/transports/index.ts
|
|
88
|
+
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
89
|
+
|
|
90
|
+
// ../mcp/src/transports/sse.ts
|
|
91
|
+
class SSETransport {
|
|
92
|
+
baseUrl;
|
|
93
|
+
messagesUrl = null;
|
|
94
|
+
headers;
|
|
95
|
+
abortController = null;
|
|
96
|
+
cookies = [];
|
|
97
|
+
startResolve = null;
|
|
98
|
+
startReject = null;
|
|
99
|
+
sessionId;
|
|
100
|
+
onmessage;
|
|
101
|
+
onerror;
|
|
102
|
+
onclose;
|
|
103
|
+
constructor(config) {
|
|
104
|
+
this.baseUrl = config.url;
|
|
105
|
+
this.headers = config.headers ?? {};
|
|
106
|
+
}
|
|
107
|
+
async start() {
|
|
108
|
+
this.abortController = new AbortController;
|
|
109
|
+
return new Promise(async (resolve, reject) => {
|
|
110
|
+
const timeout = setTimeout(() => {
|
|
111
|
+
this.startResolve = null;
|
|
112
|
+
this.startReject = null;
|
|
113
|
+
resolve();
|
|
114
|
+
}, 5000);
|
|
115
|
+
this.startResolve = () => {
|
|
116
|
+
clearTimeout(timeout);
|
|
117
|
+
this.startResolve = null;
|
|
118
|
+
this.startReject = null;
|
|
119
|
+
resolve();
|
|
120
|
+
};
|
|
121
|
+
this.startReject = (err) => {
|
|
122
|
+
clearTimeout(timeout);
|
|
123
|
+
this.startResolve = null;
|
|
124
|
+
this.startReject = null;
|
|
125
|
+
reject(err);
|
|
126
|
+
};
|
|
127
|
+
try {
|
|
128
|
+
logger.debug(`[SSE] Connecting to: ${this.baseUrl}`);
|
|
129
|
+
const response = await fetch(this.baseUrl, {
|
|
130
|
+
method: "GET",
|
|
131
|
+
headers: {
|
|
132
|
+
Accept: "application/json, text/event-stream",
|
|
133
|
+
"Cache-Control": "no-cache",
|
|
134
|
+
...this.headers
|
|
135
|
+
},
|
|
136
|
+
signal: this.abortController.signal
|
|
137
|
+
});
|
|
138
|
+
if (response.status === 405) {
|
|
139
|
+
logger.debug(`[SSE] GET not allowed (405), falling back to Streamable HTTP pattern for ${this.baseUrl}`);
|
|
140
|
+
this.startResolve();
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
if (!response.ok) {
|
|
144
|
+
this.startReject(new Error(`MCP SSE connection failed: ${response.status} ${response.statusText}`));
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
this.readSessionId(response);
|
|
148
|
+
if (response.body) {
|
|
149
|
+
this.startReading(response.body);
|
|
150
|
+
} else {
|
|
151
|
+
this.startResolve();
|
|
152
|
+
}
|
|
153
|
+
} catch (error) {
|
|
154
|
+
if (error.name !== "AbortError") {
|
|
155
|
+
this.startReject(error);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
readSessionId(response) {
|
|
161
|
+
const sessionId = response.headers.get("x-session-id") ?? response.headers.get("mcp-session-id");
|
|
162
|
+
if (sessionId) {
|
|
163
|
+
this.sessionId = sessionId;
|
|
164
|
+
}
|
|
165
|
+
const setCookie = response.headers.get("set-cookie");
|
|
166
|
+
if (setCookie) {
|
|
167
|
+
const newCookies = setCookie.split(",").map((c) => c.split(";")[0].trim());
|
|
168
|
+
this.cookies = [...new Set([...this.cookies, ...newCookies])];
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
startReading(stream) {
|
|
172
|
+
const reader = stream.getReader();
|
|
173
|
+
const decoder = new TextDecoder;
|
|
174
|
+
let buffer = "";
|
|
175
|
+
this.processStream(reader, decoder, buffer).catch((error) => {
|
|
176
|
+
if (this.onerror && error.name !== "AbortError") {
|
|
177
|
+
this.onerror(error instanceof Error ? error : new Error(String(error)));
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
async processStream(reader, decoder, buffer) {
|
|
182
|
+
try {
|
|
183
|
+
let eventType = "message";
|
|
184
|
+
let eventData = "";
|
|
185
|
+
while (true) {
|
|
186
|
+
const { done, value } = await reader.read();
|
|
187
|
+
if (done) {
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
buffer += decoder.decode(value, { stream: true });
|
|
191
|
+
const lines = buffer.split(`
|
|
192
|
+
`);
|
|
193
|
+
buffer = lines.pop() ?? "";
|
|
194
|
+
for (const line of lines) {
|
|
195
|
+
if (line.startsWith("event: ")) {
|
|
196
|
+
eventType = line.slice(7).trim();
|
|
197
|
+
} else if (line.startsWith("data: ")) {
|
|
198
|
+
const data = line.slice(6);
|
|
199
|
+
if (data.trim() === "[DONE]") {
|
|
200
|
+
this.onclose?.();
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
eventData += data + `
|
|
204
|
+
`;
|
|
205
|
+
} else if (line === "") {
|
|
206
|
+
if (eventData) {
|
|
207
|
+
eventData = eventData.trim();
|
|
208
|
+
if (eventType === "endpoint") {
|
|
209
|
+
try {
|
|
210
|
+
this.messagesUrl = new URL(eventData, this.baseUrl).href;
|
|
211
|
+
logger.debug(`[SSE] Messages endpoint received: ${this.messagesUrl}`);
|
|
212
|
+
this.startResolve?.();
|
|
213
|
+
} catch (e) {
|
|
214
|
+
logger.warn(`[SSE] Failed to parse endpoint: ${eventData}`);
|
|
215
|
+
}
|
|
216
|
+
} else if (eventType === "message" || eventType === "") {
|
|
217
|
+
try {
|
|
218
|
+
const parsed = JSON.parse(eventData);
|
|
219
|
+
this.onmessage?.(parsed);
|
|
220
|
+
} catch {}
|
|
221
|
+
}
|
|
222
|
+
eventData = "";
|
|
223
|
+
}
|
|
224
|
+
eventType = "message";
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
} catch (error) {
|
|
229
|
+
if (error.name !== "AbortError") {
|
|
230
|
+
this.onerror?.(error instanceof Error ? error : new Error(String(error)));
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
async close() {
|
|
235
|
+
this.abortController?.abort();
|
|
236
|
+
this.abortController = null;
|
|
237
|
+
this.sessionId = undefined;
|
|
238
|
+
this.onclose?.();
|
|
239
|
+
}
|
|
240
|
+
async send(message) {
|
|
241
|
+
if (!this.abortController) {
|
|
242
|
+
throw new Error("SSE transport not started \u2014 llama start() primero");
|
|
243
|
+
}
|
|
244
|
+
const targetUrl = this.messagesUrl || this.baseUrl;
|
|
245
|
+
let url = targetUrl;
|
|
246
|
+
if (this.sessionId && !url.includes(`sessionId=${this.sessionId}`)) {
|
|
247
|
+
url = `${targetUrl}${targetUrl.includes("?") ? "&" : "?"}sessionId=${this.sessionId}`;
|
|
248
|
+
}
|
|
249
|
+
const response = await fetch(url, {
|
|
250
|
+
method: "POST",
|
|
251
|
+
headers: {
|
|
252
|
+
"Content-Type": "application/json",
|
|
253
|
+
Accept: "application/json, text/event-stream",
|
|
254
|
+
...this.headers,
|
|
255
|
+
...this.cookies.length > 0 ? { Cookie: this.cookies.join("; ") } : {}
|
|
256
|
+
},
|
|
257
|
+
body: JSON.stringify(message),
|
|
258
|
+
signal: this.abortController.signal
|
|
259
|
+
});
|
|
260
|
+
this.readSessionId(response);
|
|
261
|
+
if (!response.ok) {
|
|
262
|
+
const body = await response.text().catch(() => "");
|
|
263
|
+
throw new Error(`MCP message failed (${response.status}): ${body || response.statusText}`);
|
|
264
|
+
}
|
|
265
|
+
const contentType = response.headers.get("content-type") ?? "";
|
|
266
|
+
if (contentType.includes("text/event-stream") && response.body) {
|
|
267
|
+
this.startReading(response.body);
|
|
268
|
+
} else if (contentType.includes("application/json")) {
|
|
269
|
+
const text = await response.text();
|
|
270
|
+
if (text.trim()) {
|
|
271
|
+
try {
|
|
272
|
+
this.onmessage?.(JSON.parse(text));
|
|
273
|
+
} catch {}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// ../mcp/src/transports/websocket.ts
|
|
280
|
+
class WebSocketTransport {
|
|
281
|
+
url;
|
|
282
|
+
ws = null;
|
|
283
|
+
headers;
|
|
284
|
+
intentionallyClosed = false;
|
|
285
|
+
reconnectAttempts = 0;
|
|
286
|
+
reconnectDelay;
|
|
287
|
+
reconnectMaxAttempts;
|
|
288
|
+
shouldReconnect;
|
|
289
|
+
onmessage;
|
|
290
|
+
onerror;
|
|
291
|
+
onclose;
|
|
292
|
+
constructor(config) {
|
|
293
|
+
this.url = config.url;
|
|
294
|
+
this.headers = config.headers;
|
|
295
|
+
this.shouldReconnect = config.reconnect ?? true;
|
|
296
|
+
this.reconnectDelay = config.reconnectDelay ?? 3000;
|
|
297
|
+
this.reconnectMaxAttempts = config.reconnectMaxAttempts ?? 10;
|
|
298
|
+
}
|
|
299
|
+
async start() {
|
|
300
|
+
this.intentionallyClosed = false;
|
|
301
|
+
this.reconnectAttempts = 0;
|
|
302
|
+
return this.connect();
|
|
303
|
+
}
|
|
304
|
+
connect() {
|
|
305
|
+
return new Promise((resolve, reject) => {
|
|
306
|
+
const ws = this.headers && Object.keys(this.headers).length > 0 ? new WebSocket(this.url, {
|
|
307
|
+
headers: this.headers
|
|
308
|
+
}) : new WebSocket(this.url);
|
|
309
|
+
this.ws = ws;
|
|
310
|
+
let resolved = false;
|
|
311
|
+
ws.onopen = () => {
|
|
312
|
+
resolved = true;
|
|
313
|
+
this.reconnectAttempts = 0;
|
|
314
|
+
resolve();
|
|
315
|
+
};
|
|
316
|
+
ws.onmessage = (event) => {
|
|
317
|
+
try {
|
|
318
|
+
const data = JSON.parse(event.data);
|
|
319
|
+
this.onmessage?.(data);
|
|
320
|
+
} catch {}
|
|
321
|
+
};
|
|
322
|
+
ws.onerror = (event) => {
|
|
323
|
+
const error = event.error ?? new Error(`WebSocket error en ${this.url}`);
|
|
324
|
+
if (!resolved) {
|
|
325
|
+
reject(error);
|
|
326
|
+
} else {
|
|
327
|
+
this.onerror?.(error instanceof Error ? error : new Error(String(error)));
|
|
328
|
+
}
|
|
329
|
+
};
|
|
330
|
+
ws.onclose = (event) => {
|
|
331
|
+
this.ws = null;
|
|
332
|
+
if (this.intentionallyClosed) {
|
|
333
|
+
this.onclose?.();
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
if (!resolved) {
|
|
337
|
+
reject(new Error(`WebSocket cerrado antes de conectar \u2014 code: ${event.code}, reason: ${event.reason}`));
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
if (this.shouldReconnect && this.reconnectAttempts < this.reconnectMaxAttempts) {
|
|
341
|
+
this.reconnectAttempts++;
|
|
342
|
+
const delay = this.reconnectDelay * this.reconnectAttempts;
|
|
343
|
+
setTimeout(async () => {
|
|
344
|
+
try {
|
|
345
|
+
await this.connect();
|
|
346
|
+
} catch (err) {
|
|
347
|
+
this.onerror?.(err instanceof Error ? err : new Error(String(err)));
|
|
348
|
+
this.onclose?.();
|
|
349
|
+
}
|
|
350
|
+
}, delay);
|
|
351
|
+
} else {
|
|
352
|
+
this.onerror?.(new Error(`WebSocket desconectado despu\xE9s de ${this.reconnectAttempts} intentos \u2014 ${this.url}`));
|
|
353
|
+
this.onclose?.();
|
|
354
|
+
}
|
|
355
|
+
};
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
async close() {
|
|
359
|
+
this.intentionallyClosed = true;
|
|
360
|
+
if (this.ws) {
|
|
361
|
+
this.ws.close(1000, "Client closed connection");
|
|
362
|
+
this.ws = null;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
async send(message) {
|
|
366
|
+
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
|
|
367
|
+
throw new Error(`WebSocket no est\xE1 conectado \u2014 readyState: ${this.ws?.readyState ?? "null"}`);
|
|
368
|
+
}
|
|
369
|
+
this.ws.send(JSON.stringify(message));
|
|
370
|
+
}
|
|
371
|
+
get isConnected() {
|
|
372
|
+
return this.ws !== null && this.ws.readyState === WebSocket.OPEN;
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// ../mcp/src/transports/index.ts
|
|
377
|
+
function createTransport(options) {
|
|
378
|
+
switch (options.type) {
|
|
379
|
+
case "stdio": {
|
|
380
|
+
if (!options.stdio) {
|
|
381
|
+
throw new Error("stdio config required for stdio transport");
|
|
382
|
+
}
|
|
383
|
+
return new StdioClientTransport({
|
|
384
|
+
command: options.stdio.command,
|
|
385
|
+
args: options.stdio.args ?? [],
|
|
386
|
+
env: options.stdio.env ?? process.env
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
case "sse": {
|
|
390
|
+
if (!options.sse) {
|
|
391
|
+
throw new Error("sse config required for SSE transport");
|
|
392
|
+
}
|
|
393
|
+
return new SSETransport(options.sse);
|
|
394
|
+
}
|
|
395
|
+
case "websocket": {
|
|
396
|
+
if (!options.websocket) {
|
|
397
|
+
throw new Error("websocket config required for WebSocket transport");
|
|
398
|
+
}
|
|
399
|
+
return new WebSocketTransport(options.websocket);
|
|
400
|
+
}
|
|
401
|
+
default: {
|
|
402
|
+
const _exhaustive = options.type;
|
|
403
|
+
throw new Error(`Unknown transport type: ${_exhaustive}`);
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// ../mcp/src/manager.ts
|
|
409
|
+
class MCPClientManager {
|
|
410
|
+
servers = new Map;
|
|
411
|
+
config;
|
|
412
|
+
log = logger.child("mcp");
|
|
413
|
+
constructor(config) {
|
|
414
|
+
this.config = config;
|
|
415
|
+
}
|
|
416
|
+
setLogHandler(handler) {
|
|
417
|
+
logger.setHandler(handler);
|
|
418
|
+
}
|
|
419
|
+
async initialize() {
|
|
420
|
+
const servers = this.config.servers ?? {};
|
|
421
|
+
for (const [name, serverConfig] of Object.entries(servers)) {
|
|
422
|
+
if (serverConfig.enabled !== false) {
|
|
423
|
+
this.servers.set(name, {
|
|
424
|
+
name,
|
|
425
|
+
config: serverConfig,
|
|
426
|
+
client: null,
|
|
427
|
+
transport: null,
|
|
428
|
+
status: "disconnected",
|
|
429
|
+
tools: [],
|
|
430
|
+
resources: [],
|
|
431
|
+
prompts: [],
|
|
432
|
+
reconnectAttempts: 0
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
this.log.info(`MCP Client initialized with ${this.servers.size} servers`);
|
|
437
|
+
await this.connectAll();
|
|
438
|
+
}
|
|
439
|
+
async updateConfig(config) {
|
|
440
|
+
this.config = config;
|
|
441
|
+
const newServers = this.config.servers ?? {};
|
|
442
|
+
for (const name of this.servers.keys()) {
|
|
443
|
+
if (!newServers[name] || newServers[name].enabled === false) {
|
|
444
|
+
await this.disconnectServer(name);
|
|
445
|
+
this.servers.delete(name);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
for (const [name, serverConfig] of Object.entries(newServers)) {
|
|
449
|
+
if (serverConfig.enabled !== false) {
|
|
450
|
+
const existing = this.servers.get(name);
|
|
451
|
+
if (existing) {
|
|
452
|
+
const configChanged = JSON.stringify(existing.config) !== JSON.stringify(serverConfig);
|
|
453
|
+
if (configChanged) {
|
|
454
|
+
const wasConnected = existing.status === "connected";
|
|
455
|
+
await this.disconnectServer(name);
|
|
456
|
+
existing.config = serverConfig;
|
|
457
|
+
if (wasConnected) {
|
|
458
|
+
await this.connectServer(name).catch((err) => {
|
|
459
|
+
this.log.error(`Failed to reconnect ${name} after config update: ${err.message}`);
|
|
460
|
+
});
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
} else {
|
|
464
|
+
this.servers.set(name, {
|
|
465
|
+
name,
|
|
466
|
+
config: serverConfig,
|
|
467
|
+
client: null,
|
|
468
|
+
transport: null,
|
|
469
|
+
status: "disconnected",
|
|
470
|
+
tools: [],
|
|
471
|
+
resources: [],
|
|
472
|
+
prompts: [],
|
|
473
|
+
reconnectAttempts: 0
|
|
474
|
+
});
|
|
475
|
+
await this.connectServer(name).catch((err) => {
|
|
476
|
+
this.log.error(`Failed to connect new server ${name}: ${err.message}`);
|
|
477
|
+
});
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
expandPath(p) {
|
|
483
|
+
if (p.startsWith("~")) {
|
|
484
|
+
return path.join(process.env.HOME ?? "", p.slice(1));
|
|
485
|
+
}
|
|
486
|
+
return p;
|
|
487
|
+
}
|
|
488
|
+
createTransportForServer(state) {
|
|
489
|
+
const transportType = state.config.transport;
|
|
490
|
+
switch (transportType) {
|
|
491
|
+
case "stdio": {
|
|
492
|
+
const command = state.config.command ?? "npx";
|
|
493
|
+
const args = state.config.args ?? [];
|
|
494
|
+
const env = {};
|
|
495
|
+
for (const [key, value] of Object.entries(process.env)) {
|
|
496
|
+
if (value !== undefined)
|
|
497
|
+
env[key] = value;
|
|
498
|
+
}
|
|
499
|
+
if (state.config.env) {
|
|
500
|
+
for (const [key, value] of Object.entries(state.config.env)) {
|
|
501
|
+
env[key] = this.expandPath(value);
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
return createTransport({ type: "stdio", stdio: { command, args, env } });
|
|
505
|
+
}
|
|
506
|
+
case "sse": {
|
|
507
|
+
const url = state.config.url;
|
|
508
|
+
if (!url)
|
|
509
|
+
throw new Error("SSE transport requires 'url' config");
|
|
510
|
+
return createTransport({
|
|
511
|
+
type: "sse",
|
|
512
|
+
sse: { url, headers: state.config.headers }
|
|
513
|
+
});
|
|
514
|
+
}
|
|
515
|
+
case "websocket": {
|
|
516
|
+
const url = state.config.url;
|
|
517
|
+
if (!url)
|
|
518
|
+
throw new Error("WebSocket transport requires 'url' config");
|
|
519
|
+
return createTransport({
|
|
520
|
+
type: "websocket",
|
|
521
|
+
websocket: { url, headers: state.config.headers }
|
|
522
|
+
});
|
|
523
|
+
}
|
|
524
|
+
default:
|
|
525
|
+
throw new Error(`Unknown transport type: ${transportType}`);
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
async connectServer(name) {
|
|
529
|
+
const state = this.servers.get(name);
|
|
530
|
+
if (!state)
|
|
531
|
+
throw new Error(`MCP server not found: ${name}`);
|
|
532
|
+
if (state.status === "connected")
|
|
533
|
+
return;
|
|
534
|
+
state.status = "connecting";
|
|
535
|
+
state.lastError = undefined;
|
|
536
|
+
this.log.info(`Connecting to MCP server: ${name}`);
|
|
537
|
+
try {
|
|
538
|
+
const transport = this.createTransportForServer(state);
|
|
539
|
+
const client = new Client({ name: "hive", version: "0.1.0" }, { capabilities: {} });
|
|
540
|
+
await client.connect(transport);
|
|
541
|
+
state.client = client;
|
|
542
|
+
state.transport = transport;
|
|
543
|
+
state.status = "connected";
|
|
544
|
+
state.reconnectAttempts = 0;
|
|
545
|
+
await this.discoverCapabilities(name);
|
|
546
|
+
this.log.info(`Connected to MCP server: ${name}`, {
|
|
547
|
+
tools: state.tools.length,
|
|
548
|
+
resources: state.resources.length,
|
|
549
|
+
prompts: state.prompts.length
|
|
550
|
+
});
|
|
551
|
+
} catch (error) {
|
|
552
|
+
state.status = "error";
|
|
553
|
+
state.lastError = error.message;
|
|
554
|
+
this.log.error(`Failed to connect to MCP server ${name}: ${state.lastError}`);
|
|
555
|
+
throw error;
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
async discoverCapabilities(name) {
|
|
559
|
+
const state = this.servers.get(name);
|
|
560
|
+
if (!state?.client)
|
|
561
|
+
return;
|
|
562
|
+
try {
|
|
563
|
+
const toolsResult = await state.client.listTools();
|
|
564
|
+
state.tools = (toolsResult.tools ?? []).map((t) => ({
|
|
565
|
+
name: t.name,
|
|
566
|
+
description: t.description ?? "",
|
|
567
|
+
inputSchema: t.inputSchema
|
|
568
|
+
}));
|
|
569
|
+
} catch {
|
|
570
|
+
this.log.debug(`No tools from MCP server: ${name}`);
|
|
571
|
+
}
|
|
572
|
+
try {
|
|
573
|
+
const resourcesResult = await state.client.listResources();
|
|
574
|
+
state.resources = (resourcesResult.resources ?? []).map((r) => ({
|
|
575
|
+
uri: r.uri,
|
|
576
|
+
name: r.name,
|
|
577
|
+
description: r.description,
|
|
578
|
+
mimeType: r.mimeType
|
|
579
|
+
}));
|
|
580
|
+
} catch {
|
|
581
|
+
this.log.debug(`No resources from MCP server: ${name}`);
|
|
582
|
+
}
|
|
583
|
+
try {
|
|
584
|
+
const promptsResult = await state.client.listPrompts();
|
|
585
|
+
state.prompts = (promptsResult.prompts ?? []).map((p) => ({
|
|
586
|
+
name: p.name,
|
|
587
|
+
description: p.description,
|
|
588
|
+
arguments: p.arguments
|
|
589
|
+
}));
|
|
590
|
+
} catch {
|
|
591
|
+
this.log.debug(`No prompts from MCP server: ${name}`);
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
async disconnectServer(name) {
|
|
595
|
+
const state = this.servers.get(name);
|
|
596
|
+
if (!state)
|
|
597
|
+
return;
|
|
598
|
+
if (state.client) {
|
|
599
|
+
try {
|
|
600
|
+
await state.client.close();
|
|
601
|
+
} catch {}
|
|
602
|
+
}
|
|
603
|
+
state.client = null;
|
|
604
|
+
state.transport = null;
|
|
605
|
+
state.status = "disconnected";
|
|
606
|
+
state.lastError = undefined;
|
|
607
|
+
this.log.info(`Disconnected from MCP server: ${name}`);
|
|
608
|
+
}
|
|
609
|
+
async callTool(serverName, toolName, args) {
|
|
610
|
+
const state = this.servers.get(serverName);
|
|
611
|
+
if (!state?.client) {
|
|
612
|
+
throw new Error(`MCP server not connected: ${serverName}`);
|
|
613
|
+
}
|
|
614
|
+
this.log.debug(`Calling MCP tool: ${serverName}/${toolName}`, { args });
|
|
615
|
+
const result = await state.client.callTool({
|
|
616
|
+
name: toolName,
|
|
617
|
+
arguments: args
|
|
618
|
+
});
|
|
619
|
+
return result.content;
|
|
620
|
+
}
|
|
621
|
+
async readResource(serverName, uri) {
|
|
622
|
+
const state = this.servers.get(serverName);
|
|
623
|
+
if (!state?.client) {
|
|
624
|
+
throw new Error(`MCP server not connected: ${serverName}`);
|
|
625
|
+
}
|
|
626
|
+
const result = await state.client.readResource({ uri });
|
|
627
|
+
return result.contents;
|
|
628
|
+
}
|
|
629
|
+
getServerStatus(name) {
|
|
630
|
+
return this.servers.get(name)?.status;
|
|
631
|
+
}
|
|
632
|
+
getServerTools(name) {
|
|
633
|
+
return this.servers.get(name)?.tools ?? [];
|
|
634
|
+
}
|
|
635
|
+
getServerResources(name) {
|
|
636
|
+
return this.servers.get(name)?.resources ?? [];
|
|
637
|
+
}
|
|
638
|
+
getAllTools() {
|
|
639
|
+
const result = new Map;
|
|
640
|
+
for (const [name, state] of this.servers) {
|
|
641
|
+
if (state.status === "connected") {
|
|
642
|
+
result.set(name, state.tools);
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
return result;
|
|
646
|
+
}
|
|
647
|
+
listServers() {
|
|
648
|
+
return Array.from(this.servers.values()).map((s) => ({
|
|
649
|
+
name: s.name,
|
|
650
|
+
status: s.status,
|
|
651
|
+
tools: s.tools,
|
|
652
|
+
resources: s.resources,
|
|
653
|
+
prompts: s.prompts,
|
|
654
|
+
url: s.config.transport === "stdio" ? `${s.config.command} ${s.config.args?.join(" ")}` : s.config.url,
|
|
655
|
+
error: s.lastError
|
|
656
|
+
}));
|
|
657
|
+
}
|
|
658
|
+
getServerDetails(name) {
|
|
659
|
+
const s = this.servers.get(name);
|
|
660
|
+
if (!s)
|
|
661
|
+
return;
|
|
662
|
+
const safeConfig = {
|
|
663
|
+
...s.config,
|
|
664
|
+
headers: s.config.headers ? Object.fromEntries(Object.entries(s.config.headers).map(([k, v]) => [
|
|
665
|
+
k,
|
|
666
|
+
k.toLowerCase().includes("auth") || k.toLowerCase().includes("token") || k.toLowerCase().includes("key") ? `${v.slice(0, 4)}\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022` : v
|
|
667
|
+
])) : undefined
|
|
668
|
+
};
|
|
669
|
+
return {
|
|
670
|
+
name: s.name,
|
|
671
|
+
status: s.status,
|
|
672
|
+
tools: s.tools,
|
|
673
|
+
resources: s.resources,
|
|
674
|
+
prompts: s.prompts,
|
|
675
|
+
config: safeConfig,
|
|
676
|
+
error: s.lastError
|
|
677
|
+
};
|
|
678
|
+
}
|
|
679
|
+
async connectAll() {
|
|
680
|
+
const promises = [];
|
|
681
|
+
for (const name of this.servers.keys()) {
|
|
682
|
+
promises.push(this.connectServer(name).catch((error) => {
|
|
683
|
+
this.log.error(`Failed to connect ${name}: ${error.message}`);
|
|
684
|
+
}));
|
|
685
|
+
}
|
|
686
|
+
await Promise.allSettled(promises);
|
|
687
|
+
}
|
|
688
|
+
async reconnectAll() {
|
|
689
|
+
await this.disconnectAll();
|
|
690
|
+
await this.connectAll();
|
|
691
|
+
}
|
|
692
|
+
async disconnectAll() {
|
|
693
|
+
const promises = [];
|
|
694
|
+
for (const name of this.servers.keys()) {
|
|
695
|
+
promises.push(this.disconnectServer(name));
|
|
696
|
+
}
|
|
697
|
+
await Promise.allSettled(promises);
|
|
698
|
+
}
|
|
699
|
+
}
|
|
5
700
|
export {
|
|
6
701
|
logger,
|
|
7
702
|
MCPClientManager
|