@exulu/backend 0.3.8 → 0.3.10
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 +234 -16
- package/dist/index.d.cts +8 -4
- package/dist/index.d.ts +8 -4
- package/dist/index.js +233 -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,37 @@ 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 average = items.reduce((acc, item) => {
|
|
1393
|
+
return acc + item[methodProperty];
|
|
1394
|
+
}, 0) / items.length;
|
|
1395
|
+
const total = items.reduce((acc, item) => {
|
|
1396
|
+
return acc + item[methodProperty];
|
|
1397
|
+
}, 0);
|
|
1398
|
+
return {
|
|
1399
|
+
average,
|
|
1400
|
+
total
|
|
1401
|
+
};
|
|
1402
|
+
};
|
|
1372
1403
|
var ExuluTool = class {
|
|
1373
1404
|
id;
|
|
1374
1405
|
name;
|
|
@@ -1636,6 +1667,9 @@ var ExuluContext = class {
|
|
|
1636
1667
|
};
|
|
1637
1668
|
}
|
|
1638
1669
|
if (typeof query === "string") {
|
|
1670
|
+
if (!method) {
|
|
1671
|
+
method = "cosineDistance";
|
|
1672
|
+
}
|
|
1639
1673
|
itemsQuery.limit(limit * 5);
|
|
1640
1674
|
if (statistics) {
|
|
1641
1675
|
updateStatistic({
|
|
@@ -1652,12 +1686,12 @@ var ExuluContext = class {
|
|
|
1652
1686
|
itemsQuery.leftJoin(chunksTable, function() {
|
|
1653
1687
|
this.on(chunksTable + ".source", "=", mainTable + ".id");
|
|
1654
1688
|
});
|
|
1655
|
-
itemsQuery.select(chunksTable + ".id");
|
|
1689
|
+
itemsQuery.select(chunksTable + ".id as chunk_id");
|
|
1656
1690
|
itemsQuery.select(chunksTable + ".source");
|
|
1657
1691
|
itemsQuery.select(chunksTable + ".content");
|
|
1658
1692
|
itemsQuery.select(chunksTable + ".chunk_index");
|
|
1659
|
-
itemsQuery.select(chunksTable + ".created_at");
|
|
1660
|
-
itemsQuery.select(chunksTable + ".updated_at");
|
|
1693
|
+
itemsQuery.select(chunksTable + ".created_at as chunk_created_at");
|
|
1694
|
+
itemsQuery.select(chunksTable + ".updated_at as chunk_updated_at");
|
|
1661
1695
|
const { chunks } = await this.embedder.generateFromQuery(query);
|
|
1662
1696
|
if (!chunks?.[0]?.vector) {
|
|
1663
1697
|
throw new Error("No vector generated for query.");
|
|
@@ -1699,7 +1733,7 @@ var ExuluContext = class {
|
|
|
1699
1733
|
seenSources.set(item.source, {
|
|
1700
1734
|
...Object.fromEntries(
|
|
1701
1735
|
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"
|
|
1736
|
+
(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
1737
|
).map((key) => [key, item[key]])
|
|
1704
1738
|
),
|
|
1705
1739
|
chunks: [{
|
|
@@ -1728,6 +1762,17 @@ var ExuluContext = class {
|
|
|
1728
1762
|
}
|
|
1729
1763
|
return acc;
|
|
1730
1764
|
}, []);
|
|
1765
|
+
items.forEach((item) => {
|
|
1766
|
+
if (!item.chunks?.length) {
|
|
1767
|
+
return;
|
|
1768
|
+
}
|
|
1769
|
+
const {
|
|
1770
|
+
average,
|
|
1771
|
+
total
|
|
1772
|
+
} = calculateStatistics(item.chunks, method ?? "cosineDistance");
|
|
1773
|
+
item.averageRelevance = average;
|
|
1774
|
+
item.totalRelevance = total;
|
|
1775
|
+
});
|
|
1731
1776
|
if (this.resultReranker && query) {
|
|
1732
1777
|
items = await this.resultReranker(items);
|
|
1733
1778
|
}
|
|
@@ -1850,7 +1895,7 @@ var updateStatistic = async (statistic) => {
|
|
|
1850
1895
|
};
|
|
1851
1896
|
|
|
1852
1897
|
// src/registry/index.ts
|
|
1853
|
-
var
|
|
1898
|
+
var import_express6 = require("express");
|
|
1854
1899
|
|
|
1855
1900
|
// src/registry/routes.ts
|
|
1856
1901
|
var import_express2 = require("express");
|
|
@@ -3004,6 +3049,8 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
|
|
|
3004
3049
|
const routeLogs = [];
|
|
3005
3050
|
var corsOptions = {
|
|
3006
3051
|
origin: "*",
|
|
3052
|
+
exposedHeaders: "*",
|
|
3053
|
+
allowedHeaders: "*",
|
|
3007
3054
|
optionsSuccessStatus: 200
|
|
3008
3055
|
// some legacy browsers (IE11, various SmartTVs) choke on 204
|
|
3009
3056
|
};
|
|
@@ -3074,7 +3121,7 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
|
|
|
3074
3121
|
console.log("[EXULU] graphql server started");
|
|
3075
3122
|
app.use(
|
|
3076
3123
|
"/graphql",
|
|
3077
|
-
(0, import_cors.default)(),
|
|
3124
|
+
(0, import_cors.default)(corsOptions),
|
|
3078
3125
|
import_express3.default.json(),
|
|
3079
3126
|
(0, import_express5.expressMiddleware)(server, {
|
|
3080
3127
|
context: async ({ req }) => {
|
|
@@ -3185,7 +3232,7 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
|
|
|
3185
3232
|
}
|
|
3186
3233
|
const item = await query.first();
|
|
3187
3234
|
if (!item) {
|
|
3188
|
-
|
|
3235
|
+
return null;
|
|
3189
3236
|
}
|
|
3190
3237
|
const chunks = await db2.from(context.getChunksTableName()).where({ source: item.id }).select("id");
|
|
3191
3238
|
if (chunks.length > 0) {
|
|
@@ -3206,6 +3253,12 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
|
|
|
3206
3253
|
id: req.params.id,
|
|
3207
3254
|
contextId: req.params.context
|
|
3208
3255
|
});
|
|
3256
|
+
if (!result) {
|
|
3257
|
+
res.status(200).json({
|
|
3258
|
+
message: "Item not found."
|
|
3259
|
+
});
|
|
3260
|
+
return;
|
|
3261
|
+
}
|
|
3209
3262
|
res.status(200).json(result);
|
|
3210
3263
|
});
|
|
3211
3264
|
app.delete("/items/:context/external/:id", async (req, res) => {
|
|
@@ -3219,6 +3272,12 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
|
|
|
3219
3272
|
external_id: req.params.id,
|
|
3220
3273
|
contextId: req.params.context
|
|
3221
3274
|
});
|
|
3275
|
+
if (!result) {
|
|
3276
|
+
res.status(200).json({
|
|
3277
|
+
message: "Item not found."
|
|
3278
|
+
});
|
|
3279
|
+
return;
|
|
3280
|
+
}
|
|
3222
3281
|
res.status(200).json(result);
|
|
3223
3282
|
});
|
|
3224
3283
|
app.get("/items/:context/:id", async (req, res) => {
|
|
@@ -3915,6 +3974,7 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
|
|
|
3915
3974
|
}
|
|
3916
3975
|
console.log("Routes:");
|
|
3917
3976
|
console.table(routeLogs);
|
|
3977
|
+
return app;
|
|
3918
3978
|
};
|
|
3919
3979
|
var preprocessInputs = async (data) => {
|
|
3920
3980
|
for (const key in data) {
|
|
@@ -4164,16 +4224,160 @@ var createLogsCleanerWorker = (logsDir) => {
|
|
|
4164
4224
|
return logsCleaner;
|
|
4165
4225
|
};
|
|
4166
4226
|
|
|
4227
|
+
// src/mcp/index.ts
|
|
4228
|
+
var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
4229
|
+
var import_node_crypto = require("crypto");
|
|
4230
|
+
var import_streamableHttp = require("@modelcontextprotocol/sdk/server/streamableHttp.js");
|
|
4231
|
+
var import_types = require("@modelcontextprotocol/sdk/types.js");
|
|
4232
|
+
var import_zod3 = require("zod");
|
|
4233
|
+
var import_express4 = require("express");
|
|
4234
|
+
var ExuluMCP = class {
|
|
4235
|
+
server;
|
|
4236
|
+
transports = {};
|
|
4237
|
+
express;
|
|
4238
|
+
constructor() {
|
|
4239
|
+
}
|
|
4240
|
+
create = async ({ express: express2, contexts, embedders, agents, workflows, config, tools }) => {
|
|
4241
|
+
this.express = express2;
|
|
4242
|
+
if (!this.server) {
|
|
4243
|
+
console.log("[EXULU] Creating MCP server.");
|
|
4244
|
+
this.server = new import_mcp.McpServer({
|
|
4245
|
+
name: "exulu-mcp-server",
|
|
4246
|
+
version: "1.0.0"
|
|
4247
|
+
});
|
|
4248
|
+
}
|
|
4249
|
+
this.server.registerTool(
|
|
4250
|
+
"add",
|
|
4251
|
+
{
|
|
4252
|
+
title: "Addition Tool",
|
|
4253
|
+
description: "Add two numbers",
|
|
4254
|
+
inputSchema: { a: import_zod3.z.number(), b: import_zod3.z.number() }
|
|
4255
|
+
},
|
|
4256
|
+
async ({ a, b }) => ({
|
|
4257
|
+
content: [{ type: "text", text: String(a + b) }]
|
|
4258
|
+
})
|
|
4259
|
+
);
|
|
4260
|
+
this.server.registerResource(
|
|
4261
|
+
"greeting",
|
|
4262
|
+
new import_mcp.ResourceTemplate("greeting://{name}", { list: void 0 }),
|
|
4263
|
+
{
|
|
4264
|
+
title: "Greeting Resource",
|
|
4265
|
+
// Display name for UI
|
|
4266
|
+
description: "Dynamic greeting generator"
|
|
4267
|
+
},
|
|
4268
|
+
async (uri, { name }) => ({
|
|
4269
|
+
contents: [{
|
|
4270
|
+
uri: uri.href,
|
|
4271
|
+
text: `Hello, ${name}!`
|
|
4272
|
+
}]
|
|
4273
|
+
})
|
|
4274
|
+
);
|
|
4275
|
+
this.server.registerPrompt(
|
|
4276
|
+
"review-code",
|
|
4277
|
+
{
|
|
4278
|
+
title: "Code Review",
|
|
4279
|
+
description: "Review code for best practices and potential issues",
|
|
4280
|
+
argsSchema: { code: import_zod3.z.string() }
|
|
4281
|
+
},
|
|
4282
|
+
({ code }) => ({
|
|
4283
|
+
messages: [{
|
|
4284
|
+
role: "user",
|
|
4285
|
+
content: {
|
|
4286
|
+
type: "text",
|
|
4287
|
+
text: `Please review this code:
|
|
4288
|
+
|
|
4289
|
+
${code}`
|
|
4290
|
+
}
|
|
4291
|
+
}]
|
|
4292
|
+
})
|
|
4293
|
+
);
|
|
4294
|
+
console.log("Contexts:");
|
|
4295
|
+
console.table();
|
|
4296
|
+
};
|
|
4297
|
+
connect = async () => {
|
|
4298
|
+
if (!this.express) {
|
|
4299
|
+
throw new Error("Express not initialized.");
|
|
4300
|
+
}
|
|
4301
|
+
if (!this.server) {
|
|
4302
|
+
throw new Error("MCP server not initialized.");
|
|
4303
|
+
}
|
|
4304
|
+
console.log("[EXULU] Wiring up MCP server routes to express app.");
|
|
4305
|
+
this.express.post("/mcp", async (req, res) => {
|
|
4306
|
+
const sessionId = req.headers["mcp-session-id"];
|
|
4307
|
+
let transport;
|
|
4308
|
+
if (sessionId && this.transports[sessionId]) {
|
|
4309
|
+
transport = this.transports[sessionId];
|
|
4310
|
+
} else if (!sessionId && (0, import_types.isInitializeRequest)(req.body)) {
|
|
4311
|
+
transport = new import_streamableHttp.StreamableHTTPServerTransport({
|
|
4312
|
+
sessionIdGenerator: () => (0, import_node_crypto.randomUUID)(),
|
|
4313
|
+
onsessioninitialized: (sessionId2) => {
|
|
4314
|
+
this.transports[sessionId2] = transport;
|
|
4315
|
+
}
|
|
4316
|
+
// DNS rebinding protection is disabled by default for backwards compatibility. If you are running this server
|
|
4317
|
+
// locally, make sure to set:
|
|
4318
|
+
// enableDnsRebindingProtection: true,
|
|
4319
|
+
// allowedHosts: ['127.0.0.1'],
|
|
4320
|
+
});
|
|
4321
|
+
transport.onclose = () => {
|
|
4322
|
+
if (transport.sessionId) {
|
|
4323
|
+
delete this.transports[transport.sessionId];
|
|
4324
|
+
}
|
|
4325
|
+
};
|
|
4326
|
+
const server = new import_mcp.McpServer({
|
|
4327
|
+
name: "example-server",
|
|
4328
|
+
version: "1.0.0"
|
|
4329
|
+
});
|
|
4330
|
+
await server.connect(transport);
|
|
4331
|
+
} else {
|
|
4332
|
+
res.status(400).json({
|
|
4333
|
+
jsonrpc: "2.0",
|
|
4334
|
+
error: {
|
|
4335
|
+
code: -32e3,
|
|
4336
|
+
message: "Bad Request: No valid session ID provided"
|
|
4337
|
+
},
|
|
4338
|
+
id: null
|
|
4339
|
+
});
|
|
4340
|
+
return;
|
|
4341
|
+
}
|
|
4342
|
+
await transport.handleRequest(req, res, req.body);
|
|
4343
|
+
});
|
|
4344
|
+
const handleSessionRequest = async (req, res) => {
|
|
4345
|
+
const sessionId = req.headers["mcp-session-id"];
|
|
4346
|
+
if (!sessionId || !this.transports[sessionId]) {
|
|
4347
|
+
res.status(400).send("Invalid or missing session ID");
|
|
4348
|
+
return;
|
|
4349
|
+
}
|
|
4350
|
+
const transport = this.transports[sessionId];
|
|
4351
|
+
await transport.handleRequest(req, res);
|
|
4352
|
+
};
|
|
4353
|
+
this.express.get("/mcp", handleSessionRequest);
|
|
4354
|
+
this.express.delete("/mcp", handleSessionRequest);
|
|
4355
|
+
const routeLogs = [];
|
|
4356
|
+
routeLogs.push(
|
|
4357
|
+
{ route: "/mcp", method: "GET", note: "Get MCP server status" },
|
|
4358
|
+
{ route: "/mcp", method: "POST", note: "Send MCP request" },
|
|
4359
|
+
{ route: "/mcp", method: "DELETE", note: "Terminate MCP session" }
|
|
4360
|
+
);
|
|
4361
|
+
console.log("MCP Routes:");
|
|
4362
|
+
console.table(routeLogs);
|
|
4363
|
+
return this.express;
|
|
4364
|
+
};
|
|
4365
|
+
};
|
|
4366
|
+
|
|
4167
4367
|
// src/registry/index.ts
|
|
4168
4368
|
var ExuluApp = class {
|
|
4169
|
-
_agents;
|
|
4170
|
-
_workflows;
|
|
4369
|
+
_agents = [];
|
|
4370
|
+
_workflows = [];
|
|
4171
4371
|
_config;
|
|
4172
|
-
_embedders;
|
|
4372
|
+
_embedders = [];
|
|
4173
4373
|
_queues = [];
|
|
4174
|
-
_contexts;
|
|
4175
|
-
_tools;
|
|
4176
|
-
constructor(
|
|
4374
|
+
_contexts = {};
|
|
4375
|
+
_tools = [];
|
|
4376
|
+
constructor() {
|
|
4377
|
+
}
|
|
4378
|
+
// Factory function so we can async initialize the
|
|
4379
|
+
// MCP server if needed.
|
|
4380
|
+
create = async ({ contexts, embedders, agents, workflows, config, tools }) => {
|
|
4177
4381
|
this._embedders = embedders ?? [];
|
|
4178
4382
|
this._workflows = workflows ?? [];
|
|
4179
4383
|
this._contexts = contexts ?? {};
|
|
@@ -4185,7 +4389,7 @@ var ExuluApp = class {
|
|
|
4185
4389
|
...workflows?.length ? workflows.map((workflow) => workflow.queue?.name || null) : []
|
|
4186
4390
|
];
|
|
4187
4391
|
this._queues = [...new Set(queues2.filter((o) => !!o))];
|
|
4188
|
-
}
|
|
4392
|
+
};
|
|
4189
4393
|
embedder(id) {
|
|
4190
4394
|
return this._embedders.find((x) => x.id === id);
|
|
4191
4395
|
}
|
|
@@ -4224,7 +4428,7 @@ var ExuluApp = class {
|
|
|
4224
4428
|
Object.values(this._contexts ?? {}),
|
|
4225
4429
|
this._embedders,
|
|
4226
4430
|
this._workflows,
|
|
4227
|
-
this._config
|
|
4431
|
+
this._config?.workers?.logsDir
|
|
4228
4432
|
);
|
|
4229
4433
|
}
|
|
4230
4434
|
}
|
|
@@ -4232,7 +4436,7 @@ var ExuluApp = class {
|
|
|
4232
4436
|
server = {
|
|
4233
4437
|
express: {
|
|
4234
4438
|
init: async (app) => {
|
|
4235
|
-
|
|
4439
|
+
await createExpressRoutes(
|
|
4236
4440
|
app,
|
|
4237
4441
|
this._agents,
|
|
4238
4442
|
this._embedders,
|
|
@@ -4240,6 +4444,20 @@ var ExuluApp = class {
|
|
|
4240
4444
|
this._workflows,
|
|
4241
4445
|
Object.values(this._contexts ?? {})
|
|
4242
4446
|
);
|
|
4447
|
+
if (this._config?.MCP.enabled) {
|
|
4448
|
+
const mcp = new ExuluMCP();
|
|
4449
|
+
await mcp.create({
|
|
4450
|
+
express: app,
|
|
4451
|
+
contexts: this._contexts,
|
|
4452
|
+
embedders: this._embedders,
|
|
4453
|
+
agents: this._agents,
|
|
4454
|
+
workflows: this._workflows,
|
|
4455
|
+
config: this._config,
|
|
4456
|
+
tools: this._tools
|
|
4457
|
+
});
|
|
4458
|
+
await mcp.connect();
|
|
4459
|
+
}
|
|
4460
|
+
return app;
|
|
4243
4461
|
}
|
|
4244
4462
|
}
|
|
4245
4463
|
};
|
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,37 @@ 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 average = items.reduce((acc, item) => {
|
|
1349
|
+
return acc + item[methodProperty];
|
|
1350
|
+
}, 0) / items.length;
|
|
1351
|
+
const total = items.reduce((acc, item) => {
|
|
1352
|
+
return acc + item[methodProperty];
|
|
1353
|
+
}, 0);
|
|
1354
|
+
return {
|
|
1355
|
+
average,
|
|
1356
|
+
total
|
|
1357
|
+
};
|
|
1358
|
+
};
|
|
1328
1359
|
var ExuluTool = class {
|
|
1329
1360
|
id;
|
|
1330
1361
|
name;
|
|
@@ -1592,6 +1623,9 @@ var ExuluContext = class {
|
|
|
1592
1623
|
};
|
|
1593
1624
|
}
|
|
1594
1625
|
if (typeof query === "string") {
|
|
1626
|
+
if (!method) {
|
|
1627
|
+
method = "cosineDistance";
|
|
1628
|
+
}
|
|
1595
1629
|
itemsQuery.limit(limit * 5);
|
|
1596
1630
|
if (statistics) {
|
|
1597
1631
|
updateStatistic({
|
|
@@ -1608,12 +1642,12 @@ var ExuluContext = class {
|
|
|
1608
1642
|
itemsQuery.leftJoin(chunksTable, function() {
|
|
1609
1643
|
this.on(chunksTable + ".source", "=", mainTable + ".id");
|
|
1610
1644
|
});
|
|
1611
|
-
itemsQuery.select(chunksTable + ".id");
|
|
1645
|
+
itemsQuery.select(chunksTable + ".id as chunk_id");
|
|
1612
1646
|
itemsQuery.select(chunksTable + ".source");
|
|
1613
1647
|
itemsQuery.select(chunksTable + ".content");
|
|
1614
1648
|
itemsQuery.select(chunksTable + ".chunk_index");
|
|
1615
|
-
itemsQuery.select(chunksTable + ".created_at");
|
|
1616
|
-
itemsQuery.select(chunksTable + ".updated_at");
|
|
1649
|
+
itemsQuery.select(chunksTable + ".created_at as chunk_created_at");
|
|
1650
|
+
itemsQuery.select(chunksTable + ".updated_at as chunk_updated_at");
|
|
1617
1651
|
const { chunks } = await this.embedder.generateFromQuery(query);
|
|
1618
1652
|
if (!chunks?.[0]?.vector) {
|
|
1619
1653
|
throw new Error("No vector generated for query.");
|
|
@@ -1655,7 +1689,7 @@ var ExuluContext = class {
|
|
|
1655
1689
|
seenSources.set(item.source, {
|
|
1656
1690
|
...Object.fromEntries(
|
|
1657
1691
|
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"
|
|
1692
|
+
(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
1693
|
).map((key) => [key, item[key]])
|
|
1660
1694
|
),
|
|
1661
1695
|
chunks: [{
|
|
@@ -1684,6 +1718,17 @@ var ExuluContext = class {
|
|
|
1684
1718
|
}
|
|
1685
1719
|
return acc;
|
|
1686
1720
|
}, []);
|
|
1721
|
+
items.forEach((item) => {
|
|
1722
|
+
if (!item.chunks?.length) {
|
|
1723
|
+
return;
|
|
1724
|
+
}
|
|
1725
|
+
const {
|
|
1726
|
+
average,
|
|
1727
|
+
total
|
|
1728
|
+
} = calculateStatistics(item.chunks, method ?? "cosineDistance");
|
|
1729
|
+
item.averageRelevance = average;
|
|
1730
|
+
item.totalRelevance = total;
|
|
1731
|
+
});
|
|
1687
1732
|
if (this.resultReranker && query) {
|
|
1688
1733
|
items = await this.resultReranker(items);
|
|
1689
1734
|
}
|
|
@@ -2960,6 +3005,8 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
|
|
|
2960
3005
|
const routeLogs = [];
|
|
2961
3006
|
var corsOptions = {
|
|
2962
3007
|
origin: "*",
|
|
3008
|
+
exposedHeaders: "*",
|
|
3009
|
+
allowedHeaders: "*",
|
|
2963
3010
|
optionsSuccessStatus: 200
|
|
2964
3011
|
// some legacy browsers (IE11, various SmartTVs) choke on 204
|
|
2965
3012
|
};
|
|
@@ -3030,7 +3077,7 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
|
|
|
3030
3077
|
console.log("[EXULU] graphql server started");
|
|
3031
3078
|
app.use(
|
|
3032
3079
|
"/graphql",
|
|
3033
|
-
cors(),
|
|
3080
|
+
cors(corsOptions),
|
|
3034
3081
|
express.json(),
|
|
3035
3082
|
expressMiddleware(server, {
|
|
3036
3083
|
context: async ({ req }) => {
|
|
@@ -3141,7 +3188,7 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
|
|
|
3141
3188
|
}
|
|
3142
3189
|
const item = await query.first();
|
|
3143
3190
|
if (!item) {
|
|
3144
|
-
|
|
3191
|
+
return null;
|
|
3145
3192
|
}
|
|
3146
3193
|
const chunks = await db2.from(context.getChunksTableName()).where({ source: item.id }).select("id");
|
|
3147
3194
|
if (chunks.length > 0) {
|
|
@@ -3162,6 +3209,12 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
|
|
|
3162
3209
|
id: req.params.id,
|
|
3163
3210
|
contextId: req.params.context
|
|
3164
3211
|
});
|
|
3212
|
+
if (!result) {
|
|
3213
|
+
res.status(200).json({
|
|
3214
|
+
message: "Item not found."
|
|
3215
|
+
});
|
|
3216
|
+
return;
|
|
3217
|
+
}
|
|
3165
3218
|
res.status(200).json(result);
|
|
3166
3219
|
});
|
|
3167
3220
|
app.delete("/items/:context/external/:id", async (req, res) => {
|
|
@@ -3175,6 +3228,12 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
|
|
|
3175
3228
|
external_id: req.params.id,
|
|
3176
3229
|
contextId: req.params.context
|
|
3177
3230
|
});
|
|
3231
|
+
if (!result) {
|
|
3232
|
+
res.status(200).json({
|
|
3233
|
+
message: "Item not found."
|
|
3234
|
+
});
|
|
3235
|
+
return;
|
|
3236
|
+
}
|
|
3178
3237
|
res.status(200).json(result);
|
|
3179
3238
|
});
|
|
3180
3239
|
app.get("/items/:context/:id", async (req, res) => {
|
|
@@ -3871,6 +3930,7 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
|
|
|
3871
3930
|
}
|
|
3872
3931
|
console.log("Routes:");
|
|
3873
3932
|
console.table(routeLogs);
|
|
3933
|
+
return app;
|
|
3874
3934
|
};
|
|
3875
3935
|
var preprocessInputs = async (data) => {
|
|
3876
3936
|
for (const key in data) {
|
|
@@ -4120,16 +4180,160 @@ var createLogsCleanerWorker = (logsDir) => {
|
|
|
4120
4180
|
return logsCleaner;
|
|
4121
4181
|
};
|
|
4122
4182
|
|
|
4183
|
+
// src/mcp/index.ts
|
|
4184
|
+
import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
4185
|
+
import { randomUUID } from "crypto";
|
|
4186
|
+
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
4187
|
+
import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
|
|
4188
|
+
import { z as z2 } from "zod";
|
|
4189
|
+
import "express";
|
|
4190
|
+
var ExuluMCP = class {
|
|
4191
|
+
server;
|
|
4192
|
+
transports = {};
|
|
4193
|
+
express;
|
|
4194
|
+
constructor() {
|
|
4195
|
+
}
|
|
4196
|
+
create = async ({ express: express2, contexts, embedders, agents, workflows, config, tools }) => {
|
|
4197
|
+
this.express = express2;
|
|
4198
|
+
if (!this.server) {
|
|
4199
|
+
console.log("[EXULU] Creating MCP server.");
|
|
4200
|
+
this.server = new McpServer({
|
|
4201
|
+
name: "exulu-mcp-server",
|
|
4202
|
+
version: "1.0.0"
|
|
4203
|
+
});
|
|
4204
|
+
}
|
|
4205
|
+
this.server.registerTool(
|
|
4206
|
+
"add",
|
|
4207
|
+
{
|
|
4208
|
+
title: "Addition Tool",
|
|
4209
|
+
description: "Add two numbers",
|
|
4210
|
+
inputSchema: { a: z2.number(), b: z2.number() }
|
|
4211
|
+
},
|
|
4212
|
+
async ({ a, b }) => ({
|
|
4213
|
+
content: [{ type: "text", text: String(a + b) }]
|
|
4214
|
+
})
|
|
4215
|
+
);
|
|
4216
|
+
this.server.registerResource(
|
|
4217
|
+
"greeting",
|
|
4218
|
+
new ResourceTemplate("greeting://{name}", { list: void 0 }),
|
|
4219
|
+
{
|
|
4220
|
+
title: "Greeting Resource",
|
|
4221
|
+
// Display name for UI
|
|
4222
|
+
description: "Dynamic greeting generator"
|
|
4223
|
+
},
|
|
4224
|
+
async (uri, { name }) => ({
|
|
4225
|
+
contents: [{
|
|
4226
|
+
uri: uri.href,
|
|
4227
|
+
text: `Hello, ${name}!`
|
|
4228
|
+
}]
|
|
4229
|
+
})
|
|
4230
|
+
);
|
|
4231
|
+
this.server.registerPrompt(
|
|
4232
|
+
"review-code",
|
|
4233
|
+
{
|
|
4234
|
+
title: "Code Review",
|
|
4235
|
+
description: "Review code for best practices and potential issues",
|
|
4236
|
+
argsSchema: { code: z2.string() }
|
|
4237
|
+
},
|
|
4238
|
+
({ code }) => ({
|
|
4239
|
+
messages: [{
|
|
4240
|
+
role: "user",
|
|
4241
|
+
content: {
|
|
4242
|
+
type: "text",
|
|
4243
|
+
text: `Please review this code:
|
|
4244
|
+
|
|
4245
|
+
${code}`
|
|
4246
|
+
}
|
|
4247
|
+
}]
|
|
4248
|
+
})
|
|
4249
|
+
);
|
|
4250
|
+
console.log("Contexts:");
|
|
4251
|
+
console.table();
|
|
4252
|
+
};
|
|
4253
|
+
connect = async () => {
|
|
4254
|
+
if (!this.express) {
|
|
4255
|
+
throw new Error("Express not initialized.");
|
|
4256
|
+
}
|
|
4257
|
+
if (!this.server) {
|
|
4258
|
+
throw new Error("MCP server not initialized.");
|
|
4259
|
+
}
|
|
4260
|
+
console.log("[EXULU] Wiring up MCP server routes to express app.");
|
|
4261
|
+
this.express.post("/mcp", async (req, res) => {
|
|
4262
|
+
const sessionId = req.headers["mcp-session-id"];
|
|
4263
|
+
let transport;
|
|
4264
|
+
if (sessionId && this.transports[sessionId]) {
|
|
4265
|
+
transport = this.transports[sessionId];
|
|
4266
|
+
} else if (!sessionId && isInitializeRequest(req.body)) {
|
|
4267
|
+
transport = new StreamableHTTPServerTransport({
|
|
4268
|
+
sessionIdGenerator: () => randomUUID(),
|
|
4269
|
+
onsessioninitialized: (sessionId2) => {
|
|
4270
|
+
this.transports[sessionId2] = transport;
|
|
4271
|
+
}
|
|
4272
|
+
// DNS rebinding protection is disabled by default for backwards compatibility. If you are running this server
|
|
4273
|
+
// locally, make sure to set:
|
|
4274
|
+
// enableDnsRebindingProtection: true,
|
|
4275
|
+
// allowedHosts: ['127.0.0.1'],
|
|
4276
|
+
});
|
|
4277
|
+
transport.onclose = () => {
|
|
4278
|
+
if (transport.sessionId) {
|
|
4279
|
+
delete this.transports[transport.sessionId];
|
|
4280
|
+
}
|
|
4281
|
+
};
|
|
4282
|
+
const server = new McpServer({
|
|
4283
|
+
name: "example-server",
|
|
4284
|
+
version: "1.0.0"
|
|
4285
|
+
});
|
|
4286
|
+
await server.connect(transport);
|
|
4287
|
+
} else {
|
|
4288
|
+
res.status(400).json({
|
|
4289
|
+
jsonrpc: "2.0",
|
|
4290
|
+
error: {
|
|
4291
|
+
code: -32e3,
|
|
4292
|
+
message: "Bad Request: No valid session ID provided"
|
|
4293
|
+
},
|
|
4294
|
+
id: null
|
|
4295
|
+
});
|
|
4296
|
+
return;
|
|
4297
|
+
}
|
|
4298
|
+
await transport.handleRequest(req, res, req.body);
|
|
4299
|
+
});
|
|
4300
|
+
const handleSessionRequest = async (req, res) => {
|
|
4301
|
+
const sessionId = req.headers["mcp-session-id"];
|
|
4302
|
+
if (!sessionId || !this.transports[sessionId]) {
|
|
4303
|
+
res.status(400).send("Invalid or missing session ID");
|
|
4304
|
+
return;
|
|
4305
|
+
}
|
|
4306
|
+
const transport = this.transports[sessionId];
|
|
4307
|
+
await transport.handleRequest(req, res);
|
|
4308
|
+
};
|
|
4309
|
+
this.express.get("/mcp", handleSessionRequest);
|
|
4310
|
+
this.express.delete("/mcp", handleSessionRequest);
|
|
4311
|
+
const routeLogs = [];
|
|
4312
|
+
routeLogs.push(
|
|
4313
|
+
{ route: "/mcp", method: "GET", note: "Get MCP server status" },
|
|
4314
|
+
{ route: "/mcp", method: "POST", note: "Send MCP request" },
|
|
4315
|
+
{ route: "/mcp", method: "DELETE", note: "Terminate MCP session" }
|
|
4316
|
+
);
|
|
4317
|
+
console.log("MCP Routes:");
|
|
4318
|
+
console.table(routeLogs);
|
|
4319
|
+
return this.express;
|
|
4320
|
+
};
|
|
4321
|
+
};
|
|
4322
|
+
|
|
4123
4323
|
// src/registry/index.ts
|
|
4124
4324
|
var ExuluApp = class {
|
|
4125
|
-
_agents;
|
|
4126
|
-
_workflows;
|
|
4325
|
+
_agents = [];
|
|
4326
|
+
_workflows = [];
|
|
4127
4327
|
_config;
|
|
4128
|
-
_embedders;
|
|
4328
|
+
_embedders = [];
|
|
4129
4329
|
_queues = [];
|
|
4130
|
-
_contexts;
|
|
4131
|
-
_tools;
|
|
4132
|
-
constructor(
|
|
4330
|
+
_contexts = {};
|
|
4331
|
+
_tools = [];
|
|
4332
|
+
constructor() {
|
|
4333
|
+
}
|
|
4334
|
+
// Factory function so we can async initialize the
|
|
4335
|
+
// MCP server if needed.
|
|
4336
|
+
create = async ({ contexts, embedders, agents, workflows, config, tools }) => {
|
|
4133
4337
|
this._embedders = embedders ?? [];
|
|
4134
4338
|
this._workflows = workflows ?? [];
|
|
4135
4339
|
this._contexts = contexts ?? {};
|
|
@@ -4141,7 +4345,7 @@ var ExuluApp = class {
|
|
|
4141
4345
|
...workflows?.length ? workflows.map((workflow) => workflow.queue?.name || null) : []
|
|
4142
4346
|
];
|
|
4143
4347
|
this._queues = [...new Set(queues2.filter((o) => !!o))];
|
|
4144
|
-
}
|
|
4348
|
+
};
|
|
4145
4349
|
embedder(id) {
|
|
4146
4350
|
return this._embedders.find((x) => x.id === id);
|
|
4147
4351
|
}
|
|
@@ -4180,7 +4384,7 @@ var ExuluApp = class {
|
|
|
4180
4384
|
Object.values(this._contexts ?? {}),
|
|
4181
4385
|
this._embedders,
|
|
4182
4386
|
this._workflows,
|
|
4183
|
-
this._config
|
|
4387
|
+
this._config?.workers?.logsDir
|
|
4184
4388
|
);
|
|
4185
4389
|
}
|
|
4186
4390
|
}
|
|
@@ -4188,7 +4392,7 @@ var ExuluApp = class {
|
|
|
4188
4392
|
server = {
|
|
4189
4393
|
express: {
|
|
4190
4394
|
init: async (app) => {
|
|
4191
|
-
|
|
4395
|
+
await createExpressRoutes(
|
|
4192
4396
|
app,
|
|
4193
4397
|
this._agents,
|
|
4194
4398
|
this._embedders,
|
|
@@ -4196,6 +4400,20 @@ var ExuluApp = class {
|
|
|
4196
4400
|
this._workflows,
|
|
4197
4401
|
Object.values(this._contexts ?? {})
|
|
4198
4402
|
);
|
|
4403
|
+
if (this._config?.MCP.enabled) {
|
|
4404
|
+
const mcp = new ExuluMCP();
|
|
4405
|
+
await mcp.create({
|
|
4406
|
+
express: app,
|
|
4407
|
+
contexts: this._contexts,
|
|
4408
|
+
embedders: this._embedders,
|
|
4409
|
+
agents: this._agents,
|
|
4410
|
+
workflows: this._workflows,
|
|
4411
|
+
config: this._config,
|
|
4412
|
+
tools: this._tools
|
|
4413
|
+
});
|
|
4414
|
+
await mcp.connect();
|
|
4415
|
+
}
|
|
4416
|
+
return app;
|
|
4199
4417
|
}
|
|
4200
4418
|
}
|
|
4201
4419
|
};
|
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.10",
|
|
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",
|