@exulu/backend 0.3.7 → 0.3.9
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/index.cjs +247 -16
- package/dist/index.d.cts +8 -4
- package/dist/index.d.ts +8 -4
- package/dist/index.js +246 -15
- package/ngrok.bash +1 -0
- package/ngrok.md +6 -0
- package/ngrok.yml +10 -0
- package/package.json +2 -1
package/dist/index.cjs
CHANGED
|
@@ -1369,6 +1369,44 @@ var ExuluEval = class {
|
|
|
1369
1369
|
}
|
|
1370
1370
|
};
|
|
1371
1371
|
};
|
|
1372
|
+
var calculateStatistics = (items, method) => {
|
|
1373
|
+
let methodProperty = "";
|
|
1374
|
+
if (method === "l1Distance") {
|
|
1375
|
+
methodProperty = "l1_distance";
|
|
1376
|
+
}
|
|
1377
|
+
if (method === "l2Distance") {
|
|
1378
|
+
methodProperty = "l2_distance";
|
|
1379
|
+
}
|
|
1380
|
+
if (method === "hammingDistance") {
|
|
1381
|
+
methodProperty = "hamming_distance";
|
|
1382
|
+
}
|
|
1383
|
+
if (method === "jaccardDistance") {
|
|
1384
|
+
methodProperty = "jaccard_distance";
|
|
1385
|
+
}
|
|
1386
|
+
if (method === "maxInnerProduct") {
|
|
1387
|
+
methodProperty = "inner_product";
|
|
1388
|
+
}
|
|
1389
|
+
if (method === "cosineDistance") {
|
|
1390
|
+
methodProperty = "cosine_distance";
|
|
1391
|
+
}
|
|
1392
|
+
const sorted = items.sort((a, b) => a[methodProperty] - b[methodProperty]);
|
|
1393
|
+
const median = sorted[Math.floor(items.length / 2)];
|
|
1394
|
+
const max = sorted[sorted.length - 1];
|
|
1395
|
+
const min = sorted[0];
|
|
1396
|
+
const average = items.reduce((acc, item) => {
|
|
1397
|
+
return acc + item[methodProperty];
|
|
1398
|
+
}, 0) / items.length;
|
|
1399
|
+
const total = items.reduce((acc, item) => {
|
|
1400
|
+
return acc + item[methodProperty];
|
|
1401
|
+
}, 0);
|
|
1402
|
+
return {
|
|
1403
|
+
max,
|
|
1404
|
+
min,
|
|
1405
|
+
median,
|
|
1406
|
+
average,
|
|
1407
|
+
total
|
|
1408
|
+
};
|
|
1409
|
+
};
|
|
1372
1410
|
var ExuluTool = class {
|
|
1373
1411
|
id;
|
|
1374
1412
|
name;
|
|
@@ -1636,6 +1674,9 @@ var ExuluContext = class {
|
|
|
1636
1674
|
};
|
|
1637
1675
|
}
|
|
1638
1676
|
if (typeof query === "string") {
|
|
1677
|
+
if (!method) {
|
|
1678
|
+
method = "cosineDistance";
|
|
1679
|
+
}
|
|
1639
1680
|
itemsQuery.limit(limit * 5);
|
|
1640
1681
|
if (statistics) {
|
|
1641
1682
|
updateStatistic({
|
|
@@ -1652,12 +1693,12 @@ var ExuluContext = class {
|
|
|
1652
1693
|
itemsQuery.leftJoin(chunksTable, function() {
|
|
1653
1694
|
this.on(chunksTable + ".source", "=", mainTable + ".id");
|
|
1654
1695
|
});
|
|
1655
|
-
itemsQuery.select(chunksTable + ".id");
|
|
1696
|
+
itemsQuery.select(chunksTable + ".id as chunk_id");
|
|
1656
1697
|
itemsQuery.select(chunksTable + ".source");
|
|
1657
1698
|
itemsQuery.select(chunksTable + ".content");
|
|
1658
1699
|
itemsQuery.select(chunksTable + ".chunk_index");
|
|
1659
|
-
itemsQuery.select(chunksTable + ".created_at");
|
|
1660
|
-
itemsQuery.select(chunksTable + ".updated_at");
|
|
1700
|
+
itemsQuery.select(chunksTable + ".created_at as chunk_created_at");
|
|
1701
|
+
itemsQuery.select(chunksTable + ".updated_at as chunk_updated_at");
|
|
1661
1702
|
const { chunks } = await this.embedder.generateFromQuery(query);
|
|
1662
1703
|
if (!chunks?.[0]?.vector) {
|
|
1663
1704
|
throw new Error("No vector generated for query.");
|
|
@@ -1699,7 +1740,7 @@ var ExuluContext = class {
|
|
|
1699
1740
|
seenSources.set(item.source, {
|
|
1700
1741
|
...Object.fromEntries(
|
|
1701
1742
|
Object.keys(item).filter(
|
|
1702
|
-
(key) => key !== "l1_distance" && key !== "l2_distance" && key !== "hamming_distance" && key !== "jaccard_distance" && key !== "inner_product" && key !== "cosine_distance" && key !== "content" && key !== "source" && key !== "chunk_index"
|
|
1743
|
+
(key) => key !== "l1_distance" && key !== "l2_distance" && key !== "hamming_distance" && key !== "jaccard_distance" && key !== "inner_product" && key !== "cosine_distance" && key !== "content" && key !== "source" && key !== "chunk_index" && key !== "chunk_id" && key !== "chunk_created_at" && key !== "chunk_updated_at"
|
|
1703
1744
|
).map((key) => [key, item[key]])
|
|
1704
1745
|
),
|
|
1705
1746
|
chunks: [{
|
|
@@ -1728,6 +1769,23 @@ var ExuluContext = class {
|
|
|
1728
1769
|
}
|
|
1729
1770
|
return acc;
|
|
1730
1771
|
}, []);
|
|
1772
|
+
items.forEach((item) => {
|
|
1773
|
+
if (!item.chunks?.length) {
|
|
1774
|
+
return;
|
|
1775
|
+
}
|
|
1776
|
+
const {
|
|
1777
|
+
max,
|
|
1778
|
+
min,
|
|
1779
|
+
median,
|
|
1780
|
+
average,
|
|
1781
|
+
total
|
|
1782
|
+
} = calculateStatistics(item.chunks, method ?? "cosineDistance");
|
|
1783
|
+
item.maxRelevance = max;
|
|
1784
|
+
item.minRelevance = min;
|
|
1785
|
+
item.medianRelevance = median;
|
|
1786
|
+
item.averageRelevance = average;
|
|
1787
|
+
item.totalRelevance = total;
|
|
1788
|
+
});
|
|
1731
1789
|
if (this.resultReranker && query) {
|
|
1732
1790
|
items = await this.resultReranker(items);
|
|
1733
1791
|
}
|
|
@@ -1850,7 +1908,7 @@ var updateStatistic = async (statistic) => {
|
|
|
1850
1908
|
};
|
|
1851
1909
|
|
|
1852
1910
|
// src/registry/index.ts
|
|
1853
|
-
var
|
|
1911
|
+
var import_express6 = require("express");
|
|
1854
1912
|
|
|
1855
1913
|
// src/registry/routes.ts
|
|
1856
1914
|
var import_express2 = require("express");
|
|
@@ -3004,6 +3062,8 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
|
|
|
3004
3062
|
const routeLogs = [];
|
|
3005
3063
|
var corsOptions = {
|
|
3006
3064
|
origin: "*",
|
|
3065
|
+
exposedHeaders: "*",
|
|
3066
|
+
allowedHeaders: "*",
|
|
3007
3067
|
optionsSuccessStatus: 200
|
|
3008
3068
|
// some legacy browsers (IE11, various SmartTVs) choke on 204
|
|
3009
3069
|
};
|
|
@@ -3074,7 +3134,7 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
|
|
|
3074
3134
|
console.log("[EXULU] graphql server started");
|
|
3075
3135
|
app.use(
|
|
3076
3136
|
"/graphql",
|
|
3077
|
-
(0, import_cors.default)(),
|
|
3137
|
+
(0, import_cors.default)(corsOptions),
|
|
3078
3138
|
import_express3.default.json(),
|
|
3079
3139
|
(0, import_express5.expressMiddleware)(server, {
|
|
3080
3140
|
context: async ({ req }) => {
|
|
@@ -3185,7 +3245,7 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
|
|
|
3185
3245
|
}
|
|
3186
3246
|
const item = await query.first();
|
|
3187
3247
|
if (!item) {
|
|
3188
|
-
|
|
3248
|
+
return null;
|
|
3189
3249
|
}
|
|
3190
3250
|
const chunks = await db2.from(context.getChunksTableName()).where({ source: item.id }).select("id");
|
|
3191
3251
|
if (chunks.length > 0) {
|
|
@@ -3206,6 +3266,12 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
|
|
|
3206
3266
|
id: req.params.id,
|
|
3207
3267
|
contextId: req.params.context
|
|
3208
3268
|
});
|
|
3269
|
+
if (!result) {
|
|
3270
|
+
res.status(200).json({
|
|
3271
|
+
message: "Item not found."
|
|
3272
|
+
});
|
|
3273
|
+
return;
|
|
3274
|
+
}
|
|
3209
3275
|
res.status(200).json(result);
|
|
3210
3276
|
});
|
|
3211
3277
|
app.delete("/items/:context/external/:id", async (req, res) => {
|
|
@@ -3219,6 +3285,12 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
|
|
|
3219
3285
|
external_id: req.params.id,
|
|
3220
3286
|
contextId: req.params.context
|
|
3221
3287
|
});
|
|
3288
|
+
if (!result) {
|
|
3289
|
+
res.status(200).json({
|
|
3290
|
+
message: "Item not found."
|
|
3291
|
+
});
|
|
3292
|
+
return;
|
|
3293
|
+
}
|
|
3222
3294
|
res.status(200).json(result);
|
|
3223
3295
|
});
|
|
3224
3296
|
app.get("/items/:context/:id", async (req, res) => {
|
|
@@ -3915,6 +3987,7 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
|
|
|
3915
3987
|
}
|
|
3916
3988
|
console.log("Routes:");
|
|
3917
3989
|
console.table(routeLogs);
|
|
3990
|
+
return app;
|
|
3918
3991
|
};
|
|
3919
3992
|
var preprocessInputs = async (data) => {
|
|
3920
3993
|
for (const key in data) {
|
|
@@ -4164,16 +4237,160 @@ var createLogsCleanerWorker = (logsDir) => {
|
|
|
4164
4237
|
return logsCleaner;
|
|
4165
4238
|
};
|
|
4166
4239
|
|
|
4240
|
+
// src/mcp/index.ts
|
|
4241
|
+
var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
4242
|
+
var import_node_crypto = require("crypto");
|
|
4243
|
+
var import_streamableHttp = require("@modelcontextprotocol/sdk/server/streamableHttp.js");
|
|
4244
|
+
var import_types = require("@modelcontextprotocol/sdk/types.js");
|
|
4245
|
+
var import_zod3 = require("zod");
|
|
4246
|
+
var import_express4 = require("express");
|
|
4247
|
+
var ExuluMCP = class {
|
|
4248
|
+
server;
|
|
4249
|
+
transports = {};
|
|
4250
|
+
express;
|
|
4251
|
+
constructor() {
|
|
4252
|
+
}
|
|
4253
|
+
create = async ({ express: express2, contexts, embedders, agents, workflows, config, tools }) => {
|
|
4254
|
+
this.express = express2;
|
|
4255
|
+
if (!this.server) {
|
|
4256
|
+
console.log("[EXULU] Creating MCP server.");
|
|
4257
|
+
this.server = new import_mcp.McpServer({
|
|
4258
|
+
name: "exulu-mcp-server",
|
|
4259
|
+
version: "1.0.0"
|
|
4260
|
+
});
|
|
4261
|
+
}
|
|
4262
|
+
this.server.registerTool(
|
|
4263
|
+
"add",
|
|
4264
|
+
{
|
|
4265
|
+
title: "Addition Tool",
|
|
4266
|
+
description: "Add two numbers",
|
|
4267
|
+
inputSchema: { a: import_zod3.z.number(), b: import_zod3.z.number() }
|
|
4268
|
+
},
|
|
4269
|
+
async ({ a, b }) => ({
|
|
4270
|
+
content: [{ type: "text", text: String(a + b) }]
|
|
4271
|
+
})
|
|
4272
|
+
);
|
|
4273
|
+
this.server.registerResource(
|
|
4274
|
+
"greeting",
|
|
4275
|
+
new import_mcp.ResourceTemplate("greeting://{name}", { list: void 0 }),
|
|
4276
|
+
{
|
|
4277
|
+
title: "Greeting Resource",
|
|
4278
|
+
// Display name for UI
|
|
4279
|
+
description: "Dynamic greeting generator"
|
|
4280
|
+
},
|
|
4281
|
+
async (uri, { name }) => ({
|
|
4282
|
+
contents: [{
|
|
4283
|
+
uri: uri.href,
|
|
4284
|
+
text: `Hello, ${name}!`
|
|
4285
|
+
}]
|
|
4286
|
+
})
|
|
4287
|
+
);
|
|
4288
|
+
this.server.registerPrompt(
|
|
4289
|
+
"review-code",
|
|
4290
|
+
{
|
|
4291
|
+
title: "Code Review",
|
|
4292
|
+
description: "Review code for best practices and potential issues",
|
|
4293
|
+
argsSchema: { code: import_zod3.z.string() }
|
|
4294
|
+
},
|
|
4295
|
+
({ code }) => ({
|
|
4296
|
+
messages: [{
|
|
4297
|
+
role: "user",
|
|
4298
|
+
content: {
|
|
4299
|
+
type: "text",
|
|
4300
|
+
text: `Please review this code:
|
|
4301
|
+
|
|
4302
|
+
${code}`
|
|
4303
|
+
}
|
|
4304
|
+
}]
|
|
4305
|
+
})
|
|
4306
|
+
);
|
|
4307
|
+
console.log("Contexts:");
|
|
4308
|
+
console.table();
|
|
4309
|
+
};
|
|
4310
|
+
connect = async () => {
|
|
4311
|
+
if (!this.express) {
|
|
4312
|
+
throw new Error("Express not initialized.");
|
|
4313
|
+
}
|
|
4314
|
+
if (!this.server) {
|
|
4315
|
+
throw new Error("MCP server not initialized.");
|
|
4316
|
+
}
|
|
4317
|
+
console.log("[EXULU] Wiring up MCP server routes to express app.");
|
|
4318
|
+
this.express.post("/mcp", async (req, res) => {
|
|
4319
|
+
const sessionId = req.headers["mcp-session-id"];
|
|
4320
|
+
let transport;
|
|
4321
|
+
if (sessionId && this.transports[sessionId]) {
|
|
4322
|
+
transport = this.transports[sessionId];
|
|
4323
|
+
} else if (!sessionId && (0, import_types.isInitializeRequest)(req.body)) {
|
|
4324
|
+
transport = new import_streamableHttp.StreamableHTTPServerTransport({
|
|
4325
|
+
sessionIdGenerator: () => (0, import_node_crypto.randomUUID)(),
|
|
4326
|
+
onsessioninitialized: (sessionId2) => {
|
|
4327
|
+
this.transports[sessionId2] = transport;
|
|
4328
|
+
}
|
|
4329
|
+
// DNS rebinding protection is disabled by default for backwards compatibility. If you are running this server
|
|
4330
|
+
// locally, make sure to set:
|
|
4331
|
+
// enableDnsRebindingProtection: true,
|
|
4332
|
+
// allowedHosts: ['127.0.0.1'],
|
|
4333
|
+
});
|
|
4334
|
+
transport.onclose = () => {
|
|
4335
|
+
if (transport.sessionId) {
|
|
4336
|
+
delete this.transports[transport.sessionId];
|
|
4337
|
+
}
|
|
4338
|
+
};
|
|
4339
|
+
const server = new import_mcp.McpServer({
|
|
4340
|
+
name: "example-server",
|
|
4341
|
+
version: "1.0.0"
|
|
4342
|
+
});
|
|
4343
|
+
await server.connect(transport);
|
|
4344
|
+
} else {
|
|
4345
|
+
res.status(400).json({
|
|
4346
|
+
jsonrpc: "2.0",
|
|
4347
|
+
error: {
|
|
4348
|
+
code: -32e3,
|
|
4349
|
+
message: "Bad Request: No valid session ID provided"
|
|
4350
|
+
},
|
|
4351
|
+
id: null
|
|
4352
|
+
});
|
|
4353
|
+
return;
|
|
4354
|
+
}
|
|
4355
|
+
await transport.handleRequest(req, res, req.body);
|
|
4356
|
+
});
|
|
4357
|
+
const handleSessionRequest = async (req, res) => {
|
|
4358
|
+
const sessionId = req.headers["mcp-session-id"];
|
|
4359
|
+
if (!sessionId || !this.transports[sessionId]) {
|
|
4360
|
+
res.status(400).send("Invalid or missing session ID");
|
|
4361
|
+
return;
|
|
4362
|
+
}
|
|
4363
|
+
const transport = this.transports[sessionId];
|
|
4364
|
+
await transport.handleRequest(req, res);
|
|
4365
|
+
};
|
|
4366
|
+
this.express.get("/mcp", handleSessionRequest);
|
|
4367
|
+
this.express.delete("/mcp", handleSessionRequest);
|
|
4368
|
+
const routeLogs = [];
|
|
4369
|
+
routeLogs.push(
|
|
4370
|
+
{ route: "/mcp", method: "GET", note: "Get MCP server status" },
|
|
4371
|
+
{ route: "/mcp", method: "POST", note: "Send MCP request" },
|
|
4372
|
+
{ route: "/mcp", method: "DELETE", note: "Terminate MCP session" }
|
|
4373
|
+
);
|
|
4374
|
+
console.log("MCP Routes:");
|
|
4375
|
+
console.table(routeLogs);
|
|
4376
|
+
return this.express;
|
|
4377
|
+
};
|
|
4378
|
+
};
|
|
4379
|
+
|
|
4167
4380
|
// src/registry/index.ts
|
|
4168
4381
|
var ExuluApp = class {
|
|
4169
|
-
_agents;
|
|
4170
|
-
_workflows;
|
|
4382
|
+
_agents = [];
|
|
4383
|
+
_workflows = [];
|
|
4171
4384
|
_config;
|
|
4172
|
-
_embedders;
|
|
4385
|
+
_embedders = [];
|
|
4173
4386
|
_queues = [];
|
|
4174
|
-
_contexts;
|
|
4175
|
-
_tools;
|
|
4176
|
-
constructor(
|
|
4387
|
+
_contexts = {};
|
|
4388
|
+
_tools = [];
|
|
4389
|
+
constructor() {
|
|
4390
|
+
}
|
|
4391
|
+
// Factory function so we can async initialize the
|
|
4392
|
+
// MCP server if needed.
|
|
4393
|
+
create = async ({ contexts, embedders, agents, workflows, config, tools }) => {
|
|
4177
4394
|
this._embedders = embedders ?? [];
|
|
4178
4395
|
this._workflows = workflows ?? [];
|
|
4179
4396
|
this._contexts = contexts ?? {};
|
|
@@ -4185,7 +4402,7 @@ var ExuluApp = class {
|
|
|
4185
4402
|
...workflows?.length ? workflows.map((workflow) => workflow.queue?.name || null) : []
|
|
4186
4403
|
];
|
|
4187
4404
|
this._queues = [...new Set(queues2.filter((o) => !!o))];
|
|
4188
|
-
}
|
|
4405
|
+
};
|
|
4189
4406
|
embedder(id) {
|
|
4190
4407
|
return this._embedders.find((x) => x.id === id);
|
|
4191
4408
|
}
|
|
@@ -4224,7 +4441,7 @@ var ExuluApp = class {
|
|
|
4224
4441
|
Object.values(this._contexts ?? {}),
|
|
4225
4442
|
this._embedders,
|
|
4226
4443
|
this._workflows,
|
|
4227
|
-
this._config
|
|
4444
|
+
this._config?.workers?.logsDir
|
|
4228
4445
|
);
|
|
4229
4446
|
}
|
|
4230
4447
|
}
|
|
@@ -4232,7 +4449,7 @@ var ExuluApp = class {
|
|
|
4232
4449
|
server = {
|
|
4233
4450
|
express: {
|
|
4234
4451
|
init: async (app) => {
|
|
4235
|
-
|
|
4452
|
+
await createExpressRoutes(
|
|
4236
4453
|
app,
|
|
4237
4454
|
this._agents,
|
|
4238
4455
|
this._embedders,
|
|
@@ -4240,6 +4457,20 @@ var ExuluApp = class {
|
|
|
4240
4457
|
this._workflows,
|
|
4241
4458
|
Object.values(this._contexts ?? {})
|
|
4242
4459
|
);
|
|
4460
|
+
if (this._config?.MCP.enabled) {
|
|
4461
|
+
const mcp = new ExuluMCP();
|
|
4462
|
+
await mcp.create({
|
|
4463
|
+
express: app,
|
|
4464
|
+
contexts: this._contexts,
|
|
4465
|
+
embedders: this._embedders,
|
|
4466
|
+
agents: this._agents,
|
|
4467
|
+
workflows: this._workflows,
|
|
4468
|
+
config: this._config,
|
|
4469
|
+
tools: this._tools
|
|
4470
|
+
});
|
|
4471
|
+
await mcp.connect();
|
|
4472
|
+
}
|
|
4473
|
+
return app;
|
|
4243
4474
|
}
|
|
4244
4475
|
}
|
|
4245
4476
|
};
|
package/dist/index.d.cts
CHANGED
|
@@ -457,23 +457,27 @@ type ExuluConfig = {
|
|
|
457
457
|
enabled: boolean;
|
|
458
458
|
logsDir?: string;
|
|
459
459
|
};
|
|
460
|
+
MCP: {
|
|
461
|
+
enabled: boolean;
|
|
462
|
+
};
|
|
460
463
|
};
|
|
461
464
|
declare class ExuluApp {
|
|
462
465
|
private _agents;
|
|
463
466
|
private _workflows;
|
|
464
|
-
private _config
|
|
467
|
+
private _config?;
|
|
465
468
|
private _embedders;
|
|
466
469
|
private _queues;
|
|
467
470
|
private _contexts?;
|
|
468
471
|
private _tools;
|
|
469
|
-
constructor(
|
|
472
|
+
constructor();
|
|
473
|
+
create: ({ contexts, embedders, agents, workflows, config, tools }: {
|
|
470
474
|
contexts?: Record<string, ExuluContext>;
|
|
471
475
|
config: ExuluConfig;
|
|
472
476
|
embedders?: ExuluEmbedder[];
|
|
473
477
|
agents?: ExuluAgent[];
|
|
474
478
|
workflows?: ExuluWorkflow[];
|
|
475
479
|
tools?: ExuluTool[];
|
|
476
|
-
})
|
|
480
|
+
}) => Promise<void>;
|
|
477
481
|
embedder(id: string): ExuluEmbedder | undefined;
|
|
478
482
|
tool(id: string): ExuluTool | undefined;
|
|
479
483
|
tools(): ExuluTool[];
|
|
@@ -491,7 +495,7 @@ declare class ExuluApp {
|
|
|
491
495
|
};
|
|
492
496
|
server: {
|
|
493
497
|
express: {
|
|
494
|
-
init: (app: Express) => Promise<
|
|
498
|
+
init: (app: Express) => Promise<Express>;
|
|
495
499
|
};
|
|
496
500
|
};
|
|
497
501
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -457,23 +457,27 @@ type ExuluConfig = {
|
|
|
457
457
|
enabled: boolean;
|
|
458
458
|
logsDir?: string;
|
|
459
459
|
};
|
|
460
|
+
MCP: {
|
|
461
|
+
enabled: boolean;
|
|
462
|
+
};
|
|
460
463
|
};
|
|
461
464
|
declare class ExuluApp {
|
|
462
465
|
private _agents;
|
|
463
466
|
private _workflows;
|
|
464
|
-
private _config
|
|
467
|
+
private _config?;
|
|
465
468
|
private _embedders;
|
|
466
469
|
private _queues;
|
|
467
470
|
private _contexts?;
|
|
468
471
|
private _tools;
|
|
469
|
-
constructor(
|
|
472
|
+
constructor();
|
|
473
|
+
create: ({ contexts, embedders, agents, workflows, config, tools }: {
|
|
470
474
|
contexts?: Record<string, ExuluContext>;
|
|
471
475
|
config: ExuluConfig;
|
|
472
476
|
embedders?: ExuluEmbedder[];
|
|
473
477
|
agents?: ExuluAgent[];
|
|
474
478
|
workflows?: ExuluWorkflow[];
|
|
475
479
|
tools?: ExuluTool[];
|
|
476
|
-
})
|
|
480
|
+
}) => Promise<void>;
|
|
477
481
|
embedder(id: string): ExuluEmbedder | undefined;
|
|
478
482
|
tool(id: string): ExuluTool | undefined;
|
|
479
483
|
tools(): ExuluTool[];
|
|
@@ -491,7 +495,7 @@ declare class ExuluApp {
|
|
|
491
495
|
};
|
|
492
496
|
server: {
|
|
493
497
|
express: {
|
|
494
|
-
init: (app: Express) => Promise<
|
|
498
|
+
init: (app: Express) => Promise<Express>;
|
|
495
499
|
};
|
|
496
500
|
};
|
|
497
501
|
}
|
package/dist/index.js
CHANGED
|
@@ -1325,6 +1325,44 @@ var ExuluEval = class {
|
|
|
1325
1325
|
}
|
|
1326
1326
|
};
|
|
1327
1327
|
};
|
|
1328
|
+
var calculateStatistics = (items, method) => {
|
|
1329
|
+
let methodProperty = "";
|
|
1330
|
+
if (method === "l1Distance") {
|
|
1331
|
+
methodProperty = "l1_distance";
|
|
1332
|
+
}
|
|
1333
|
+
if (method === "l2Distance") {
|
|
1334
|
+
methodProperty = "l2_distance";
|
|
1335
|
+
}
|
|
1336
|
+
if (method === "hammingDistance") {
|
|
1337
|
+
methodProperty = "hamming_distance";
|
|
1338
|
+
}
|
|
1339
|
+
if (method === "jaccardDistance") {
|
|
1340
|
+
methodProperty = "jaccard_distance";
|
|
1341
|
+
}
|
|
1342
|
+
if (method === "maxInnerProduct") {
|
|
1343
|
+
methodProperty = "inner_product";
|
|
1344
|
+
}
|
|
1345
|
+
if (method === "cosineDistance") {
|
|
1346
|
+
methodProperty = "cosine_distance";
|
|
1347
|
+
}
|
|
1348
|
+
const sorted = items.sort((a, b) => a[methodProperty] - b[methodProperty]);
|
|
1349
|
+
const median = sorted[Math.floor(items.length / 2)];
|
|
1350
|
+
const max = sorted[sorted.length - 1];
|
|
1351
|
+
const min = sorted[0];
|
|
1352
|
+
const average = items.reduce((acc, item) => {
|
|
1353
|
+
return acc + item[methodProperty];
|
|
1354
|
+
}, 0) / items.length;
|
|
1355
|
+
const total = items.reduce((acc, item) => {
|
|
1356
|
+
return acc + item[methodProperty];
|
|
1357
|
+
}, 0);
|
|
1358
|
+
return {
|
|
1359
|
+
max,
|
|
1360
|
+
min,
|
|
1361
|
+
median,
|
|
1362
|
+
average,
|
|
1363
|
+
total
|
|
1364
|
+
};
|
|
1365
|
+
};
|
|
1328
1366
|
var ExuluTool = class {
|
|
1329
1367
|
id;
|
|
1330
1368
|
name;
|
|
@@ -1592,6 +1630,9 @@ var ExuluContext = class {
|
|
|
1592
1630
|
};
|
|
1593
1631
|
}
|
|
1594
1632
|
if (typeof query === "string") {
|
|
1633
|
+
if (!method) {
|
|
1634
|
+
method = "cosineDistance";
|
|
1635
|
+
}
|
|
1595
1636
|
itemsQuery.limit(limit * 5);
|
|
1596
1637
|
if (statistics) {
|
|
1597
1638
|
updateStatistic({
|
|
@@ -1608,12 +1649,12 @@ var ExuluContext = class {
|
|
|
1608
1649
|
itemsQuery.leftJoin(chunksTable, function() {
|
|
1609
1650
|
this.on(chunksTable + ".source", "=", mainTable + ".id");
|
|
1610
1651
|
});
|
|
1611
|
-
itemsQuery.select(chunksTable + ".id");
|
|
1652
|
+
itemsQuery.select(chunksTable + ".id as chunk_id");
|
|
1612
1653
|
itemsQuery.select(chunksTable + ".source");
|
|
1613
1654
|
itemsQuery.select(chunksTable + ".content");
|
|
1614
1655
|
itemsQuery.select(chunksTable + ".chunk_index");
|
|
1615
|
-
itemsQuery.select(chunksTable + ".created_at");
|
|
1616
|
-
itemsQuery.select(chunksTable + ".updated_at");
|
|
1656
|
+
itemsQuery.select(chunksTable + ".created_at as chunk_created_at");
|
|
1657
|
+
itemsQuery.select(chunksTable + ".updated_at as chunk_updated_at");
|
|
1617
1658
|
const { chunks } = await this.embedder.generateFromQuery(query);
|
|
1618
1659
|
if (!chunks?.[0]?.vector) {
|
|
1619
1660
|
throw new Error("No vector generated for query.");
|
|
@@ -1655,7 +1696,7 @@ var ExuluContext = class {
|
|
|
1655
1696
|
seenSources.set(item.source, {
|
|
1656
1697
|
...Object.fromEntries(
|
|
1657
1698
|
Object.keys(item).filter(
|
|
1658
|
-
(key) => key !== "l1_distance" && key !== "l2_distance" && key !== "hamming_distance" && key !== "jaccard_distance" && key !== "inner_product" && key !== "cosine_distance" && key !== "content" && key !== "source" && key !== "chunk_index"
|
|
1699
|
+
(key) => key !== "l1_distance" && key !== "l2_distance" && key !== "hamming_distance" && key !== "jaccard_distance" && key !== "inner_product" && key !== "cosine_distance" && key !== "content" && key !== "source" && key !== "chunk_index" && key !== "chunk_id" && key !== "chunk_created_at" && key !== "chunk_updated_at"
|
|
1659
1700
|
).map((key) => [key, item[key]])
|
|
1660
1701
|
),
|
|
1661
1702
|
chunks: [{
|
|
@@ -1684,6 +1725,23 @@ var ExuluContext = class {
|
|
|
1684
1725
|
}
|
|
1685
1726
|
return acc;
|
|
1686
1727
|
}, []);
|
|
1728
|
+
items.forEach((item) => {
|
|
1729
|
+
if (!item.chunks?.length) {
|
|
1730
|
+
return;
|
|
1731
|
+
}
|
|
1732
|
+
const {
|
|
1733
|
+
max,
|
|
1734
|
+
min,
|
|
1735
|
+
median,
|
|
1736
|
+
average,
|
|
1737
|
+
total
|
|
1738
|
+
} = calculateStatistics(item.chunks, method ?? "cosineDistance");
|
|
1739
|
+
item.maxRelevance = max;
|
|
1740
|
+
item.minRelevance = min;
|
|
1741
|
+
item.medianRelevance = median;
|
|
1742
|
+
item.averageRelevance = average;
|
|
1743
|
+
item.totalRelevance = total;
|
|
1744
|
+
});
|
|
1687
1745
|
if (this.resultReranker && query) {
|
|
1688
1746
|
items = await this.resultReranker(items);
|
|
1689
1747
|
}
|
|
@@ -2960,6 +3018,8 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
|
|
|
2960
3018
|
const routeLogs = [];
|
|
2961
3019
|
var corsOptions = {
|
|
2962
3020
|
origin: "*",
|
|
3021
|
+
exposedHeaders: "*",
|
|
3022
|
+
allowedHeaders: "*",
|
|
2963
3023
|
optionsSuccessStatus: 200
|
|
2964
3024
|
// some legacy browsers (IE11, various SmartTVs) choke on 204
|
|
2965
3025
|
};
|
|
@@ -3030,7 +3090,7 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
|
|
|
3030
3090
|
console.log("[EXULU] graphql server started");
|
|
3031
3091
|
app.use(
|
|
3032
3092
|
"/graphql",
|
|
3033
|
-
cors(),
|
|
3093
|
+
cors(corsOptions),
|
|
3034
3094
|
express.json(),
|
|
3035
3095
|
expressMiddleware(server, {
|
|
3036
3096
|
context: async ({ req }) => {
|
|
@@ -3141,7 +3201,7 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
|
|
|
3141
3201
|
}
|
|
3142
3202
|
const item = await query.first();
|
|
3143
3203
|
if (!item) {
|
|
3144
|
-
|
|
3204
|
+
return null;
|
|
3145
3205
|
}
|
|
3146
3206
|
const chunks = await db2.from(context.getChunksTableName()).where({ source: item.id }).select("id");
|
|
3147
3207
|
if (chunks.length > 0) {
|
|
@@ -3162,6 +3222,12 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
|
|
|
3162
3222
|
id: req.params.id,
|
|
3163
3223
|
contextId: req.params.context
|
|
3164
3224
|
});
|
|
3225
|
+
if (!result) {
|
|
3226
|
+
res.status(200).json({
|
|
3227
|
+
message: "Item not found."
|
|
3228
|
+
});
|
|
3229
|
+
return;
|
|
3230
|
+
}
|
|
3165
3231
|
res.status(200).json(result);
|
|
3166
3232
|
});
|
|
3167
3233
|
app.delete("/items/:context/external/:id", async (req, res) => {
|
|
@@ -3175,6 +3241,12 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
|
|
|
3175
3241
|
external_id: req.params.id,
|
|
3176
3242
|
contextId: req.params.context
|
|
3177
3243
|
});
|
|
3244
|
+
if (!result) {
|
|
3245
|
+
res.status(200).json({
|
|
3246
|
+
message: "Item not found."
|
|
3247
|
+
});
|
|
3248
|
+
return;
|
|
3249
|
+
}
|
|
3178
3250
|
res.status(200).json(result);
|
|
3179
3251
|
});
|
|
3180
3252
|
app.get("/items/:context/:id", async (req, res) => {
|
|
@@ -3871,6 +3943,7 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
|
|
|
3871
3943
|
}
|
|
3872
3944
|
console.log("Routes:");
|
|
3873
3945
|
console.table(routeLogs);
|
|
3946
|
+
return app;
|
|
3874
3947
|
};
|
|
3875
3948
|
var preprocessInputs = async (data) => {
|
|
3876
3949
|
for (const key in data) {
|
|
@@ -4120,16 +4193,160 @@ var createLogsCleanerWorker = (logsDir) => {
|
|
|
4120
4193
|
return logsCleaner;
|
|
4121
4194
|
};
|
|
4122
4195
|
|
|
4196
|
+
// src/mcp/index.ts
|
|
4197
|
+
import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
4198
|
+
import { randomUUID } from "crypto";
|
|
4199
|
+
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
4200
|
+
import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
|
|
4201
|
+
import { z as z2 } from "zod";
|
|
4202
|
+
import "express";
|
|
4203
|
+
var ExuluMCP = class {
|
|
4204
|
+
server;
|
|
4205
|
+
transports = {};
|
|
4206
|
+
express;
|
|
4207
|
+
constructor() {
|
|
4208
|
+
}
|
|
4209
|
+
create = async ({ express: express2, contexts, embedders, agents, workflows, config, tools }) => {
|
|
4210
|
+
this.express = express2;
|
|
4211
|
+
if (!this.server) {
|
|
4212
|
+
console.log("[EXULU] Creating MCP server.");
|
|
4213
|
+
this.server = new McpServer({
|
|
4214
|
+
name: "exulu-mcp-server",
|
|
4215
|
+
version: "1.0.0"
|
|
4216
|
+
});
|
|
4217
|
+
}
|
|
4218
|
+
this.server.registerTool(
|
|
4219
|
+
"add",
|
|
4220
|
+
{
|
|
4221
|
+
title: "Addition Tool",
|
|
4222
|
+
description: "Add two numbers",
|
|
4223
|
+
inputSchema: { a: z2.number(), b: z2.number() }
|
|
4224
|
+
},
|
|
4225
|
+
async ({ a, b }) => ({
|
|
4226
|
+
content: [{ type: "text", text: String(a + b) }]
|
|
4227
|
+
})
|
|
4228
|
+
);
|
|
4229
|
+
this.server.registerResource(
|
|
4230
|
+
"greeting",
|
|
4231
|
+
new ResourceTemplate("greeting://{name}", { list: void 0 }),
|
|
4232
|
+
{
|
|
4233
|
+
title: "Greeting Resource",
|
|
4234
|
+
// Display name for UI
|
|
4235
|
+
description: "Dynamic greeting generator"
|
|
4236
|
+
},
|
|
4237
|
+
async (uri, { name }) => ({
|
|
4238
|
+
contents: [{
|
|
4239
|
+
uri: uri.href,
|
|
4240
|
+
text: `Hello, ${name}!`
|
|
4241
|
+
}]
|
|
4242
|
+
})
|
|
4243
|
+
);
|
|
4244
|
+
this.server.registerPrompt(
|
|
4245
|
+
"review-code",
|
|
4246
|
+
{
|
|
4247
|
+
title: "Code Review",
|
|
4248
|
+
description: "Review code for best practices and potential issues",
|
|
4249
|
+
argsSchema: { code: z2.string() }
|
|
4250
|
+
},
|
|
4251
|
+
({ code }) => ({
|
|
4252
|
+
messages: [{
|
|
4253
|
+
role: "user",
|
|
4254
|
+
content: {
|
|
4255
|
+
type: "text",
|
|
4256
|
+
text: `Please review this code:
|
|
4257
|
+
|
|
4258
|
+
${code}`
|
|
4259
|
+
}
|
|
4260
|
+
}]
|
|
4261
|
+
})
|
|
4262
|
+
);
|
|
4263
|
+
console.log("Contexts:");
|
|
4264
|
+
console.table();
|
|
4265
|
+
};
|
|
4266
|
+
connect = async () => {
|
|
4267
|
+
if (!this.express) {
|
|
4268
|
+
throw new Error("Express not initialized.");
|
|
4269
|
+
}
|
|
4270
|
+
if (!this.server) {
|
|
4271
|
+
throw new Error("MCP server not initialized.");
|
|
4272
|
+
}
|
|
4273
|
+
console.log("[EXULU] Wiring up MCP server routes to express app.");
|
|
4274
|
+
this.express.post("/mcp", async (req, res) => {
|
|
4275
|
+
const sessionId = req.headers["mcp-session-id"];
|
|
4276
|
+
let transport;
|
|
4277
|
+
if (sessionId && this.transports[sessionId]) {
|
|
4278
|
+
transport = this.transports[sessionId];
|
|
4279
|
+
} else if (!sessionId && isInitializeRequest(req.body)) {
|
|
4280
|
+
transport = new StreamableHTTPServerTransport({
|
|
4281
|
+
sessionIdGenerator: () => randomUUID(),
|
|
4282
|
+
onsessioninitialized: (sessionId2) => {
|
|
4283
|
+
this.transports[sessionId2] = transport;
|
|
4284
|
+
}
|
|
4285
|
+
// DNS rebinding protection is disabled by default for backwards compatibility. If you are running this server
|
|
4286
|
+
// locally, make sure to set:
|
|
4287
|
+
// enableDnsRebindingProtection: true,
|
|
4288
|
+
// allowedHosts: ['127.0.0.1'],
|
|
4289
|
+
});
|
|
4290
|
+
transport.onclose = () => {
|
|
4291
|
+
if (transport.sessionId) {
|
|
4292
|
+
delete this.transports[transport.sessionId];
|
|
4293
|
+
}
|
|
4294
|
+
};
|
|
4295
|
+
const server = new McpServer({
|
|
4296
|
+
name: "example-server",
|
|
4297
|
+
version: "1.0.0"
|
|
4298
|
+
});
|
|
4299
|
+
await server.connect(transport);
|
|
4300
|
+
} else {
|
|
4301
|
+
res.status(400).json({
|
|
4302
|
+
jsonrpc: "2.0",
|
|
4303
|
+
error: {
|
|
4304
|
+
code: -32e3,
|
|
4305
|
+
message: "Bad Request: No valid session ID provided"
|
|
4306
|
+
},
|
|
4307
|
+
id: null
|
|
4308
|
+
});
|
|
4309
|
+
return;
|
|
4310
|
+
}
|
|
4311
|
+
await transport.handleRequest(req, res, req.body);
|
|
4312
|
+
});
|
|
4313
|
+
const handleSessionRequest = async (req, res) => {
|
|
4314
|
+
const sessionId = req.headers["mcp-session-id"];
|
|
4315
|
+
if (!sessionId || !this.transports[sessionId]) {
|
|
4316
|
+
res.status(400).send("Invalid or missing session ID");
|
|
4317
|
+
return;
|
|
4318
|
+
}
|
|
4319
|
+
const transport = this.transports[sessionId];
|
|
4320
|
+
await transport.handleRequest(req, res);
|
|
4321
|
+
};
|
|
4322
|
+
this.express.get("/mcp", handleSessionRequest);
|
|
4323
|
+
this.express.delete("/mcp", handleSessionRequest);
|
|
4324
|
+
const routeLogs = [];
|
|
4325
|
+
routeLogs.push(
|
|
4326
|
+
{ route: "/mcp", method: "GET", note: "Get MCP server status" },
|
|
4327
|
+
{ route: "/mcp", method: "POST", note: "Send MCP request" },
|
|
4328
|
+
{ route: "/mcp", method: "DELETE", note: "Terminate MCP session" }
|
|
4329
|
+
);
|
|
4330
|
+
console.log("MCP Routes:");
|
|
4331
|
+
console.table(routeLogs);
|
|
4332
|
+
return this.express;
|
|
4333
|
+
};
|
|
4334
|
+
};
|
|
4335
|
+
|
|
4123
4336
|
// src/registry/index.ts
|
|
4124
4337
|
var ExuluApp = class {
|
|
4125
|
-
_agents;
|
|
4126
|
-
_workflows;
|
|
4338
|
+
_agents = [];
|
|
4339
|
+
_workflows = [];
|
|
4127
4340
|
_config;
|
|
4128
|
-
_embedders;
|
|
4341
|
+
_embedders = [];
|
|
4129
4342
|
_queues = [];
|
|
4130
|
-
_contexts;
|
|
4131
|
-
_tools;
|
|
4132
|
-
constructor(
|
|
4343
|
+
_contexts = {};
|
|
4344
|
+
_tools = [];
|
|
4345
|
+
constructor() {
|
|
4346
|
+
}
|
|
4347
|
+
// Factory function so we can async initialize the
|
|
4348
|
+
// MCP server if needed.
|
|
4349
|
+
create = async ({ contexts, embedders, agents, workflows, config, tools }) => {
|
|
4133
4350
|
this._embedders = embedders ?? [];
|
|
4134
4351
|
this._workflows = workflows ?? [];
|
|
4135
4352
|
this._contexts = contexts ?? {};
|
|
@@ -4141,7 +4358,7 @@ var ExuluApp = class {
|
|
|
4141
4358
|
...workflows?.length ? workflows.map((workflow) => workflow.queue?.name || null) : []
|
|
4142
4359
|
];
|
|
4143
4360
|
this._queues = [...new Set(queues2.filter((o) => !!o))];
|
|
4144
|
-
}
|
|
4361
|
+
};
|
|
4145
4362
|
embedder(id) {
|
|
4146
4363
|
return this._embedders.find((x) => x.id === id);
|
|
4147
4364
|
}
|
|
@@ -4180,7 +4397,7 @@ var ExuluApp = class {
|
|
|
4180
4397
|
Object.values(this._contexts ?? {}),
|
|
4181
4398
|
this._embedders,
|
|
4182
4399
|
this._workflows,
|
|
4183
|
-
this._config
|
|
4400
|
+
this._config?.workers?.logsDir
|
|
4184
4401
|
);
|
|
4185
4402
|
}
|
|
4186
4403
|
}
|
|
@@ -4188,7 +4405,7 @@ var ExuluApp = class {
|
|
|
4188
4405
|
server = {
|
|
4189
4406
|
express: {
|
|
4190
4407
|
init: async (app) => {
|
|
4191
|
-
|
|
4408
|
+
await createExpressRoutes(
|
|
4192
4409
|
app,
|
|
4193
4410
|
this._agents,
|
|
4194
4411
|
this._embedders,
|
|
@@ -4196,6 +4413,20 @@ var ExuluApp = class {
|
|
|
4196
4413
|
this._workflows,
|
|
4197
4414
|
Object.values(this._contexts ?? {})
|
|
4198
4415
|
);
|
|
4416
|
+
if (this._config?.MCP.enabled) {
|
|
4417
|
+
const mcp = new ExuluMCP();
|
|
4418
|
+
await mcp.create({
|
|
4419
|
+
express: app,
|
|
4420
|
+
contexts: this._contexts,
|
|
4421
|
+
embedders: this._embedders,
|
|
4422
|
+
agents: this._agents,
|
|
4423
|
+
workflows: this._workflows,
|
|
4424
|
+
config: this._config,
|
|
4425
|
+
tools: this._tools
|
|
4426
|
+
});
|
|
4427
|
+
await mcp.connect();
|
|
4428
|
+
}
|
|
4429
|
+
return app;
|
|
4199
4430
|
}
|
|
4200
4431
|
}
|
|
4201
4432
|
};
|
package/ngrok.bash
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
ngrok start --all --config ./ngrok.yml
|
package/ngrok.md
ADDED
package/ngrok.yml
ADDED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@exulu/backend",
|
|
3
3
|
"author": "Qventu Bv.",
|
|
4
|
-
"version": "0.3.
|
|
4
|
+
"version": "0.3.9",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"private": false,
|
|
7
7
|
"publishConfig": {
|
|
@@ -41,6 +41,7 @@
|
|
|
41
41
|
"@aws-sdk/client-sts": "^3.338.0",
|
|
42
42
|
"@aws-sdk/s3-request-presigner": "^3.338.0",
|
|
43
43
|
"@inkjs/ui": "^2.0.0",
|
|
44
|
+
"@modelcontextprotocol/sdk": "^1.14.0",
|
|
44
45
|
"@panva/hkdf": "^1.2.1",
|
|
45
46
|
"@types/bcrypt": "^5.0.2",
|
|
46
47
|
"@types/cors": "^2.8.18",
|