@cardor/heimdall-mcp 0.1.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 +391 -0
- package/bin/heimdall-mcp.js +2 -0
- package/dist/MySqlStore-VOXUWDAJ.js +111 -0
- package/dist/MySqlStore-VOXUWDAJ.js.map +1 -0
- package/dist/PostgresStore-QUSLDMPH.js +111 -0
- package/dist/PostgresStore-QUSLDMPH.js.map +1 -0
- package/dist/SqliteStore-HMGNDB3O.js +109 -0
- package/dist/SqliteStore-HMGNDB3O.js.map +1 -0
- package/dist/chunk-VOBMDW4J.js +660 -0
- package/dist/chunk-VOBMDW4J.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +42 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +147 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -0
- package/package.json +53 -0
|
@@ -0,0 +1,660 @@
|
|
|
1
|
+
// src/proxy/McpProxy.ts
|
|
2
|
+
import { EventEmitter } from "events";
|
|
3
|
+
|
|
4
|
+
// src/interceptor/ForwardInterceptor.ts
|
|
5
|
+
var ForwardInterceptor = class {
|
|
6
|
+
constructor(outbound) {
|
|
7
|
+
this.outbound = outbound;
|
|
8
|
+
}
|
|
9
|
+
outbound;
|
|
10
|
+
name = "ForwardInterceptor";
|
|
11
|
+
async intercept(request, _context, _next) {
|
|
12
|
+
return this.outbound.sendAndWait(request);
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
// src/interceptor/InterceptorPipeline.ts
|
|
17
|
+
import { randomBytes } from "crypto";
|
|
18
|
+
var InterceptorPipeline = class {
|
|
19
|
+
interceptors = [];
|
|
20
|
+
use(interceptor) {
|
|
21
|
+
this.interceptors.push(interceptor);
|
|
22
|
+
return this;
|
|
23
|
+
}
|
|
24
|
+
async run(request) {
|
|
25
|
+
const context = {
|
|
26
|
+
startedAt: /* @__PURE__ */ new Date(),
|
|
27
|
+
traceId: randomBytes(16).toString("hex"),
|
|
28
|
+
spanId: randomBytes(8).toString("hex"),
|
|
29
|
+
metadata: {}
|
|
30
|
+
};
|
|
31
|
+
let index = 0;
|
|
32
|
+
const next = async () => {
|
|
33
|
+
const interceptor = this.interceptors[index++];
|
|
34
|
+
if (!interceptor) throw new Error("Pipeline ended without ForwardInterceptor");
|
|
35
|
+
return interceptor.intercept(request, context, next);
|
|
36
|
+
};
|
|
37
|
+
return next();
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
// src/interceptor/TelemetryInterceptor.ts
|
|
42
|
+
var TelemetryInterceptor = class {
|
|
43
|
+
constructor(collector) {
|
|
44
|
+
this.collector = collector;
|
|
45
|
+
}
|
|
46
|
+
collector;
|
|
47
|
+
name = "TelemetryInterceptor";
|
|
48
|
+
async intercept(request, context, next) {
|
|
49
|
+
context.startedAt = /* @__PURE__ */ new Date();
|
|
50
|
+
let response;
|
|
51
|
+
let status = "ok";
|
|
52
|
+
try {
|
|
53
|
+
response = await next();
|
|
54
|
+
if (response.error) status = "error";
|
|
55
|
+
} catch (err) {
|
|
56
|
+
status = "error";
|
|
57
|
+
response = {
|
|
58
|
+
jsonrpc: "2.0",
|
|
59
|
+
id: request.id,
|
|
60
|
+
error: { code: -32603, message: String(err) }
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
const endedAt = /* @__PURE__ */ new Date();
|
|
64
|
+
const durationMs = endedAt.getTime() - context.startedAt.getTime();
|
|
65
|
+
await this.collector.record({
|
|
66
|
+
traceId: context.traceId,
|
|
67
|
+
spanId: context.spanId,
|
|
68
|
+
request,
|
|
69
|
+
response,
|
|
70
|
+
status,
|
|
71
|
+
startedAt: context.startedAt,
|
|
72
|
+
endedAt,
|
|
73
|
+
durationMs
|
|
74
|
+
});
|
|
75
|
+
return response;
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
// src/telemetry/LogEmitter.ts
|
|
80
|
+
import pc from "picocolors";
|
|
81
|
+
var LogEmitter = class {
|
|
82
|
+
enabled;
|
|
83
|
+
constructor(enabled = true) {
|
|
84
|
+
this.enabled = enabled;
|
|
85
|
+
}
|
|
86
|
+
info(message, meta) {
|
|
87
|
+
this.emit("info", message, meta);
|
|
88
|
+
}
|
|
89
|
+
warn(message, meta) {
|
|
90
|
+
this.emit("warn", message, meta);
|
|
91
|
+
}
|
|
92
|
+
error(message, meta) {
|
|
93
|
+
this.emit("error", message, meta);
|
|
94
|
+
}
|
|
95
|
+
debug(message, meta) {
|
|
96
|
+
this.emit("debug", message, meta);
|
|
97
|
+
}
|
|
98
|
+
emit(level, message, meta) {
|
|
99
|
+
if (!this.enabled) return;
|
|
100
|
+
const ts = (/* @__PURE__ */ new Date()).toISOString();
|
|
101
|
+
const prefix = {
|
|
102
|
+
info: pc.blue("[info]"),
|
|
103
|
+
warn: pc.yellow("[warn]"),
|
|
104
|
+
error: pc.red("[error]"),
|
|
105
|
+
debug: pc.gray("[debug]")
|
|
106
|
+
}[level];
|
|
107
|
+
const metaStr = meta ? " " + JSON.stringify(meta) : "";
|
|
108
|
+
process.stderr.write(`${ts} ${prefix} ${message}${metaStr}
|
|
109
|
+
`);
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
// src/telemetry/McpSpanBuilder.ts
|
|
114
|
+
import { randomBytes as randomBytes2 } from "crypto";
|
|
115
|
+
var METHOD_TO_SPAN_NAME = {
|
|
116
|
+
initialize: "mcp.initialize",
|
|
117
|
+
"tools/list": "mcp.tools.list",
|
|
118
|
+
"tools/call": "mcp.tool.call",
|
|
119
|
+
"resources/read": "mcp.resource.read",
|
|
120
|
+
"resources/list": "mcp.resources.list",
|
|
121
|
+
"prompts/get": "mcp.prompt.get",
|
|
122
|
+
"prompts/list": "mcp.prompts.list",
|
|
123
|
+
shutdown: "mcp.shutdown"
|
|
124
|
+
};
|
|
125
|
+
var McpSpanBuilder = class {
|
|
126
|
+
build(input) {
|
|
127
|
+
const method = input.request.method ?? "unknown";
|
|
128
|
+
const spanName = METHOD_TO_SPAN_NAME[method] ?? `mcp.${method}`;
|
|
129
|
+
const params = input.request.params;
|
|
130
|
+
const result = input.response.result;
|
|
131
|
+
const attributes = this.buildAttributes(method, params, result, input);
|
|
132
|
+
const events = this.buildEvents(method, params, result);
|
|
133
|
+
return {
|
|
134
|
+
id: `${input.traceId}-${input.spanId}`,
|
|
135
|
+
traceId: input.traceId,
|
|
136
|
+
spanId: input.spanId,
|
|
137
|
+
name: spanName,
|
|
138
|
+
status: input.status,
|
|
139
|
+
startedAt: input.startedAt,
|
|
140
|
+
endedAt: input.endedAt,
|
|
141
|
+
durationMs: input.durationMs,
|
|
142
|
+
attributes,
|
|
143
|
+
events
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
buildAttributes(method, params, result, input) {
|
|
147
|
+
const base = {
|
|
148
|
+
"gen_ai.operation.name": method,
|
|
149
|
+
"mcp.duration_ms": input.durationMs
|
|
150
|
+
};
|
|
151
|
+
if (method === "tools/call") {
|
|
152
|
+
const name = params?.name;
|
|
153
|
+
base["gen_ai.tool.name"] = name;
|
|
154
|
+
base["gen_ai.tool.call.id"] = randomBytes2(8).toString("hex");
|
|
155
|
+
}
|
|
156
|
+
if (method === "tools/list") {
|
|
157
|
+
const tools = result?.tools;
|
|
158
|
+
base["mcp.tools_count"] = Array.isArray(tools) ? tools.length : 0;
|
|
159
|
+
}
|
|
160
|
+
if (method === "resources/read") {
|
|
161
|
+
base["url.full"] = params?.uri;
|
|
162
|
+
}
|
|
163
|
+
if (method === "prompts/get") {
|
|
164
|
+
base["mcp.prompt_name"] = params?.name;
|
|
165
|
+
}
|
|
166
|
+
if (method === "initialize") {
|
|
167
|
+
const p = params;
|
|
168
|
+
const r = result;
|
|
169
|
+
base["mcp.client_version"] = p?.clientInfo?.version;
|
|
170
|
+
base["mcp.server_version"] = r?.serverInfo?.version;
|
|
171
|
+
base["mcp.client_capabilities"] = JSON.stringify(p?.capabilities ?? {});
|
|
172
|
+
base["mcp.server_capabilities"] = JSON.stringify(r?.capabilities ?? {});
|
|
173
|
+
}
|
|
174
|
+
return base;
|
|
175
|
+
}
|
|
176
|
+
buildEvents(method, params, result) {
|
|
177
|
+
const events = [];
|
|
178
|
+
if (method === "tools/call") {
|
|
179
|
+
events.push({ name: "tool.input", timestamp: /* @__PURE__ */ new Date(), attributes: { body: JSON.stringify(params) } });
|
|
180
|
+
if (result) events.push({ name: "tool.output", timestamp: /* @__PURE__ */ new Date(), attributes: { body: JSON.stringify(result) } });
|
|
181
|
+
}
|
|
182
|
+
if (method === "tools/list" && result) {
|
|
183
|
+
events.push({ name: "tools.list", timestamp: /* @__PURE__ */ new Date(), attributes: { tools: JSON.stringify(result.tools ?? []) } });
|
|
184
|
+
}
|
|
185
|
+
if (method === "prompts/get" && result) {
|
|
186
|
+
events.push({ name: "prompt.rendered", timestamp: /* @__PURE__ */ new Date(), attributes: { body: JSON.stringify(result) } });
|
|
187
|
+
}
|
|
188
|
+
return events;
|
|
189
|
+
}
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
// src/telemetry/MetricsRecorder.ts
|
|
193
|
+
var MetricsRecorder = class {
|
|
194
|
+
metrics = /* @__PURE__ */ new Map();
|
|
195
|
+
record(toolName, status, durationMs) {
|
|
196
|
+
const existing = this.metrics.get(toolName) ?? { callCount: 0, errorCount: 0, totalDurationMs: 0 };
|
|
197
|
+
existing.callCount++;
|
|
198
|
+
if (status === "error") existing.errorCount++;
|
|
199
|
+
existing.totalDurationMs += durationMs;
|
|
200
|
+
this.metrics.set(toolName, existing);
|
|
201
|
+
}
|
|
202
|
+
getAll() {
|
|
203
|
+
const result = /* @__PURE__ */ new Map();
|
|
204
|
+
for (const [name, m] of this.metrics) {
|
|
205
|
+
result.set(name, {
|
|
206
|
+
...m,
|
|
207
|
+
avgDurationMs: m.callCount > 0 ? m.totalDurationMs / m.callCount : 0
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
return result;
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
// src/telemetry/TelemetryCollector.ts
|
|
215
|
+
var TelemetryCollector = class {
|
|
216
|
+
constructor(store) {
|
|
217
|
+
this.store = store;
|
|
218
|
+
}
|
|
219
|
+
store;
|
|
220
|
+
spanBuilder = new McpSpanBuilder();
|
|
221
|
+
metrics = new MetricsRecorder();
|
|
222
|
+
log = new LogEmitter();
|
|
223
|
+
async record(input) {
|
|
224
|
+
const span = this.spanBuilder.build(input);
|
|
225
|
+
const toolName = input.request.params?.name;
|
|
226
|
+
if (input.request.method === "tools/call" && toolName) {
|
|
227
|
+
this.metrics.record(toolName, input.status, input.durationMs);
|
|
228
|
+
}
|
|
229
|
+
this.log.debug(`${span.name} [${input.status}] ${input.durationMs}ms`, {
|
|
230
|
+
traceId: span.traceId,
|
|
231
|
+
spanId: span.spanId
|
|
232
|
+
});
|
|
233
|
+
try {
|
|
234
|
+
await this.store.save(span);
|
|
235
|
+
} catch (err) {
|
|
236
|
+
this.log.error("Failed to save span", { err: String(err) });
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
getMetrics() {
|
|
240
|
+
return this.metrics.getAll();
|
|
241
|
+
}
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
// src/proxy/McpProxy.ts
|
|
245
|
+
var McpProxy = class extends EventEmitter {
|
|
246
|
+
constructor(inbound, outbound, store) {
|
|
247
|
+
super();
|
|
248
|
+
this.inbound = inbound;
|
|
249
|
+
this.outbound = outbound;
|
|
250
|
+
const collector = new TelemetryCollector(store);
|
|
251
|
+
this.pipeline = new InterceptorPipeline();
|
|
252
|
+
this.pipeline.use(new TelemetryInterceptor(collector));
|
|
253
|
+
this.pipeline.use(new ForwardInterceptor(outbound));
|
|
254
|
+
}
|
|
255
|
+
inbound;
|
|
256
|
+
outbound;
|
|
257
|
+
pipeline;
|
|
258
|
+
addInterceptor(interceptor) {
|
|
259
|
+
this.pipeline.use(interceptor);
|
|
260
|
+
return this;
|
|
261
|
+
}
|
|
262
|
+
async start() {
|
|
263
|
+
this.inbound.onMessage(async (msg) => {
|
|
264
|
+
try {
|
|
265
|
+
return await this.pipeline.run(msg);
|
|
266
|
+
} catch (err) {
|
|
267
|
+
this.emit("error", err);
|
|
268
|
+
return {
|
|
269
|
+
jsonrpc: "2.0",
|
|
270
|
+
id: msg.id,
|
|
271
|
+
error: { code: -32603, message: String(err) }
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
async stop() {
|
|
277
|
+
await this.inbound.close();
|
|
278
|
+
await this.outbound.close();
|
|
279
|
+
}
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
// src/proxy/ProxyBuilder.ts
|
|
283
|
+
import * as v from "valibot";
|
|
284
|
+
|
|
285
|
+
// src/store/StoreResolver.ts
|
|
286
|
+
import { homedir } from "os";
|
|
287
|
+
function toLibsqlUrl(connectionString) {
|
|
288
|
+
if (connectionString.startsWith("file:") || connectionString === ":memory:") {
|
|
289
|
+
return connectionString;
|
|
290
|
+
}
|
|
291
|
+
const raw = connectionString.replace(/^sqlite:\/\//, "");
|
|
292
|
+
const expanded = raw.startsWith("~/") ? homedir() + raw.slice(1) : raw;
|
|
293
|
+
return `file:${expanded}`;
|
|
294
|
+
}
|
|
295
|
+
var StoreResolver = class {
|
|
296
|
+
static async resolve(connectionString) {
|
|
297
|
+
let store;
|
|
298
|
+
if (connectionString.startsWith("sqlite://") || connectionString.startsWith("file:") || connectionString === ":memory:") {
|
|
299
|
+
const { SqliteStore } = await import("./SqliteStore-HMGNDB3O.js");
|
|
300
|
+
store = new SqliteStore(toLibsqlUrl(connectionString));
|
|
301
|
+
} else if (connectionString.startsWith("postgres://") || connectionString.startsWith("postgresql://")) {
|
|
302
|
+
const { PostgresStore } = await import("./PostgresStore-QUSLDMPH.js");
|
|
303
|
+
store = new PostgresStore(connectionString);
|
|
304
|
+
} else if (connectionString.startsWith("mysql://")) {
|
|
305
|
+
const { MySqlStore } = await import("./MySqlStore-VOXUWDAJ.js");
|
|
306
|
+
store = new MySqlStore(connectionString);
|
|
307
|
+
} else {
|
|
308
|
+
throw new Error(`Unsupported store connection string: "${connectionString}". Expected sqlite://, postgres://, or mysql://`);
|
|
309
|
+
}
|
|
310
|
+
await store.init();
|
|
311
|
+
return store;
|
|
312
|
+
}
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
// src/transport/HttpInbound.ts
|
|
316
|
+
import { createServer } from "http";
|
|
317
|
+
var HttpInbound = class {
|
|
318
|
+
constructor(port, host = "0.0.0.0") {
|
|
319
|
+
this.port = port;
|
|
320
|
+
this.host = host;
|
|
321
|
+
this.server = createServer(async (req, res) => {
|
|
322
|
+
if (req.method !== "POST") {
|
|
323
|
+
res.writeHead(405).end();
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
const chunks = [];
|
|
327
|
+
for await (const chunk of req) chunks.push(chunk);
|
|
328
|
+
const body = Buffer.concat(chunks).toString();
|
|
329
|
+
try {
|
|
330
|
+
const msg = JSON.parse(body);
|
|
331
|
+
const response = this.handler ? await this.handler(msg) : { jsonrpc: "2.0", id: msg.id, result: null };
|
|
332
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
333
|
+
res.end(JSON.stringify(response));
|
|
334
|
+
} catch {
|
|
335
|
+
res.writeHead(400).end();
|
|
336
|
+
}
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
port;
|
|
340
|
+
host;
|
|
341
|
+
handler;
|
|
342
|
+
server;
|
|
343
|
+
async send(_message) {
|
|
344
|
+
}
|
|
345
|
+
onMessage(handler) {
|
|
346
|
+
this.handler = handler;
|
|
347
|
+
this.server.listen(this.port, this.host);
|
|
348
|
+
}
|
|
349
|
+
async close() {
|
|
350
|
+
await new Promise(
|
|
351
|
+
(resolve, reject) => this.server.close((err) => err ? reject(err) : resolve())
|
|
352
|
+
);
|
|
353
|
+
}
|
|
354
|
+
};
|
|
355
|
+
|
|
356
|
+
// src/transport/HttpOutbound.ts
|
|
357
|
+
var HttpOutbound = class {
|
|
358
|
+
constructor(url) {
|
|
359
|
+
this.url = url;
|
|
360
|
+
}
|
|
361
|
+
url;
|
|
362
|
+
async send(message) {
|
|
363
|
+
await fetch(this.url, {
|
|
364
|
+
method: "POST",
|
|
365
|
+
headers: { "Content-Type": "application/json" },
|
|
366
|
+
body: JSON.stringify(message)
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
async sendAndWait(message) {
|
|
370
|
+
const res = await fetch(this.url, {
|
|
371
|
+
method: "POST",
|
|
372
|
+
headers: { "Content-Type": "application/json" },
|
|
373
|
+
body: JSON.stringify(message)
|
|
374
|
+
});
|
|
375
|
+
return res.json();
|
|
376
|
+
}
|
|
377
|
+
onMessage(_handler) {
|
|
378
|
+
}
|
|
379
|
+
async close() {
|
|
380
|
+
}
|
|
381
|
+
};
|
|
382
|
+
|
|
383
|
+
// src/transport/SseInbound.ts
|
|
384
|
+
import { createServer as createServer2 } from "http";
|
|
385
|
+
var SseInbound = class {
|
|
386
|
+
constructor(port, host = "0.0.0.0") {
|
|
387
|
+
this.port = port;
|
|
388
|
+
this.host = host;
|
|
389
|
+
this.server = createServer2(async (req, res) => {
|
|
390
|
+
if (req.url === "/sse" && req.method === "GET") {
|
|
391
|
+
res.writeHead(200, {
|
|
392
|
+
"Content-Type": "text/event-stream",
|
|
393
|
+
"Cache-Control": "no-cache",
|
|
394
|
+
Connection: "keep-alive"
|
|
395
|
+
});
|
|
396
|
+
this.clients.add(res);
|
|
397
|
+
req.on("close", () => this.clients.delete(res));
|
|
398
|
+
return;
|
|
399
|
+
}
|
|
400
|
+
if (req.method === "POST") {
|
|
401
|
+
const chunks = [];
|
|
402
|
+
for await (const chunk of req) chunks.push(chunk);
|
|
403
|
+
try {
|
|
404
|
+
const msg = JSON.parse(Buffer.concat(chunks).toString());
|
|
405
|
+
const response = this.handler ? await this.handler(msg) : { jsonrpc: "2.0", id: msg.id, result: null };
|
|
406
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
407
|
+
res.end(JSON.stringify(response));
|
|
408
|
+
} catch {
|
|
409
|
+
res.writeHead(400).end();
|
|
410
|
+
}
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
res.writeHead(404).end();
|
|
414
|
+
});
|
|
415
|
+
}
|
|
416
|
+
port;
|
|
417
|
+
host;
|
|
418
|
+
handler;
|
|
419
|
+
server;
|
|
420
|
+
clients = /* @__PURE__ */ new Set();
|
|
421
|
+
async send(message) {
|
|
422
|
+
const data = `data: ${JSON.stringify(message)}
|
|
423
|
+
|
|
424
|
+
`;
|
|
425
|
+
for (const client of this.clients) client.write(data);
|
|
426
|
+
}
|
|
427
|
+
onMessage(handler) {
|
|
428
|
+
this.handler = handler;
|
|
429
|
+
this.server.listen(this.port, this.host);
|
|
430
|
+
}
|
|
431
|
+
async close() {
|
|
432
|
+
for (const client of this.clients) client.end();
|
|
433
|
+
await new Promise(
|
|
434
|
+
(resolve, reject) => this.server.close((err) => err ? reject(err) : resolve())
|
|
435
|
+
);
|
|
436
|
+
}
|
|
437
|
+
};
|
|
438
|
+
|
|
439
|
+
// src/transport/SseOutbound.ts
|
|
440
|
+
var SseOutbound = class {
|
|
441
|
+
constructor(url) {
|
|
442
|
+
this.url = url;
|
|
443
|
+
}
|
|
444
|
+
url;
|
|
445
|
+
eventSource;
|
|
446
|
+
pending = /* @__PURE__ */ new Map();
|
|
447
|
+
ensureConnected() {
|
|
448
|
+
if (this.eventSource) return;
|
|
449
|
+
this.eventSource = new EventSource(`${this.url}/sse`);
|
|
450
|
+
this.eventSource.onmessage = (e) => {
|
|
451
|
+
try {
|
|
452
|
+
const msg = JSON.parse(e.data);
|
|
453
|
+
const id = msg.id;
|
|
454
|
+
if (id != null) {
|
|
455
|
+
const resolve = this.pending.get(id);
|
|
456
|
+
if (resolve) {
|
|
457
|
+
this.pending.delete(id);
|
|
458
|
+
resolve(msg);
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
} catch {
|
|
462
|
+
}
|
|
463
|
+
};
|
|
464
|
+
}
|
|
465
|
+
async send(message) {
|
|
466
|
+
await fetch(this.url, {
|
|
467
|
+
method: "POST",
|
|
468
|
+
headers: { "Content-Type": "application/json" },
|
|
469
|
+
body: JSON.stringify(message)
|
|
470
|
+
});
|
|
471
|
+
}
|
|
472
|
+
async sendAndWait(message) {
|
|
473
|
+
this.ensureConnected();
|
|
474
|
+
return new Promise((resolve, reject) => {
|
|
475
|
+
const id = message.id;
|
|
476
|
+
if (id == null) {
|
|
477
|
+
this.send(message).catch(reject);
|
|
478
|
+
resolve({ jsonrpc: "2.0", id: null });
|
|
479
|
+
return;
|
|
480
|
+
}
|
|
481
|
+
this.pending.set(id, resolve);
|
|
482
|
+
this.send(message).catch((err) => {
|
|
483
|
+
this.pending.delete(id);
|
|
484
|
+
reject(err);
|
|
485
|
+
});
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
onMessage(_handler) {
|
|
489
|
+
}
|
|
490
|
+
async close() {
|
|
491
|
+
this.eventSource?.close();
|
|
492
|
+
}
|
|
493
|
+
};
|
|
494
|
+
|
|
495
|
+
// src/transport/StdioInbound.ts
|
|
496
|
+
import { createInterface } from "readline";
|
|
497
|
+
var StdioInbound = class {
|
|
498
|
+
handler;
|
|
499
|
+
async send(message) {
|
|
500
|
+
process.stdout.write(JSON.stringify(message) + "\n");
|
|
501
|
+
}
|
|
502
|
+
onMessage(handler) {
|
|
503
|
+
this.handler = handler;
|
|
504
|
+
const rl = createInterface({ input: process.stdin, terminal: false });
|
|
505
|
+
rl.on("line", async (line) => {
|
|
506
|
+
const trimmed = line.trim();
|
|
507
|
+
if (!trimmed) return;
|
|
508
|
+
try {
|
|
509
|
+
const msg = JSON.parse(trimmed);
|
|
510
|
+
if (this.handler) {
|
|
511
|
+
const response = await this.handler(msg);
|
|
512
|
+
await this.send(response);
|
|
513
|
+
}
|
|
514
|
+
} catch {
|
|
515
|
+
}
|
|
516
|
+
});
|
|
517
|
+
}
|
|
518
|
+
async close() {
|
|
519
|
+
process.stdin.destroy();
|
|
520
|
+
}
|
|
521
|
+
};
|
|
522
|
+
|
|
523
|
+
// src/transport/StdioOutbound.ts
|
|
524
|
+
import { spawn } from "child_process";
|
|
525
|
+
import { createInterface as createInterface2 } from "readline";
|
|
526
|
+
var StdioOutbound = class {
|
|
527
|
+
proc;
|
|
528
|
+
pending = /* @__PURE__ */ new Map();
|
|
529
|
+
constructor(command, args = []) {
|
|
530
|
+
this.proc = spawn(command, args, {
|
|
531
|
+
stdio: ["pipe", "pipe", "inherit"]
|
|
532
|
+
});
|
|
533
|
+
const rl = createInterface2({ input: this.proc.stdout, terminal: false });
|
|
534
|
+
rl.on("line", (line) => {
|
|
535
|
+
const trimmed = line.trim();
|
|
536
|
+
if (!trimmed) return;
|
|
537
|
+
try {
|
|
538
|
+
const msg = JSON.parse(trimmed);
|
|
539
|
+
const id = msg.id;
|
|
540
|
+
if (id != null) {
|
|
541
|
+
const resolve = this.pending.get(id);
|
|
542
|
+
if (resolve) {
|
|
543
|
+
this.pending.delete(id);
|
|
544
|
+
resolve(msg);
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
} catch {
|
|
548
|
+
}
|
|
549
|
+
});
|
|
550
|
+
}
|
|
551
|
+
async send(message) {
|
|
552
|
+
return new Promise((resolve, reject) => {
|
|
553
|
+
this.proc.stdin.write(JSON.stringify(message) + "\n", (err) => {
|
|
554
|
+
if (err) reject(err);
|
|
555
|
+
else resolve();
|
|
556
|
+
});
|
|
557
|
+
});
|
|
558
|
+
}
|
|
559
|
+
async sendAndWait(message) {
|
|
560
|
+
return new Promise((resolve, reject) => {
|
|
561
|
+
const id = message.id;
|
|
562
|
+
if (id == null) {
|
|
563
|
+
this.send(message).catch(reject);
|
|
564
|
+
resolve({ jsonrpc: "2.0", id: null });
|
|
565
|
+
return;
|
|
566
|
+
}
|
|
567
|
+
this.pending.set(id, resolve);
|
|
568
|
+
this.send(message).catch((err) => {
|
|
569
|
+
this.pending.delete(id);
|
|
570
|
+
reject(err);
|
|
571
|
+
});
|
|
572
|
+
});
|
|
573
|
+
}
|
|
574
|
+
// McpTransport.onMessage is not used for outbound — responses come via sendAndWait
|
|
575
|
+
onMessage(_handler) {
|
|
576
|
+
}
|
|
577
|
+
async close() {
|
|
578
|
+
this.proc.stdin.end();
|
|
579
|
+
await new Promise((resolve) => this.proc.on("close", resolve));
|
|
580
|
+
}
|
|
581
|
+
};
|
|
582
|
+
|
|
583
|
+
// src/transport/TransportFactory.ts
|
|
584
|
+
var TransportFactory = class {
|
|
585
|
+
static createInbound(config) {
|
|
586
|
+
switch (config.transport) {
|
|
587
|
+
case "stdio":
|
|
588
|
+
return new StdioInbound();
|
|
589
|
+
case "http":
|
|
590
|
+
return new HttpInbound(config.port ?? 3e3, config.host);
|
|
591
|
+
case "sse":
|
|
592
|
+
return new SseInbound(config.port ?? 3e3, config.host);
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
static createOutbound(config) {
|
|
596
|
+
switch (config.transport) {
|
|
597
|
+
case "stdio":
|
|
598
|
+
if (!config.command) throw new Error("outbound stdio requires a command");
|
|
599
|
+
return new StdioOutbound(config.command, config.args);
|
|
600
|
+
case "http":
|
|
601
|
+
if (!config.url) throw new Error("outbound http requires a url");
|
|
602
|
+
return new HttpOutbound(config.url);
|
|
603
|
+
case "sse":
|
|
604
|
+
if (!config.url) throw new Error("outbound sse requires a url");
|
|
605
|
+
return new SseOutbound(config.url);
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
};
|
|
609
|
+
|
|
610
|
+
// src/proxy/ProxyBuilder.ts
|
|
611
|
+
var InboundSchema = v.object({
|
|
612
|
+
transport: v.picklist(["stdio", "http", "sse"]),
|
|
613
|
+
port: v.optional(v.number()),
|
|
614
|
+
host: v.optional(v.string())
|
|
615
|
+
});
|
|
616
|
+
var OutboundSchema = v.object({
|
|
617
|
+
transport: v.picklist(["stdio", "http", "sse"]),
|
|
618
|
+
url: v.optional(v.string()),
|
|
619
|
+
command: v.optional(v.string()),
|
|
620
|
+
args: v.optional(v.array(v.string()))
|
|
621
|
+
});
|
|
622
|
+
var ProxyBuilder = class _ProxyBuilder {
|
|
623
|
+
_inbound;
|
|
624
|
+
_outbound;
|
|
625
|
+
_store;
|
|
626
|
+
static create() {
|
|
627
|
+
return new _ProxyBuilder();
|
|
628
|
+
}
|
|
629
|
+
inbound(config) {
|
|
630
|
+
this._inbound = v.parse(InboundSchema, config);
|
|
631
|
+
return this;
|
|
632
|
+
}
|
|
633
|
+
outbound(config) {
|
|
634
|
+
this._outbound = v.parse(OutboundSchema, config);
|
|
635
|
+
return this;
|
|
636
|
+
}
|
|
637
|
+
store(connectionString) {
|
|
638
|
+
this._store = connectionString;
|
|
639
|
+
return this;
|
|
640
|
+
}
|
|
641
|
+
async build() {
|
|
642
|
+
if (!this._inbound) throw new Error("ProxyBuilder: inbound config is required");
|
|
643
|
+
if (!this._outbound) throw new Error("ProxyBuilder: outbound config is required");
|
|
644
|
+
if (!this._store) throw new Error("ProxyBuilder: store connection string is required");
|
|
645
|
+
const inbound = TransportFactory.createInbound(this._inbound);
|
|
646
|
+
const outbound = TransportFactory.createOutbound(this._outbound);
|
|
647
|
+
const store = await StoreResolver.resolve(this._store);
|
|
648
|
+
return new McpProxy(
|
|
649
|
+
inbound,
|
|
650
|
+
outbound,
|
|
651
|
+
store
|
|
652
|
+
);
|
|
653
|
+
}
|
|
654
|
+
};
|
|
655
|
+
|
|
656
|
+
export {
|
|
657
|
+
McpProxy,
|
|
658
|
+
ProxyBuilder
|
|
659
|
+
};
|
|
660
|
+
//# sourceMappingURL=chunk-VOBMDW4J.js.map
|