@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 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 import_express4 = require("express");
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
- throw new Error("Item not found.");
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({ contexts, embedders, agents, workflows, config, tools }) {
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.workers?.logsDir
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
- return await createExpressRoutes(
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({ 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,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
- throw new Error("Item not found.");
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({ contexts, embedders, agents, workflows, config, tools }) {
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.workers?.logsDir
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
- return await createExpressRoutes(
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
@@ -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.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",