@exulu/backend 0.3.8 → 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 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 import_express4 = require("express");
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
- throw new Error("Item not found.");
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({ contexts, embedders, agents, workflows, config, tools }) {
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.workers?.logsDir
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
- return await createExpressRoutes(
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({ contexts, embedders, agents, workflows, config, tools }: {
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<void>;
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({ contexts, embedders, agents, workflows, config, tools }: {
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<void>;
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
- throw new Error("Item not found.");
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({ contexts, embedders, agents, workflows, config, tools }) {
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.workers?.logsDir
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
- return await createExpressRoutes(
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
@@ -0,0 +1,6 @@
1
+ To run ngrok so a cloud based client such as ChatGPT or Anthropic Claude can connect to your local MCP
2
+ run the command:
3
+
4
+ ```bash
5
+ ngrok start --all --config ./ngrok.yml
6
+ ```
package/ngrok.yml ADDED
@@ -0,0 +1,10 @@
1
+ version: "2"
2
+ authtoken: 1bDq4H21OulxgyopkEHZ5eH3Psh_6aNDpzDuBCTCVjtK7nMNx
3
+ tunnels:
4
+ backend:
5
+ #host_header: "rewrite"
6
+ #response_header:
7
+ #add: ["Access-Control-Allow-Origin: *"]
8
+ proto: http
9
+ addr: 9001
10
+ domain: exulu.ngrok.app
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@exulu/backend",
3
3
  "author": "Qventu Bv.",
4
- "version": "0.3.8",
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",