opensentinel 2.1.1
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/LICENSE +21 -0
- package/README.md +283 -0
- package/dist/bot-KJ26BG56.js +15 -0
- package/dist/bot-KJ26BG56.js.map +1 -0
- package/dist/charts-MMXM6BWW.js +241 -0
- package/dist/charts-MMXM6BWW.js.map +1 -0
- package/dist/chunk-4LVWXUNC.js +1079 -0
- package/dist/chunk-4LVWXUNC.js.map +1 -0
- package/dist/chunk-4TG2IG5K.js +5249 -0
- package/dist/chunk-4TG2IG5K.js.map +1 -0
- package/dist/chunk-6DRDKB45.js +251 -0
- package/dist/chunk-6DRDKB45.js.map +1 -0
- package/dist/chunk-6SNHU3CY.js +123 -0
- package/dist/chunk-6SNHU3CY.js.map +1 -0
- package/dist/chunk-CI6Q63MM.js +1613 -0
- package/dist/chunk-CI6Q63MM.js.map +1 -0
- package/dist/chunk-CQ4JURG7.js +57 -0
- package/dist/chunk-CQ4JURG7.js.map +1 -0
- package/dist/chunk-F6QUZQGI.js +51 -0
- package/dist/chunk-F6QUZQGI.js.map +1 -0
- package/dist/chunk-GK3E2I7A.js +216 -0
- package/dist/chunk-GK3E2I7A.js.map +1 -0
- package/dist/chunk-GUBEEYDW.js +211 -0
- package/dist/chunk-GUBEEYDW.js.map +1 -0
- package/dist/chunk-GVJVEWHI.js +29 -0
- package/dist/chunk-GVJVEWHI.js.map +1 -0
- package/dist/chunk-HH2HBTQM.js +806 -0
- package/dist/chunk-HH2HBTQM.js.map +1 -0
- package/dist/chunk-JXUP2X7V.js +129 -0
- package/dist/chunk-JXUP2X7V.js.map +1 -0
- package/dist/chunk-KHNYJY2Z.js +178 -0
- package/dist/chunk-KHNYJY2Z.js.map +1 -0
- package/dist/chunk-L3F43VPB.js +652 -0
- package/dist/chunk-L3F43VPB.js.map +1 -0
- package/dist/chunk-L3PDU3XN.js +803 -0
- package/dist/chunk-L3PDU3XN.js.map +1 -0
- package/dist/chunk-NSBPE2FW.js +17 -0
- package/dist/chunk-NSBPE2FW.js.map +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +52 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/setup.d.ts +9 -0
- package/dist/commands/setup.js +374 -0
- package/dist/commands/setup.js.map +1 -0
- package/dist/commands/start.d.ts +8 -0
- package/dist/commands/start.js +27 -0
- package/dist/commands/start.js.map +1 -0
- package/dist/commands/status.d.ts +8 -0
- package/dist/commands/status.js +57 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/commands/stop.d.ts +8 -0
- package/dist/commands/stop.js +37 -0
- package/dist/commands/stop.js.map +1 -0
- package/dist/commands/utils.d.ts +50 -0
- package/dist/commands/utils.js +36 -0
- package/dist/commands/utils.js.map +1 -0
- package/dist/discord-ZOJFTVTB.js +49 -0
- package/dist/discord-ZOJFTVTB.js.map +1 -0
- package/dist/imessage-JFRB6EJ7.js +14 -0
- package/dist/imessage-JFRB6EJ7.js.map +1 -0
- package/dist/lib.d.ts +855 -0
- package/dist/lib.js +274 -0
- package/dist/lib.js.map +1 -0
- package/dist/mcp-LS7Q3Z5W.js +30 -0
- package/dist/mcp-LS7Q3Z5W.js.map +1 -0
- package/dist/scheduler-EZ7CZMCS.js +42 -0
- package/dist/scheduler-EZ7CZMCS.js.map +1 -0
- package/dist/signal-T3MCSULM.js +14 -0
- package/dist/signal-T3MCSULM.js.map +1 -0
- package/dist/slack-N2M4FHAJ.js +54 -0
- package/dist/slack-N2M4FHAJ.js.map +1 -0
- package/dist/src-K7GASHRH.js +430 -0
- package/dist/src-K7GASHRH.js.map +1 -0
- package/dist/tools-24GZHYRF.js +16 -0
- package/dist/tools-24GZHYRF.js.map +1 -0
- package/dist/whatsapp-VCRUPAO5.js +14 -0
- package/dist/whatsapp-VCRUPAO5.js.map +1 -0
- package/drizzle/0000_chilly_shinobi_shaw.sql +75 -0
- package/drizzle/0001_freezing_shape.sql +274 -0
- package/drizzle/meta/0000_snapshot.json +529 -0
- package/drizzle/meta/0001_snapshot.json +2576 -0
- package/drizzle/meta/_journal.json +20 -0
- package/package.json +98 -0
|
@@ -0,0 +1,652 @@
|
|
|
1
|
+
// src/core/mcp/client.ts
|
|
2
|
+
import { spawn } from "child_process";
|
|
3
|
+
import { nanoid } from "nanoid";
|
|
4
|
+
var MCP_PROTOCOL_VERSION = "2024-11-05";
|
|
5
|
+
var MCPClient = class {
|
|
6
|
+
config;
|
|
7
|
+
state;
|
|
8
|
+
process = null;
|
|
9
|
+
pendingRequests = /* @__PURE__ */ new Map();
|
|
10
|
+
messageBuffer = "";
|
|
11
|
+
requestTimeout;
|
|
12
|
+
constructor(config, timeout = 3e4) {
|
|
13
|
+
this.config = config;
|
|
14
|
+
this.requestTimeout = timeout;
|
|
15
|
+
this.state = {
|
|
16
|
+
config,
|
|
17
|
+
status: "disconnected",
|
|
18
|
+
tools: []
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
get id() {
|
|
22
|
+
return this.config.id;
|
|
23
|
+
}
|
|
24
|
+
get name() {
|
|
25
|
+
return this.config.name;
|
|
26
|
+
}
|
|
27
|
+
get status() {
|
|
28
|
+
return this.state.status;
|
|
29
|
+
}
|
|
30
|
+
get tools() {
|
|
31
|
+
return this.state.tools;
|
|
32
|
+
}
|
|
33
|
+
get serverInfo() {
|
|
34
|
+
return this.state.serverInfo;
|
|
35
|
+
}
|
|
36
|
+
// ============================================
|
|
37
|
+
// CONNECTION MANAGEMENT
|
|
38
|
+
// ============================================
|
|
39
|
+
async connect() {
|
|
40
|
+
if (this.state.status === "connected") {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
this.state.status = "connecting";
|
|
44
|
+
try {
|
|
45
|
+
if (this.config.transport === "stdio") {
|
|
46
|
+
await this.connectStdio();
|
|
47
|
+
} else if (this.config.transport === "http+sse") {
|
|
48
|
+
await this.connectHttpSse();
|
|
49
|
+
} else {
|
|
50
|
+
throw new Error(`Unsupported transport: ${this.config.transport}`);
|
|
51
|
+
}
|
|
52
|
+
const initResult = await this.initialize();
|
|
53
|
+
this.state.capabilities = initResult.capabilities;
|
|
54
|
+
this.state.serverInfo = initResult.serverInfo;
|
|
55
|
+
await this.refreshTools();
|
|
56
|
+
this.state.status = "connected";
|
|
57
|
+
this.state.lastActivity = /* @__PURE__ */ new Date();
|
|
58
|
+
console.log(`[MCP] Connected to ${this.name} (${this.state.tools.length} tools)`);
|
|
59
|
+
} catch (error) {
|
|
60
|
+
this.state.status = "error";
|
|
61
|
+
this.state.lastError = error instanceof Error ? error.message : "Unknown error";
|
|
62
|
+
throw error;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
async disconnect() {
|
|
66
|
+
if (this.state.status === "disconnected") {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
for (const [, pending] of this.pendingRequests) {
|
|
70
|
+
clearTimeout(pending.timeout);
|
|
71
|
+
pending.reject(new Error("Connection closed"));
|
|
72
|
+
}
|
|
73
|
+
this.pendingRequests.clear();
|
|
74
|
+
if (this.process) {
|
|
75
|
+
this.process.kill();
|
|
76
|
+
this.process = null;
|
|
77
|
+
}
|
|
78
|
+
this.state.status = "disconnected";
|
|
79
|
+
this.state.tools = [];
|
|
80
|
+
console.log(`[MCP] Disconnected from ${this.name}`);
|
|
81
|
+
}
|
|
82
|
+
// ============================================
|
|
83
|
+
// STDIO TRANSPORT
|
|
84
|
+
// ============================================
|
|
85
|
+
async connectStdio() {
|
|
86
|
+
if (!this.config.command) {
|
|
87
|
+
throw new Error("STDIO transport requires a command");
|
|
88
|
+
}
|
|
89
|
+
const env = {
|
|
90
|
+
...process.env,
|
|
91
|
+
...this.config.env
|
|
92
|
+
};
|
|
93
|
+
this.process = spawn(
|
|
94
|
+
this.config.command,
|
|
95
|
+
this.config.args || [],
|
|
96
|
+
{
|
|
97
|
+
cwd: this.config.cwd || process.cwd(),
|
|
98
|
+
env,
|
|
99
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
100
|
+
}
|
|
101
|
+
);
|
|
102
|
+
this.readStdout();
|
|
103
|
+
this.readStderr();
|
|
104
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
105
|
+
}
|
|
106
|
+
readStdout() {
|
|
107
|
+
if (!this.process?.stdout) return;
|
|
108
|
+
this.process.stdout.on("data", (chunk) => {
|
|
109
|
+
this.messageBuffer += chunk.toString();
|
|
110
|
+
this.processMessageBuffer();
|
|
111
|
+
});
|
|
112
|
+
this.process.stdout.on("error", (error) => {
|
|
113
|
+
if (this.state.status === "connected" || this.state.status === "connecting") {
|
|
114
|
+
console.error(`[MCP] ${this.name} stdout error:`, error);
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
readStderr() {
|
|
119
|
+
if (!this.process?.stderr) return;
|
|
120
|
+
this.process.stderr.on("data", (chunk) => {
|
|
121
|
+
const text = chunk.toString();
|
|
122
|
+
if (text.trim()) {
|
|
123
|
+
console.log(`[MCP] ${this.name} stderr: ${text.trim()}`);
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
processMessageBuffer() {
|
|
128
|
+
const lines = this.messageBuffer.split("\n");
|
|
129
|
+
this.messageBuffer = lines.pop() || "";
|
|
130
|
+
for (const line of lines) {
|
|
131
|
+
if (!line.trim()) continue;
|
|
132
|
+
try {
|
|
133
|
+
const message = JSON.parse(line);
|
|
134
|
+
this.handleResponse(message);
|
|
135
|
+
} catch {
|
|
136
|
+
console.warn(`[MCP] ${this.name} failed to parse message: ${line.slice(0, 100)}`);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
handleResponse(response) {
|
|
141
|
+
if (response.id === void 0 || response.id === null) {
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
const pending = this.pendingRequests.get(response.id);
|
|
145
|
+
if (!pending) {
|
|
146
|
+
console.warn(`[MCP] ${this.name} received response for unknown request: ${response.id}`);
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
clearTimeout(pending.timeout);
|
|
150
|
+
this.pendingRequests.delete(response.id);
|
|
151
|
+
if (response.error) {
|
|
152
|
+
pending.reject(new Error(response.error.message));
|
|
153
|
+
} else {
|
|
154
|
+
pending.resolve(response.result);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
async sendRequest(method, params) {
|
|
158
|
+
if (!this.process?.stdin) {
|
|
159
|
+
throw new Error("Not connected");
|
|
160
|
+
}
|
|
161
|
+
const id = nanoid();
|
|
162
|
+
const request = {
|
|
163
|
+
jsonrpc: "2.0",
|
|
164
|
+
id,
|
|
165
|
+
method,
|
|
166
|
+
params
|
|
167
|
+
};
|
|
168
|
+
const promise = new Promise((resolve, reject) => {
|
|
169
|
+
const timeout = setTimeout(() => {
|
|
170
|
+
this.pendingRequests.delete(id);
|
|
171
|
+
reject(new Error(`Request timeout: ${method}`));
|
|
172
|
+
}, this.requestTimeout);
|
|
173
|
+
this.pendingRequests.set(id, { resolve, reject, timeout });
|
|
174
|
+
});
|
|
175
|
+
const data = JSON.stringify(request) + "\n";
|
|
176
|
+
this.process.stdin.write(data);
|
|
177
|
+
return promise;
|
|
178
|
+
}
|
|
179
|
+
// ============================================
|
|
180
|
+
// HTTP+SSE TRANSPORT
|
|
181
|
+
// ============================================
|
|
182
|
+
async connectHttpSse() {
|
|
183
|
+
if (!this.config.url) {
|
|
184
|
+
throw new Error("HTTP+SSE transport requires a URL");
|
|
185
|
+
}
|
|
186
|
+
try {
|
|
187
|
+
const response = await fetch(`${this.config.url}/health`, {
|
|
188
|
+
headers: this.config.headers
|
|
189
|
+
});
|
|
190
|
+
if (!response.ok) {
|
|
191
|
+
throw new Error(`Server health check failed: ${response.status}`);
|
|
192
|
+
}
|
|
193
|
+
} catch (error) {
|
|
194
|
+
const response = await fetch(this.config.url, {
|
|
195
|
+
method: "POST",
|
|
196
|
+
headers: {
|
|
197
|
+
"Content-Type": "application/json",
|
|
198
|
+
...this.config.headers
|
|
199
|
+
},
|
|
200
|
+
body: JSON.stringify({
|
|
201
|
+
jsonrpc: "2.0",
|
|
202
|
+
id: "ping",
|
|
203
|
+
method: "ping"
|
|
204
|
+
})
|
|
205
|
+
});
|
|
206
|
+
if (!response.ok) {
|
|
207
|
+
throw new Error(`Server connection failed: ${response.status}`);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
async sendHttpRequest(method, params) {
|
|
212
|
+
if (!this.config.url) {
|
|
213
|
+
throw new Error("HTTP+SSE transport requires a URL");
|
|
214
|
+
}
|
|
215
|
+
const request = {
|
|
216
|
+
jsonrpc: "2.0",
|
|
217
|
+
id: nanoid(),
|
|
218
|
+
method,
|
|
219
|
+
params
|
|
220
|
+
};
|
|
221
|
+
const response = await fetch(this.config.url, {
|
|
222
|
+
method: "POST",
|
|
223
|
+
headers: {
|
|
224
|
+
"Content-Type": "application/json",
|
|
225
|
+
...this.config.headers
|
|
226
|
+
},
|
|
227
|
+
body: JSON.stringify(request)
|
|
228
|
+
});
|
|
229
|
+
if (!response.ok) {
|
|
230
|
+
throw new Error(`HTTP request failed: ${response.status}`);
|
|
231
|
+
}
|
|
232
|
+
const result = await response.json();
|
|
233
|
+
if (result.error) {
|
|
234
|
+
throw new Error(result.error.message);
|
|
235
|
+
}
|
|
236
|
+
return result.result;
|
|
237
|
+
}
|
|
238
|
+
// ============================================
|
|
239
|
+
// MCP PROTOCOL METHODS
|
|
240
|
+
// ============================================
|
|
241
|
+
async initialize() {
|
|
242
|
+
const params = {
|
|
243
|
+
protocolVersion: MCP_PROTOCOL_VERSION,
|
|
244
|
+
capabilities: {
|
|
245
|
+
roots: { listChanged: true }
|
|
246
|
+
},
|
|
247
|
+
clientInfo: {
|
|
248
|
+
name: "OpenSentinel",
|
|
249
|
+
version: "2.0.0"
|
|
250
|
+
}
|
|
251
|
+
};
|
|
252
|
+
const result = await this.request("initialize", params);
|
|
253
|
+
await this.notify("notifications/initialized", {});
|
|
254
|
+
return result;
|
|
255
|
+
}
|
|
256
|
+
async refreshTools() {
|
|
257
|
+
const result = await this.request("tools/list", {});
|
|
258
|
+
this.state.tools = result.tools || [];
|
|
259
|
+
return this.state.tools;
|
|
260
|
+
}
|
|
261
|
+
async callTool(name, args) {
|
|
262
|
+
const params = {
|
|
263
|
+
name,
|
|
264
|
+
arguments: args
|
|
265
|
+
};
|
|
266
|
+
try {
|
|
267
|
+
const result = await this.request("tools/call", params);
|
|
268
|
+
this.state.lastActivity = /* @__PURE__ */ new Date();
|
|
269
|
+
const textContent = result.content.filter((c) => c.type === "text" && c.text).map((c) => c.text).join("\n");
|
|
270
|
+
return {
|
|
271
|
+
success: !result.isError,
|
|
272
|
+
output: textContent || JSON.stringify(result.content),
|
|
273
|
+
isError: result.isError
|
|
274
|
+
};
|
|
275
|
+
} catch (error) {
|
|
276
|
+
return {
|
|
277
|
+
success: false,
|
|
278
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
// ============================================
|
|
283
|
+
// GENERIC REQUEST/NOTIFY
|
|
284
|
+
// ============================================
|
|
285
|
+
async request(method, params) {
|
|
286
|
+
if (this.config.transport === "stdio") {
|
|
287
|
+
return this.sendRequest(method, params);
|
|
288
|
+
} else {
|
|
289
|
+
return this.sendHttpRequest(method, params);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
async notify(method, params) {
|
|
293
|
+
if (this.config.transport === "stdio" && this.process?.stdin) {
|
|
294
|
+
const notification = {
|
|
295
|
+
jsonrpc: "2.0",
|
|
296
|
+
method,
|
|
297
|
+
params
|
|
298
|
+
};
|
|
299
|
+
this.process.stdin.write(JSON.stringify(notification) + "\n");
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
// ============================================
|
|
303
|
+
// STATE ACCESS
|
|
304
|
+
// ============================================
|
|
305
|
+
getState() {
|
|
306
|
+
return { ...this.state };
|
|
307
|
+
}
|
|
308
|
+
};
|
|
309
|
+
|
|
310
|
+
// src/core/mcp/registry.ts
|
|
311
|
+
var MCPRegistry = class {
|
|
312
|
+
clients = /* @__PURE__ */ new Map();
|
|
313
|
+
config;
|
|
314
|
+
defaultTimeout;
|
|
315
|
+
constructor(config) {
|
|
316
|
+
this.config = config;
|
|
317
|
+
this.defaultTimeout = config.settings?.timeout || 3e4;
|
|
318
|
+
}
|
|
319
|
+
// ============================================
|
|
320
|
+
// INITIALIZATION
|
|
321
|
+
// ============================================
|
|
322
|
+
/**
|
|
323
|
+
* Connect to all enabled MCP servers
|
|
324
|
+
*/
|
|
325
|
+
async initialize() {
|
|
326
|
+
const enabledServers = this.config.servers.filter((s) => s.enabled);
|
|
327
|
+
console.log(`[MCP] Initializing ${enabledServers.length} server(s)...`);
|
|
328
|
+
const results = await Promise.allSettled(
|
|
329
|
+
enabledServers.map((config) => this.connectServer(config))
|
|
330
|
+
);
|
|
331
|
+
results.forEach((result, index) => {
|
|
332
|
+
if (result.status === "rejected") {
|
|
333
|
+
console.error(
|
|
334
|
+
`[MCP] Failed to connect to ${enabledServers[index].name}:`,
|
|
335
|
+
result.reason
|
|
336
|
+
);
|
|
337
|
+
}
|
|
338
|
+
});
|
|
339
|
+
const connected = results.filter((r) => r.status === "fulfilled").length;
|
|
340
|
+
console.log(`[MCP] Connected to ${connected}/${enabledServers.length} servers`);
|
|
341
|
+
}
|
|
342
|
+
/**
|
|
343
|
+
* Connect to a single MCP server
|
|
344
|
+
*/
|
|
345
|
+
async connectServer(config) {
|
|
346
|
+
if (this.clients.has(config.id)) {
|
|
347
|
+
throw new Error(`Server ${config.id} already connected`);
|
|
348
|
+
}
|
|
349
|
+
const client = new MCPClient(config, this.defaultTimeout);
|
|
350
|
+
await client.connect();
|
|
351
|
+
this.clients.set(config.id, client);
|
|
352
|
+
}
|
|
353
|
+
/**
|
|
354
|
+
* Disconnect from a specific server
|
|
355
|
+
*/
|
|
356
|
+
async disconnectServer(id) {
|
|
357
|
+
const client = this.clients.get(id);
|
|
358
|
+
if (client) {
|
|
359
|
+
await client.disconnect();
|
|
360
|
+
this.clients.delete(id);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
/**
|
|
364
|
+
* Disconnect from all servers
|
|
365
|
+
*/
|
|
366
|
+
async shutdown() {
|
|
367
|
+
console.log("[MCP] Shutting down all connections...");
|
|
368
|
+
await Promise.all(
|
|
369
|
+
Array.from(this.clients.values()).map((client) => client.disconnect())
|
|
370
|
+
);
|
|
371
|
+
this.clients.clear();
|
|
372
|
+
}
|
|
373
|
+
// ============================================
|
|
374
|
+
// TOOL MANAGEMENT
|
|
375
|
+
// ============================================
|
|
376
|
+
/**
|
|
377
|
+
* Get all tools from all connected servers
|
|
378
|
+
* Tools are prefixed with "mcp_{serverId}_" for routing
|
|
379
|
+
*/
|
|
380
|
+
getAllTools() {
|
|
381
|
+
const tools = [];
|
|
382
|
+
for (const [serverId, client] of this.clients) {
|
|
383
|
+
if (client.status === "connected") {
|
|
384
|
+
for (const tool of client.tools) {
|
|
385
|
+
tools.push({ serverId, tool });
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
return tools;
|
|
390
|
+
}
|
|
391
|
+
/**
|
|
392
|
+
* Get tools from a specific server
|
|
393
|
+
*/
|
|
394
|
+
getServerTools(serverId) {
|
|
395
|
+
const client = this.clients.get(serverId);
|
|
396
|
+
return client?.tools || [];
|
|
397
|
+
}
|
|
398
|
+
/**
|
|
399
|
+
* Call a tool on a specific server
|
|
400
|
+
*/
|
|
401
|
+
async callTool(serverId, toolName, args) {
|
|
402
|
+
const client = this.clients.get(serverId);
|
|
403
|
+
if (!client) {
|
|
404
|
+
return {
|
|
405
|
+
success: false,
|
|
406
|
+
error: `MCP server not found: ${serverId}`
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
if (client.status !== "connected") {
|
|
410
|
+
return {
|
|
411
|
+
success: false,
|
|
412
|
+
error: `MCP server not connected: ${serverId}`
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
console.log(`[MCP] Calling ${serverId}:${toolName}`);
|
|
416
|
+
return client.callTool(toolName, args);
|
|
417
|
+
}
|
|
418
|
+
// ============================================
|
|
419
|
+
// SERVER MANAGEMENT
|
|
420
|
+
// ============================================
|
|
421
|
+
/**
|
|
422
|
+
* Get status of all servers
|
|
423
|
+
*/
|
|
424
|
+
getServerStates() {
|
|
425
|
+
return Array.from(this.clients.values()).map((client) => client.getState());
|
|
426
|
+
}
|
|
427
|
+
/**
|
|
428
|
+
* Get status of a specific server
|
|
429
|
+
*/
|
|
430
|
+
getServerState(serverId) {
|
|
431
|
+
return this.clients.get(serverId)?.getState();
|
|
432
|
+
}
|
|
433
|
+
/**
|
|
434
|
+
* Check if a server is connected
|
|
435
|
+
*/
|
|
436
|
+
isConnected(serverId) {
|
|
437
|
+
const client = this.clients.get(serverId);
|
|
438
|
+
return client?.status === "connected";
|
|
439
|
+
}
|
|
440
|
+
/**
|
|
441
|
+
* Refresh tools from all connected servers
|
|
442
|
+
*/
|
|
443
|
+
async refreshAllTools() {
|
|
444
|
+
await Promise.all(
|
|
445
|
+
Array.from(this.clients.values()).filter((client) => client.status === "connected").map((client) => client.refreshTools())
|
|
446
|
+
);
|
|
447
|
+
}
|
|
448
|
+
/**
|
|
449
|
+
* Add a new server configuration and optionally connect
|
|
450
|
+
*/
|
|
451
|
+
async addServer(config, connect = true) {
|
|
452
|
+
if (this.clients.has(config.id)) {
|
|
453
|
+
throw new Error(`Server ${config.id} already exists`);
|
|
454
|
+
}
|
|
455
|
+
this.config.servers.push(config);
|
|
456
|
+
if (connect && config.enabled) {
|
|
457
|
+
await this.connectServer(config);
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
/**
|
|
461
|
+
* Remove a server
|
|
462
|
+
*/
|
|
463
|
+
async removeServer(serverId) {
|
|
464
|
+
await this.disconnectServer(serverId);
|
|
465
|
+
this.config.servers = this.config.servers.filter((s) => s.id !== serverId);
|
|
466
|
+
}
|
|
467
|
+
/**
|
|
468
|
+
* Get the current configuration
|
|
469
|
+
*/
|
|
470
|
+
getConfig() {
|
|
471
|
+
return { ...this.config };
|
|
472
|
+
}
|
|
473
|
+
/**
|
|
474
|
+
* Get count of connected servers
|
|
475
|
+
*/
|
|
476
|
+
get connectedCount() {
|
|
477
|
+
return Array.from(this.clients.values()).filter(
|
|
478
|
+
(c) => c.status === "connected"
|
|
479
|
+
).length;
|
|
480
|
+
}
|
|
481
|
+
/**
|
|
482
|
+
* Get total tool count across all servers
|
|
483
|
+
*/
|
|
484
|
+
get totalToolCount() {
|
|
485
|
+
return this.getAllTools().length;
|
|
486
|
+
}
|
|
487
|
+
};
|
|
488
|
+
async function loadMCPConfig(path) {
|
|
489
|
+
try {
|
|
490
|
+
const { readFile, access } = await import("fs/promises");
|
|
491
|
+
try {
|
|
492
|
+
await access(path);
|
|
493
|
+
} catch {
|
|
494
|
+
console.log(`[MCP] Config file not found: ${path}, using empty config`);
|
|
495
|
+
return { servers: [] };
|
|
496
|
+
}
|
|
497
|
+
const content = await readFile(path, "utf-8");
|
|
498
|
+
const config = JSON.parse(content);
|
|
499
|
+
if (!Array.isArray(config.servers)) {
|
|
500
|
+
console.warn("[MCP] Invalid config: servers must be an array");
|
|
501
|
+
return { servers: [] };
|
|
502
|
+
}
|
|
503
|
+
return config;
|
|
504
|
+
} catch (error) {
|
|
505
|
+
console.error("[MCP] Failed to load config:", error);
|
|
506
|
+
return { servers: [] };
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
async function initMCPRegistry(configPath) {
|
|
510
|
+
const config = await loadMCPConfig(configPath);
|
|
511
|
+
const registry = new MCPRegistry(config);
|
|
512
|
+
await registry.initialize();
|
|
513
|
+
return registry;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
// src/core/mcp/tool-bridge.ts
|
|
517
|
+
var MCP_TOOL_PREFIX = "mcp_";
|
|
518
|
+
function createMCPToolName(serverId, toolName) {
|
|
519
|
+
const safeServerId = serverId.replace(/[^a-zA-Z0-9_]/g, "_");
|
|
520
|
+
const safeToolName = toolName.replace(/[^a-zA-Z0-9_]/g, "_");
|
|
521
|
+
return `${MCP_TOOL_PREFIX}${safeServerId}__${safeToolName}`;
|
|
522
|
+
}
|
|
523
|
+
function isMCPTool(name) {
|
|
524
|
+
return name.startsWith(MCP_TOOL_PREFIX);
|
|
525
|
+
}
|
|
526
|
+
function parseMCPToolName(name) {
|
|
527
|
+
if (!isMCPTool(name)) {
|
|
528
|
+
return null;
|
|
529
|
+
}
|
|
530
|
+
const withoutPrefix = name.slice(MCP_TOOL_PREFIX.length);
|
|
531
|
+
const separatorIndex = withoutPrefix.indexOf("__");
|
|
532
|
+
if (separatorIndex === -1) {
|
|
533
|
+
return null;
|
|
534
|
+
}
|
|
535
|
+
return {
|
|
536
|
+
serverId: withoutPrefix.slice(0, separatorIndex),
|
|
537
|
+
toolName: withoutPrefix.slice(separatorIndex + 2)
|
|
538
|
+
};
|
|
539
|
+
}
|
|
540
|
+
function convertProperty(prop) {
|
|
541
|
+
const result = {
|
|
542
|
+
type: prop.type
|
|
543
|
+
};
|
|
544
|
+
if (prop.description) {
|
|
545
|
+
result.description = prop.description;
|
|
546
|
+
}
|
|
547
|
+
if (prop.enum) {
|
|
548
|
+
result.enum = prop.enum;
|
|
549
|
+
}
|
|
550
|
+
if (prop.items) {
|
|
551
|
+
result.items = convertProperty(prop.items);
|
|
552
|
+
}
|
|
553
|
+
if (prop.properties) {
|
|
554
|
+
result.properties = Object.fromEntries(
|
|
555
|
+
Object.entries(prop.properties).map(([key, value]) => [
|
|
556
|
+
key,
|
|
557
|
+
convertProperty(value)
|
|
558
|
+
])
|
|
559
|
+
);
|
|
560
|
+
}
|
|
561
|
+
if (prop.required) {
|
|
562
|
+
result.required = prop.required;
|
|
563
|
+
}
|
|
564
|
+
return result;
|
|
565
|
+
}
|
|
566
|
+
function mcpToolToAnthropicTool(serverId, serverName, tool) {
|
|
567
|
+
const properties = {};
|
|
568
|
+
if (tool.inputSchema.properties) {
|
|
569
|
+
for (const [key, value] of Object.entries(tool.inputSchema.properties)) {
|
|
570
|
+
properties[key] = convertProperty(value);
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
const inputSchema = {
|
|
574
|
+
type: "object",
|
|
575
|
+
properties,
|
|
576
|
+
required: tool.inputSchema.required || []
|
|
577
|
+
};
|
|
578
|
+
const name = createMCPToolName(serverId, tool.name);
|
|
579
|
+
const description = tool.description ? `[${serverName}] ${tool.description}` : `[${serverName}] ${tool.name}`;
|
|
580
|
+
return {
|
|
581
|
+
name,
|
|
582
|
+
description,
|
|
583
|
+
input_schema: inputSchema
|
|
584
|
+
};
|
|
585
|
+
}
|
|
586
|
+
function mcpToolsToAnthropicTools(registry) {
|
|
587
|
+
const tools = [];
|
|
588
|
+
const serverStates = registry.getServerStates();
|
|
589
|
+
for (const state of serverStates) {
|
|
590
|
+
if (state.status !== "connected") continue;
|
|
591
|
+
const serverName = state.serverInfo?.name || state.config.name;
|
|
592
|
+
for (const tool of state.tools) {
|
|
593
|
+
tools.push(mcpToolToAnthropicTool(state.config.id, serverName, tool));
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
return tools;
|
|
597
|
+
}
|
|
598
|
+
async function executeMCPTool(registry, toolName, args) {
|
|
599
|
+
const parsed = parseMCPToolName(toolName);
|
|
600
|
+
if (!parsed) {
|
|
601
|
+
return {
|
|
602
|
+
success: false,
|
|
603
|
+
error: `Invalid MCP tool name: ${toolName}`
|
|
604
|
+
};
|
|
605
|
+
}
|
|
606
|
+
return registry.callTool(parsed.serverId, parsed.toolName, args);
|
|
607
|
+
}
|
|
608
|
+
function getMCPToolSummary(registry) {
|
|
609
|
+
const states = registry.getServerStates();
|
|
610
|
+
const lines = [];
|
|
611
|
+
for (const state of states) {
|
|
612
|
+
const status = state.status === "connected" ? "\u2713" : "\u2717";
|
|
613
|
+
const serverName = state.serverInfo?.name || state.config.name;
|
|
614
|
+
const toolCount = state.tools.length;
|
|
615
|
+
lines.push(` ${status} ${serverName}: ${toolCount} tools`);
|
|
616
|
+
if (state.status === "connected" && state.tools.length > 0) {
|
|
617
|
+
const toolNames = state.tools.map((t) => t.name).join(", ");
|
|
618
|
+
lines.push(` Tools: ${toolNames}`);
|
|
619
|
+
} else if (state.lastError) {
|
|
620
|
+
lines.push(` Error: ${state.lastError}`);
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
if (lines.length === 0) {
|
|
624
|
+
return " No MCP servers configured";
|
|
625
|
+
}
|
|
626
|
+
return lines.join("\n");
|
|
627
|
+
}
|
|
628
|
+
function findMCPTool(registry, toolName) {
|
|
629
|
+
const allTools = registry.getAllTools();
|
|
630
|
+
for (const { serverId, tool } of allTools) {
|
|
631
|
+
if (tool.name === toolName) {
|
|
632
|
+
return { serverId, tool };
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
return null;
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
export {
|
|
639
|
+
MCPClient,
|
|
640
|
+
MCPRegistry,
|
|
641
|
+
loadMCPConfig,
|
|
642
|
+
initMCPRegistry,
|
|
643
|
+
createMCPToolName,
|
|
644
|
+
isMCPTool,
|
|
645
|
+
parseMCPToolName,
|
|
646
|
+
mcpToolToAnthropicTool,
|
|
647
|
+
mcpToolsToAnthropicTools,
|
|
648
|
+
executeMCPTool,
|
|
649
|
+
getMCPToolSummary,
|
|
650
|
+
findMCPTool
|
|
651
|
+
};
|
|
652
|
+
//# sourceMappingURL=chunk-L3F43VPB.js.map
|