@exulu/backend 1.30.5 → 1.31.1
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/CHANGELOG.md +2 -2
- package/dist/index.cjs +271 -111
- package/dist/index.js +272 -112
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
## [1.
|
|
1
|
+
## [1.31.1](https://github.com/Qventu/exulu-backend/compare/v1.31.0...v1.31.1) (2025-11-10)
|
|
2
2
|
|
|
3
3
|
|
|
4
4
|
### Bug Fixes
|
|
5
5
|
|
|
6
|
-
*
|
|
6
|
+
* redis auth url ([bbf51d6](https://github.com/Qventu/exulu-backend/commit/bbf51d63be8dbbc88e8989633207db4b147e3da6))
|
package/dist/index.cjs
CHANGED
|
@@ -60,7 +60,8 @@ var import_redis = require("redis");
|
|
|
60
60
|
var redisServer = {
|
|
61
61
|
host: `${process.env.REDIS_HOST}`,
|
|
62
62
|
port: process.env.REDIS_PORT,
|
|
63
|
-
password: process.env.REDIS_PASSWORD || void 0
|
|
63
|
+
password: process.env.REDIS_PASSWORD || void 0,
|
|
64
|
+
username: process.env.REDIS_USER || void 0
|
|
64
65
|
};
|
|
65
66
|
|
|
66
67
|
// src/redis/client.ts
|
|
@@ -71,7 +72,12 @@ async function redisClient() {
|
|
|
71
72
|
}
|
|
72
73
|
if (!client["exulu"]) {
|
|
73
74
|
try {
|
|
74
|
-
|
|
75
|
+
let url = "";
|
|
76
|
+
if (redisServer.username) {
|
|
77
|
+
url = `redis://${redisServer.username}:${redisServer.password}@${redisServer.host}:${redisServer.port}`;
|
|
78
|
+
} else {
|
|
79
|
+
url = `redis://${redisServer.host}:${redisServer.port}`;
|
|
80
|
+
}
|
|
75
81
|
client["exulu"] = (0, import_redis.createClient)({
|
|
76
82
|
url
|
|
77
83
|
});
|
|
@@ -437,8 +443,6 @@ var sanitizeName = (name) => {
|
|
|
437
443
|
|
|
438
444
|
// src/registry/classes.ts
|
|
439
445
|
var import_crypto_js2 = __toESM(require("crypto-js"), 1);
|
|
440
|
-
var import_streamableHttp = require("@modelcontextprotocol/sdk/client/streamableHttp.js");
|
|
441
|
-
var import_mcp_stdio = require("ai/mcp-stdio");
|
|
442
446
|
|
|
443
447
|
// src/registry/utils/graphql.ts
|
|
444
448
|
var import_schema = require("@graphql-tools/schema");
|
|
@@ -1141,6 +1145,10 @@ var statisticsSchema = {
|
|
|
1141
1145
|
{
|
|
1142
1146
|
name: "role",
|
|
1143
1147
|
type: "uuid"
|
|
1148
|
+
},
|
|
1149
|
+
{
|
|
1150
|
+
name: "project",
|
|
1151
|
+
type: "uuid"
|
|
1144
1152
|
}
|
|
1145
1153
|
]
|
|
1146
1154
|
};
|
|
@@ -4040,7 +4048,6 @@ type StatisticsResult {
|
|
|
4040
4048
|
}
|
|
4041
4049
|
`;
|
|
4042
4050
|
const fullSDL = typeDefs + mutationDefs + modelDefs + genericTypes;
|
|
4043
|
-
console.log("[EXULU] Full SDL:", fullSDL);
|
|
4044
4051
|
const schema = (0, import_schema.makeExecutableSchema)({
|
|
4045
4052
|
typeDefs: fullSDL,
|
|
4046
4053
|
resolvers
|
|
@@ -4633,7 +4640,6 @@ var createUppyRoutes = async (app, config) => {
|
|
|
4633
4640
|
};
|
|
4634
4641
|
|
|
4635
4642
|
// src/registry/classes.ts
|
|
4636
|
-
var import_streamableHttp2 = require("@modelcontextprotocol/sdk/server/streamableHttp.js");
|
|
4637
4643
|
var s3Client2;
|
|
4638
4644
|
function sanitizeToolName(name) {
|
|
4639
4645
|
if (typeof name !== "string") return "";
|
|
@@ -4875,7 +4881,6 @@ var ExuluAgent2 = class {
|
|
|
4875
4881
|
return typeof model === "string" ? model : model?.modelId || "";
|
|
4876
4882
|
}
|
|
4877
4883
|
// Exports the agent as a tool that can be used by another agent
|
|
4878
|
-
// todo test this
|
|
4879
4884
|
tool = async (instance, agents) => {
|
|
4880
4885
|
const agentInstance = await loadAgent(instance);
|
|
4881
4886
|
if (!agentInstance) {
|
|
@@ -5794,6 +5799,7 @@ var updateStatistic = async (statistic) => {
|
|
|
5794
5799
|
const existing = await db3.from("tracking").where({
|
|
5795
5800
|
...statistic.user ? { user: statistic.user } : {},
|
|
5796
5801
|
...statistic.role ? { role: statistic.role } : {},
|
|
5802
|
+
...statistic.project ? { project: statistic.project } : {},
|
|
5797
5803
|
name: statistic.name,
|
|
5798
5804
|
label: statistic.label,
|
|
5799
5805
|
type: statistic.type,
|
|
@@ -5807,16 +5813,14 @@ var updateStatistic = async (statistic) => {
|
|
|
5807
5813
|
total: statistic.count ?? 1,
|
|
5808
5814
|
createdAt: currentDate,
|
|
5809
5815
|
...statistic.user ? { user: statistic.user } : {},
|
|
5810
|
-
...statistic.role ? { role: statistic.role } : {}
|
|
5816
|
+
...statistic.role ? { role: statistic.role } : {},
|
|
5817
|
+
...statistic.project ? { project: statistic.project } : {}
|
|
5811
5818
|
});
|
|
5812
5819
|
} else {
|
|
5813
5820
|
await db3.from("tracking").update({
|
|
5814
5821
|
total: db3.raw("total + ?", [statistic.count ?? 1])
|
|
5815
5822
|
}).where({
|
|
5816
|
-
|
|
5817
|
-
label: statistic.label,
|
|
5818
|
-
type: statistic.type,
|
|
5819
|
-
createdAt: currentDate
|
|
5823
|
+
id: existing.id
|
|
5820
5824
|
});
|
|
5821
5825
|
}
|
|
5822
5826
|
};
|
|
@@ -5906,6 +5910,9 @@ var CLAUDE_MESSAGES = {
|
|
|
5906
5910
|
\x1B[0m`,
|
|
5907
5911
|
not_enabled: `
|
|
5908
5912
|
\x1B[41m -- The agent you selected does not have a valid API key set for it. --
|
|
5913
|
+
\x1B[0m`,
|
|
5914
|
+
missing_project: `
|
|
5915
|
+
\x1B[41m -- Project not found or you do not have access to it. --
|
|
5909
5916
|
\x1B[0m`
|
|
5910
5917
|
};
|
|
5911
5918
|
|
|
@@ -6208,7 +6215,6 @@ Mood: friendly and intelligent.
|
|
|
6208
6215
|
console.log("[EXULU] agent tools", agentInstance.tools?.map((x) => x.name + " (" + x.id + ")"));
|
|
6209
6216
|
const disabledTools = req.body.disabledTools ? req.body.disabledTools : [];
|
|
6210
6217
|
let enabledTools = await getEnabledTools(agentInstance, tools, disabledTools, agents, user);
|
|
6211
|
-
console.log("[EXULU] enabled tools", enabledTools?.map((x) => x.name + " (" + x.id + ")"));
|
|
6212
6218
|
const variableName = agentInstance.providerapikey;
|
|
6213
6219
|
const variable = await db3.from("variables").where({ name: variableName }).first();
|
|
6214
6220
|
if (!variable) {
|
|
@@ -6356,11 +6362,12 @@ Mood: friendly and intelligent.
|
|
|
6356
6362
|
}
|
|
6357
6363
|
});
|
|
6358
6364
|
});
|
|
6359
|
-
app.use("/gateway/anthropic/:
|
|
6365
|
+
app.use("/gateway/anthropic/:agent/:project", import_express3.default.raw({ type: "*/*", limit: REQUEST_SIZE_LIMIT }), async (req, res) => {
|
|
6360
6366
|
try {
|
|
6361
6367
|
if (!req.body.tools) {
|
|
6362
6368
|
req.body.tools = [];
|
|
6363
6369
|
}
|
|
6370
|
+
const { db: db3 } = await postgresClient();
|
|
6364
6371
|
const authenticationResult = await requestValidators.authenticate(req);
|
|
6365
6372
|
if (!authenticationResult.user?.id) {
|
|
6366
6373
|
console.log("[EXULU] failed authentication result", authenticationResult);
|
|
@@ -6368,20 +6375,35 @@ Mood: friendly and intelligent.
|
|
|
6368
6375
|
return;
|
|
6369
6376
|
}
|
|
6370
6377
|
const user = authenticationResult.user;
|
|
6371
|
-
|
|
6372
|
-
|
|
6373
|
-
|
|
6374
|
-
|
|
6375
|
-
|
|
6376
|
-
const agent = await query.first();
|
|
6378
|
+
let agentQuery = db3("agents");
|
|
6379
|
+
agentQuery.select("*");
|
|
6380
|
+
agentQuery = applyAccessControl(agentsSchema2(), authenticationResult.user, agentQuery);
|
|
6381
|
+
agentQuery.where({ id: req.params.agent });
|
|
6382
|
+
const agent = await agentQuery.first();
|
|
6377
6383
|
if (!agent) {
|
|
6378
6384
|
const arrayBuffer = createCustomAnthropicStreamingMessage(`
|
|
6379
|
-
\x1B[41m -- Agent ${req.params.
|
|
6385
|
+
\x1B[41m -- Agent ${req.params.agent} not found or you do not have access to it. --
|
|
6380
6386
|
\x1B[0m`);
|
|
6381
6387
|
res.setHeader("Content-Type", "application/json");
|
|
6382
6388
|
res.end(Buffer.from(arrayBuffer));
|
|
6383
6389
|
return;
|
|
6384
6390
|
}
|
|
6391
|
+
let project = null;
|
|
6392
|
+
if (!req.body.project || req.body.project === "DEFAULT") {
|
|
6393
|
+
project = null;
|
|
6394
|
+
} else {
|
|
6395
|
+
let projectQuery = db3("projects");
|
|
6396
|
+
projectQuery.select("*");
|
|
6397
|
+
projectQuery = applyAccessControl(projectsSchema2(), authenticationResult.user, projectQuery);
|
|
6398
|
+
projectQuery.where({ id: req.params.project });
|
|
6399
|
+
project = await projectQuery.first();
|
|
6400
|
+
if (!project) {
|
|
6401
|
+
const arrayBuffer = createCustomAnthropicStreamingMessage(CLAUDE_MESSAGES.missing_project);
|
|
6402
|
+
res.setHeader("Content-Type", "application/json");
|
|
6403
|
+
res.end(Buffer.from(arrayBuffer));
|
|
6404
|
+
return;
|
|
6405
|
+
}
|
|
6406
|
+
}
|
|
6385
6407
|
console.log("[EXULU] anthropic proxy called for agent:", agent?.name);
|
|
6386
6408
|
if (!process.env.NEXTAUTH_SECRET) {
|
|
6387
6409
|
const arrayBuffer = createCustomAnthropicStreamingMessage(CLAUDE_MESSAGES.missing_nextauth_secret);
|
|
@@ -6425,10 +6447,47 @@ Mood: friendly and intelligent.
|
|
|
6425
6447
|
apiKey: anthropicApiKey
|
|
6426
6448
|
});
|
|
6427
6449
|
const tokens = {};
|
|
6428
|
-
|
|
6429
|
-
|
|
6430
|
-
|
|
6431
|
-
|
|
6450
|
+
const disabledTools = req.body.disabledTools ? req.body.disabledTools : [];
|
|
6451
|
+
let enabledTools = await getEnabledTools(agent, tools, disabledTools, agents, user);
|
|
6452
|
+
let system = req.body.system;
|
|
6453
|
+
if (Array.isArray(req.body.system)) {
|
|
6454
|
+
system = [
|
|
6455
|
+
...req.body.system,
|
|
6456
|
+
...agent ? [
|
|
6457
|
+
{
|
|
6458
|
+
type: "text",
|
|
6459
|
+
text: `
|
|
6460
|
+
You are an agent named: ${agent?.name}
|
|
6461
|
+
Here are some additional instructions for you: ${agent?.instructions}`
|
|
6462
|
+
}
|
|
6463
|
+
] : [],
|
|
6464
|
+
...project ? [
|
|
6465
|
+
{
|
|
6466
|
+
type: "text",
|
|
6467
|
+
text: `Additional information:
|
|
6468
|
+
|
|
6469
|
+
The project you are working on is: ${project?.name}
|
|
6470
|
+
Here is some additional information about the project: ${project?.description}`
|
|
6471
|
+
}
|
|
6472
|
+
] : []
|
|
6473
|
+
];
|
|
6474
|
+
} else {
|
|
6475
|
+
system = `${req.body.system}
|
|
6476
|
+
|
|
6477
|
+
|
|
6478
|
+
${agent ? `You are an agent named: ${agent?.name}
|
|
6479
|
+
Here are some additional instructions for you: ${agent?.instructions}` : ""}
|
|
6480
|
+
|
|
6481
|
+
${project?.id ? `Additional information:
|
|
6482
|
+
|
|
6483
|
+
The project you are working on is: ${project?.name}
|
|
6484
|
+
The project description is: ${project?.description}` : ""}
|
|
6485
|
+
`;
|
|
6486
|
+
}
|
|
6487
|
+
for await (const event of client2.messages.stream({
|
|
6488
|
+
...req.body,
|
|
6489
|
+
system
|
|
6490
|
+
})) {
|
|
6432
6491
|
if (event.message?.id) {
|
|
6433
6492
|
tokens[event.message.id] = {
|
|
6434
6493
|
input_tokens: event.message.usage.input_tokens,
|
|
@@ -6437,11 +6496,45 @@ Mood: friendly and intelligent.
|
|
|
6437
6496
|
output_tokens: event.message.usage.output_tokens
|
|
6438
6497
|
};
|
|
6439
6498
|
}
|
|
6440
|
-
|
|
6499
|
+
if (event.message?.type === "tool_use" && event.message?.name?.includes("exulu_")) {
|
|
6500
|
+
const toolName = event.message?.name;
|
|
6501
|
+
console.log("[EXULU] Using tool", toolName);
|
|
6502
|
+
const inputs = event.message?.input;
|
|
6503
|
+
const id = event.message?.id;
|
|
6504
|
+
const tool2 = enabledTools.find((tool3) => tool3.id === toolName.replace("exulu_", ""));
|
|
6505
|
+
if (!tool2 || !tool2.tool.execute) {
|
|
6506
|
+
console.error("[EXULU] Tool not found or not enabled.", toolName);
|
|
6507
|
+
continue;
|
|
6508
|
+
}
|
|
6509
|
+
const toolResult = await tool2.tool.execute(inputs, {
|
|
6510
|
+
toolCallId: id,
|
|
6511
|
+
messages: [{
|
|
6512
|
+
...event.message,
|
|
6513
|
+
role: "tool"
|
|
6514
|
+
}]
|
|
6515
|
+
});
|
|
6516
|
+
console.log("[EXULU] Tool result", toolResult);
|
|
6517
|
+
const toolResultMessage = {
|
|
6518
|
+
role: "user",
|
|
6519
|
+
content: [
|
|
6520
|
+
{
|
|
6521
|
+
type: "tool_result",
|
|
6522
|
+
tool_use_id: id,
|
|
6523
|
+
content: toolResult
|
|
6524
|
+
}
|
|
6525
|
+
]
|
|
6526
|
+
};
|
|
6527
|
+
res.write(`event: tool_result
|
|
6528
|
+
data: ${JSON.stringify(toolResultMessage)}
|
|
6529
|
+
|
|
6530
|
+
`);
|
|
6531
|
+
} else {
|
|
6532
|
+
const msg = `event: ${event.type}
|
|
6441
6533
|
data: ${JSON.stringify(event)}
|
|
6442
6534
|
|
|
6443
6535
|
`;
|
|
6444
|
-
|
|
6536
|
+
res.write(msg);
|
|
6537
|
+
}
|
|
6445
6538
|
}
|
|
6446
6539
|
let totalInputTokens = 0;
|
|
6447
6540
|
let totalOutputTokens = 0;
|
|
@@ -6461,7 +6554,8 @@ data: ${JSON.stringify(event)}
|
|
|
6461
6554
|
trigger: statistics.trigger,
|
|
6462
6555
|
count: 1,
|
|
6463
6556
|
user: user.id,
|
|
6464
|
-
role: user?.role?.id
|
|
6557
|
+
role: user?.role?.id,
|
|
6558
|
+
...project ? { project: project.id } : {}
|
|
6465
6559
|
}),
|
|
6466
6560
|
...totalInputTokens ? [
|
|
6467
6561
|
updateStatistic({
|
|
@@ -6471,7 +6565,8 @@ data: ${JSON.stringify(event)}
|
|
|
6471
6565
|
trigger: statistics.trigger,
|
|
6472
6566
|
count: totalInputTokens,
|
|
6473
6567
|
user: user.id,
|
|
6474
|
-
role: user?.role?.id
|
|
6568
|
+
role: user?.role?.id,
|
|
6569
|
+
...project ? { project: project.id } : {}
|
|
6475
6570
|
})
|
|
6476
6571
|
] : [],
|
|
6477
6572
|
...totalOutputTokens ? [
|
|
@@ -6480,7 +6575,10 @@ data: ${JSON.stringify(event)}
|
|
|
6480
6575
|
label: statistics.label,
|
|
6481
6576
|
type: STATISTICS_TYPE_ENUM.AGENT_RUN,
|
|
6482
6577
|
trigger: statistics.trigger,
|
|
6483
|
-
count:
|
|
6578
|
+
count: totalInputTokens,
|
|
6579
|
+
user: user.id,
|
|
6580
|
+
role: user?.role?.id,
|
|
6581
|
+
...project ? { project: project.id } : {}
|
|
6484
6582
|
})
|
|
6485
6583
|
] : []
|
|
6486
6584
|
]);
|
|
@@ -7199,97 +7297,159 @@ function getAverage(arr) {
|
|
|
7199
7297
|
// src/mcp/index.ts
|
|
7200
7298
|
var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
7201
7299
|
var import_node_crypto4 = require("crypto");
|
|
7202
|
-
var
|
|
7300
|
+
var import_streamableHttp = require("@modelcontextprotocol/sdk/server/streamableHttp.js");
|
|
7203
7301
|
var import_types = require("@modelcontextprotocol/sdk/types.js");
|
|
7204
|
-
var import_zod2 = require("zod");
|
|
7205
7302
|
var import_express4 = require("express");
|
|
7206
7303
|
var import_api3 = require("@opentelemetry/api");
|
|
7304
|
+
var import_crypto_js5 = __toESM(require("crypto-js"), 1);
|
|
7305
|
+
var import_zod2 = require("zod");
|
|
7207
7306
|
var SESSION_ID_HEADER = "mcp-session-id";
|
|
7208
7307
|
var ExuluMCP = class {
|
|
7209
|
-
server;
|
|
7308
|
+
server = {};
|
|
7210
7309
|
transports = {};
|
|
7211
|
-
express;
|
|
7212
7310
|
constructor() {
|
|
7213
7311
|
}
|
|
7214
|
-
|
|
7215
|
-
|
|
7216
|
-
if (!
|
|
7312
|
+
configure = async ({ user, agentInstance, allTools, allAgents, allContexts, config, tracer }) => {
|
|
7313
|
+
let server = this.server[agentInstance.id];
|
|
7314
|
+
if (!server) {
|
|
7217
7315
|
console.log("[EXULU] Creating MCP server.");
|
|
7218
|
-
|
|
7219
|
-
|
|
7220
|
-
|
|
7316
|
+
server = {
|
|
7317
|
+
mcp: new import_mcp.McpServer({
|
|
7318
|
+
name: "exulu-mcp-server-" + agentInstance.name + "(" + agentInstance.id + ")",
|
|
7319
|
+
version: "1.0.0"
|
|
7320
|
+
}),
|
|
7321
|
+
tools: {}
|
|
7322
|
+
};
|
|
7323
|
+
this.server[agentInstance.id] = server;
|
|
7324
|
+
}
|
|
7325
|
+
const disabledTools = [];
|
|
7326
|
+
let enabledTools = await getEnabledTools(agentInstance, allTools, disabledTools, allAgents, user);
|
|
7327
|
+
const backend = allAgents.find((a) => a.id === agentInstance.backend);
|
|
7328
|
+
if (!backend) {
|
|
7329
|
+
throw new Error("Agent backend not found for agent " + agentInstance.name + " (" + agentInstance.id + ").");
|
|
7330
|
+
}
|
|
7331
|
+
const agentTool = await backend.tool(agentInstance.id, allAgents);
|
|
7332
|
+
if (agentTool) {
|
|
7333
|
+
enabledTools = [
|
|
7334
|
+
...enabledTools,
|
|
7335
|
+
agentTool
|
|
7336
|
+
];
|
|
7337
|
+
}
|
|
7338
|
+
const variableName = agentInstance.providerapikey;
|
|
7339
|
+
const { db: db3 } = await postgresClient();
|
|
7340
|
+
const variable = await db3.from("variables").where({ name: variableName }).first();
|
|
7341
|
+
if (!variable) {
|
|
7342
|
+
throw new Error("Provider API key variable not found.");
|
|
7343
|
+
}
|
|
7344
|
+
let providerapikey = variable.value;
|
|
7345
|
+
if (!variable.encrypted) {
|
|
7346
|
+
throw new Error("Provider API key variable not encrypted, for security reasons you are only allowed to use encrypted variables for provider API keys.");
|
|
7347
|
+
}
|
|
7348
|
+
if (variable.encrypted) {
|
|
7349
|
+
const bytes = import_crypto_js5.default.AES.decrypt(variable.value, process.env.NEXTAUTH_SECRET);
|
|
7350
|
+
providerapikey = bytes.toString(import_crypto_js5.default.enc.Utf8);
|
|
7351
|
+
}
|
|
7352
|
+
console.log("[EXULU] Enabled tools", enabledTools?.map((x) => x.name + " (" + x.id + ")"));
|
|
7353
|
+
for (const tool2 of enabledTools || []) {
|
|
7354
|
+
if (server.tools[tool2.id]) {
|
|
7355
|
+
continue;
|
|
7356
|
+
}
|
|
7357
|
+
server.mcp.registerTool(sanitizeToolName(tool2.name + "_agent_" + tool2.id), {
|
|
7358
|
+
title: tool2.name + " agent",
|
|
7359
|
+
description: tool2.description,
|
|
7360
|
+
inputSchema: {
|
|
7361
|
+
inputs: tool2.inputSchema || import_zod2.z.object({})
|
|
7362
|
+
}
|
|
7363
|
+
}, async ({ inputs }, args) => {
|
|
7364
|
+
console.log("[EXULU] MCP tool name", tool2.name);
|
|
7365
|
+
console.log("[EXULU] MCP tool inputs", inputs);
|
|
7366
|
+
console.log("[EXULU] MCP tool args", args);
|
|
7367
|
+
const configValues = agentInstance.tools;
|
|
7368
|
+
const tools = convertToolsArrayToObject(
|
|
7369
|
+
[tool2],
|
|
7370
|
+
allTools,
|
|
7371
|
+
configValues,
|
|
7372
|
+
providerapikey,
|
|
7373
|
+
allContexts,
|
|
7374
|
+
user,
|
|
7375
|
+
config
|
|
7376
|
+
);
|
|
7377
|
+
const convertedTool = tools[sanitizeToolName(tool2.name)];
|
|
7378
|
+
if (!convertedTool?.execute) {
|
|
7379
|
+
console.error("[EXULU] Tool not found in converted tools array.", tools);
|
|
7380
|
+
throw new Error("Tool not found in converted tools array.");
|
|
7381
|
+
}
|
|
7382
|
+
const iterator = await convertedTool.execute(inputs, {
|
|
7383
|
+
toolCallId: tool2.id + "_" + (0, import_node_crypto4.randomUUID)(),
|
|
7384
|
+
messages: []
|
|
7385
|
+
});
|
|
7386
|
+
let result;
|
|
7387
|
+
for await (const value of iterator) {
|
|
7388
|
+
result = value;
|
|
7389
|
+
}
|
|
7390
|
+
console.log("[EXULU] MCP tool result", result);
|
|
7391
|
+
return {
|
|
7392
|
+
content: [{ type: "text", text: JSON.stringify(result) }],
|
|
7393
|
+
structuredContent: result
|
|
7394
|
+
};
|
|
7221
7395
|
});
|
|
7396
|
+
server.tools[tool2.id] = tool2.name;
|
|
7222
7397
|
}
|
|
7223
|
-
|
|
7224
|
-
"getAgents",
|
|
7225
|
-
{
|
|
7226
|
-
title: "Get agents",
|
|
7227
|
-
description: "Retrieves a list of all available agents"
|
|
7228
|
-
},
|
|
7229
|
-
async () => ({
|
|
7230
|
-
content: agents ? agents.map((agent) => {
|
|
7231
|
-
return {
|
|
7232
|
-
type: "text",
|
|
7233
|
-
text: `${agent.name} - ${agent.description}`
|
|
7234
|
-
};
|
|
7235
|
-
}) : [{
|
|
7236
|
-
type: "text",
|
|
7237
|
-
text: "No agents found."
|
|
7238
|
-
}]
|
|
7239
|
-
})
|
|
7240
|
-
);
|
|
7241
|
-
this.server.registerResource(
|
|
7242
|
-
"greeting",
|
|
7243
|
-
new import_mcp.ResourceTemplate("greeting://{name}", { list: void 0 }),
|
|
7244
|
-
{
|
|
7245
|
-
title: "Greeting Resource",
|
|
7246
|
-
// Display name for UI
|
|
7247
|
-
description: "Dynamic greeting generator"
|
|
7248
|
-
},
|
|
7249
|
-
async (uri, { name }) => ({
|
|
7250
|
-
contents: [{
|
|
7251
|
-
uri: uri.href,
|
|
7252
|
-
text: `Hello, ${name}!`
|
|
7253
|
-
}]
|
|
7254
|
-
})
|
|
7255
|
-
);
|
|
7256
|
-
this.server.registerPrompt(
|
|
7257
|
-
"review-code",
|
|
7258
|
-
{
|
|
7259
|
-
title: "Code Review",
|
|
7260
|
-
description: "Review code for best practices and potential issues",
|
|
7261
|
-
argsSchema: { code: import_zod2.z.string() }
|
|
7262
|
-
},
|
|
7263
|
-
({ code }) => ({
|
|
7264
|
-
messages: [{
|
|
7265
|
-
role: "user",
|
|
7266
|
-
content: {
|
|
7267
|
-
type: "text",
|
|
7268
|
-
text: `Please review this code:
|
|
7269
|
-
|
|
7270
|
-
${code}`
|
|
7271
|
-
}
|
|
7272
|
-
}]
|
|
7273
|
-
})
|
|
7274
|
-
);
|
|
7398
|
+
return server.mcp;
|
|
7275
7399
|
};
|
|
7276
|
-
|
|
7277
|
-
if (!
|
|
7400
|
+
create = async ({ express: express3, allTools, allAgents, allContexts, config }) => {
|
|
7401
|
+
if (!express3) {
|
|
7278
7402
|
throw new Error("Express not initialized.");
|
|
7279
7403
|
}
|
|
7280
7404
|
if (!this.server) {
|
|
7281
7405
|
throw new Error("MCP server not initialized.");
|
|
7282
7406
|
}
|
|
7283
|
-
|
|
7284
|
-
|
|
7285
|
-
|
|
7407
|
+
express3.post("/mcp/:agent", async (req, res) => {
|
|
7408
|
+
console.log("[EXULU] MCP request received.", req.params.agent);
|
|
7409
|
+
if (!req.params.agent) {
|
|
7410
|
+
res.status(400).json({
|
|
7411
|
+
error: "Bad Request: No agent ID provided"
|
|
7412
|
+
});
|
|
7413
|
+
return;
|
|
7414
|
+
}
|
|
7415
|
+
const agentInstance = await loadAgent(req.params.agent);
|
|
7416
|
+
if (!agentInstance) {
|
|
7417
|
+
console.error("[EXULU] Agent not found.", req.params.agent);
|
|
7418
|
+
res.status(404).json({
|
|
7419
|
+
error: "Agent not found"
|
|
7420
|
+
});
|
|
7421
|
+
return;
|
|
7422
|
+
}
|
|
7423
|
+
const authenticationResult = await requestValidators.authenticate(req);
|
|
7424
|
+
if (!authenticationResult.user?.id) {
|
|
7425
|
+
res.status(authenticationResult.code || 500).json({ detail: `${authenticationResult.message}` });
|
|
7426
|
+
return;
|
|
7427
|
+
}
|
|
7428
|
+
const user = authenticationResult.user;
|
|
7429
|
+
const hasAccessToAgent = await checkRecordAccess(agentInstance, "read", user);
|
|
7430
|
+
if (!hasAccessToAgent) {
|
|
7431
|
+
res.status(401).json({
|
|
7432
|
+
message: "You don't have access to this agent."
|
|
7433
|
+
});
|
|
7434
|
+
return;
|
|
7435
|
+
}
|
|
7436
|
+
const server = await this.configure({
|
|
7437
|
+
agentInstance,
|
|
7438
|
+
user,
|
|
7439
|
+
allTools,
|
|
7440
|
+
allAgents,
|
|
7441
|
+
allContexts,
|
|
7442
|
+
config
|
|
7443
|
+
});
|
|
7444
|
+
if (!server) {
|
|
7445
|
+
throw new Error("MCP server for agent " + req.params.agent + " not initialized.");
|
|
7286
7446
|
}
|
|
7287
7447
|
const sessionId = req.headers[SESSION_ID_HEADER];
|
|
7288
7448
|
let transport;
|
|
7289
7449
|
if (sessionId && this.transports[sessionId]) {
|
|
7290
7450
|
transport = this.transports[sessionId];
|
|
7291
7451
|
} else if (!sessionId && (0, import_types.isInitializeRequest)(req.body)) {
|
|
7292
|
-
transport = new
|
|
7452
|
+
transport = new import_streamableHttp.StreamableHTTPServerTransport({
|
|
7293
7453
|
sessionIdGenerator: () => (0, import_node_crypto4.randomUUID)(),
|
|
7294
7454
|
onsessioninitialized: (sessionId2) => {
|
|
7295
7455
|
this.transports[sessionId2] = transport;
|
|
@@ -7304,13 +7464,14 @@ ${code}`
|
|
|
7304
7464
|
delete this.transports[transport.sessionId];
|
|
7305
7465
|
}
|
|
7306
7466
|
};
|
|
7307
|
-
await
|
|
7467
|
+
await server.connect(transport);
|
|
7308
7468
|
} else {
|
|
7309
7469
|
res.status(400).json({
|
|
7310
7470
|
error: "Bad Request: No valid session ID provided"
|
|
7311
7471
|
});
|
|
7312
7472
|
return;
|
|
7313
7473
|
}
|
|
7474
|
+
req.headers["exulu-agent-id"] = req.params.agent;
|
|
7314
7475
|
await transport.handleRequest(req, res, req.body);
|
|
7315
7476
|
});
|
|
7316
7477
|
const handleSessionRequest = async (req, res) => {
|
|
@@ -7323,9 +7484,10 @@ ${code}`
|
|
|
7323
7484
|
const transport = this.transports[sessionId];
|
|
7324
7485
|
await transport.handleRequest(req, res);
|
|
7325
7486
|
};
|
|
7326
|
-
|
|
7327
|
-
|
|
7328
|
-
|
|
7487
|
+
express3.get("/mcp/:agent", handleSessionRequest);
|
|
7488
|
+
express3.delete("/mcp/:agent", handleSessionRequest);
|
|
7489
|
+
console.log("[EXULU] MCP server created.");
|
|
7490
|
+
return express3;
|
|
7329
7491
|
};
|
|
7330
7492
|
};
|
|
7331
7493
|
|
|
@@ -8760,13 +8922,11 @@ var ExuluApp = class {
|
|
|
8760
8922
|
const mcp = new ExuluMCP();
|
|
8761
8923
|
await mcp.create({
|
|
8762
8924
|
express: app,
|
|
8763
|
-
|
|
8764
|
-
|
|
8765
|
-
|
|
8766
|
-
|
|
8767
|
-
tracer
|
|
8925
|
+
allTools: this._tools,
|
|
8926
|
+
allAgents: this._agents,
|
|
8927
|
+
allContexts: Object.values(this._contexts ?? {}),
|
|
8928
|
+
config: this._config
|
|
8768
8929
|
});
|
|
8769
|
-
await mcp.connect();
|
|
8770
8930
|
}
|
|
8771
8931
|
return app;
|
|
8772
8932
|
}
|
|
@@ -10203,7 +10363,7 @@ var create = ({
|
|
|
10203
10363
|
};
|
|
10204
10364
|
|
|
10205
10365
|
// src/index.ts
|
|
10206
|
-
var
|
|
10366
|
+
var import_crypto_js6 = __toESM(require("crypto-js"), 1);
|
|
10207
10367
|
var ExuluJobs = {
|
|
10208
10368
|
redis: redisClient,
|
|
10209
10369
|
jobs: {
|
|
@@ -10240,8 +10400,8 @@ var ExuluVariables = {
|
|
|
10240
10400
|
throw new Error(`Variable ${name} not found.`);
|
|
10241
10401
|
}
|
|
10242
10402
|
if (variable.encrypted) {
|
|
10243
|
-
const bytes =
|
|
10244
|
-
variable.value = bytes.toString(
|
|
10403
|
+
const bytes = import_crypto_js6.default.AES.decrypt(variable.value, process.env.NEXTAUTH_SECRET);
|
|
10404
|
+
variable.value = bytes.toString(import_crypto_js6.default.enc.Utf8);
|
|
10245
10405
|
}
|
|
10246
10406
|
return variable.value;
|
|
10247
10407
|
}
|
package/dist/index.js
CHANGED
|
@@ -8,7 +8,8 @@ import { createClient } from "redis";
|
|
|
8
8
|
var redisServer = {
|
|
9
9
|
host: `${process.env.REDIS_HOST}`,
|
|
10
10
|
port: process.env.REDIS_PORT,
|
|
11
|
-
password: process.env.REDIS_PASSWORD || void 0
|
|
11
|
+
password: process.env.REDIS_PASSWORD || void 0,
|
|
12
|
+
username: process.env.REDIS_USER || void 0
|
|
12
13
|
};
|
|
13
14
|
|
|
14
15
|
// src/redis/client.ts
|
|
@@ -19,7 +20,12 @@ async function redisClient() {
|
|
|
19
20
|
}
|
|
20
21
|
if (!client["exulu"]) {
|
|
21
22
|
try {
|
|
22
|
-
|
|
23
|
+
let url = "";
|
|
24
|
+
if (redisServer.username) {
|
|
25
|
+
url = `redis://${redisServer.username}:${redisServer.password}@${redisServer.host}:${redisServer.port}`;
|
|
26
|
+
} else {
|
|
27
|
+
url = `redis://${redisServer.host}:${redisServer.port}`;
|
|
28
|
+
}
|
|
23
29
|
client["exulu"] = createClient({
|
|
24
30
|
url
|
|
25
31
|
});
|
|
@@ -385,8 +391,6 @@ var sanitizeName = (name) => {
|
|
|
385
391
|
|
|
386
392
|
// src/registry/classes.ts
|
|
387
393
|
import CryptoJS2 from "crypto-js";
|
|
388
|
-
import "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
389
|
-
import "ai/mcp-stdio";
|
|
390
394
|
|
|
391
395
|
// src/registry/utils/graphql.ts
|
|
392
396
|
import { makeExecutableSchema } from "@graphql-tools/schema";
|
|
@@ -1089,6 +1093,10 @@ var statisticsSchema = {
|
|
|
1089
1093
|
{
|
|
1090
1094
|
name: "role",
|
|
1091
1095
|
type: "uuid"
|
|
1096
|
+
},
|
|
1097
|
+
{
|
|
1098
|
+
name: "project",
|
|
1099
|
+
type: "uuid"
|
|
1092
1100
|
}
|
|
1093
1101
|
]
|
|
1094
1102
|
};
|
|
@@ -3988,7 +3996,6 @@ type StatisticsResult {
|
|
|
3988
3996
|
}
|
|
3989
3997
|
`;
|
|
3990
3998
|
const fullSDL = typeDefs + mutationDefs + modelDefs + genericTypes;
|
|
3991
|
-
console.log("[EXULU] Full SDL:", fullSDL);
|
|
3992
3999
|
const schema = makeExecutableSchema({
|
|
3993
4000
|
typeDefs: fullSDL,
|
|
3994
4001
|
resolvers
|
|
@@ -4600,7 +4607,6 @@ var createUppyRoutes = async (app, config) => {
|
|
|
4600
4607
|
};
|
|
4601
4608
|
|
|
4602
4609
|
// src/registry/classes.ts
|
|
4603
|
-
import "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
4604
4610
|
var s3Client2;
|
|
4605
4611
|
function sanitizeToolName(name) {
|
|
4606
4612
|
if (typeof name !== "string") return "";
|
|
@@ -4842,7 +4848,6 @@ var ExuluAgent2 = class {
|
|
|
4842
4848
|
return typeof model === "string" ? model : model?.modelId || "";
|
|
4843
4849
|
}
|
|
4844
4850
|
// Exports the agent as a tool that can be used by another agent
|
|
4845
|
-
// todo test this
|
|
4846
4851
|
tool = async (instance, agents) => {
|
|
4847
4852
|
const agentInstance = await loadAgent(instance);
|
|
4848
4853
|
if (!agentInstance) {
|
|
@@ -5761,6 +5766,7 @@ var updateStatistic = async (statistic) => {
|
|
|
5761
5766
|
const existing = await db3.from("tracking").where({
|
|
5762
5767
|
...statistic.user ? { user: statistic.user } : {},
|
|
5763
5768
|
...statistic.role ? { role: statistic.role } : {},
|
|
5769
|
+
...statistic.project ? { project: statistic.project } : {},
|
|
5764
5770
|
name: statistic.name,
|
|
5765
5771
|
label: statistic.label,
|
|
5766
5772
|
type: statistic.type,
|
|
@@ -5774,16 +5780,14 @@ var updateStatistic = async (statistic) => {
|
|
|
5774
5780
|
total: statistic.count ?? 1,
|
|
5775
5781
|
createdAt: currentDate,
|
|
5776
5782
|
...statistic.user ? { user: statistic.user } : {},
|
|
5777
|
-
...statistic.role ? { role: statistic.role } : {}
|
|
5783
|
+
...statistic.role ? { role: statistic.role } : {},
|
|
5784
|
+
...statistic.project ? { project: statistic.project } : {}
|
|
5778
5785
|
});
|
|
5779
5786
|
} else {
|
|
5780
5787
|
await db3.from("tracking").update({
|
|
5781
5788
|
total: db3.raw("total + ?", [statistic.count ?? 1])
|
|
5782
5789
|
}).where({
|
|
5783
|
-
|
|
5784
|
-
label: statistic.label,
|
|
5785
|
-
type: statistic.type,
|
|
5786
|
-
createdAt: currentDate
|
|
5790
|
+
id: existing.id
|
|
5787
5791
|
});
|
|
5788
5792
|
}
|
|
5789
5793
|
};
|
|
@@ -5873,6 +5877,9 @@ var CLAUDE_MESSAGES = {
|
|
|
5873
5877
|
\x1B[0m`,
|
|
5874
5878
|
not_enabled: `
|
|
5875
5879
|
\x1B[41m -- The agent you selected does not have a valid API key set for it. --
|
|
5880
|
+
\x1B[0m`,
|
|
5881
|
+
missing_project: `
|
|
5882
|
+
\x1B[41m -- Project not found or you do not have access to it. --
|
|
5876
5883
|
\x1B[0m`
|
|
5877
5884
|
};
|
|
5878
5885
|
|
|
@@ -6175,7 +6182,6 @@ Mood: friendly and intelligent.
|
|
|
6175
6182
|
console.log("[EXULU] agent tools", agentInstance.tools?.map((x) => x.name + " (" + x.id + ")"));
|
|
6176
6183
|
const disabledTools = req.body.disabledTools ? req.body.disabledTools : [];
|
|
6177
6184
|
let enabledTools = await getEnabledTools(agentInstance, tools, disabledTools, agents, user);
|
|
6178
|
-
console.log("[EXULU] enabled tools", enabledTools?.map((x) => x.name + " (" + x.id + ")"));
|
|
6179
6185
|
const variableName = agentInstance.providerapikey;
|
|
6180
6186
|
const variable = await db3.from("variables").where({ name: variableName }).first();
|
|
6181
6187
|
if (!variable) {
|
|
@@ -6323,11 +6329,12 @@ Mood: friendly and intelligent.
|
|
|
6323
6329
|
}
|
|
6324
6330
|
});
|
|
6325
6331
|
});
|
|
6326
|
-
app.use("/gateway/anthropic/:
|
|
6332
|
+
app.use("/gateway/anthropic/:agent/:project", express.raw({ type: "*/*", limit: REQUEST_SIZE_LIMIT }), async (req, res) => {
|
|
6327
6333
|
try {
|
|
6328
6334
|
if (!req.body.tools) {
|
|
6329
6335
|
req.body.tools = [];
|
|
6330
6336
|
}
|
|
6337
|
+
const { db: db3 } = await postgresClient();
|
|
6331
6338
|
const authenticationResult = await requestValidators.authenticate(req);
|
|
6332
6339
|
if (!authenticationResult.user?.id) {
|
|
6333
6340
|
console.log("[EXULU] failed authentication result", authenticationResult);
|
|
@@ -6335,20 +6342,35 @@ Mood: friendly and intelligent.
|
|
|
6335
6342
|
return;
|
|
6336
6343
|
}
|
|
6337
6344
|
const user = authenticationResult.user;
|
|
6338
|
-
|
|
6339
|
-
|
|
6340
|
-
|
|
6341
|
-
|
|
6342
|
-
|
|
6343
|
-
const agent = await query.first();
|
|
6345
|
+
let agentQuery = db3("agents");
|
|
6346
|
+
agentQuery.select("*");
|
|
6347
|
+
agentQuery = applyAccessControl(agentsSchema2(), authenticationResult.user, agentQuery);
|
|
6348
|
+
agentQuery.where({ id: req.params.agent });
|
|
6349
|
+
const agent = await agentQuery.first();
|
|
6344
6350
|
if (!agent) {
|
|
6345
6351
|
const arrayBuffer = createCustomAnthropicStreamingMessage(`
|
|
6346
|
-
\x1B[41m -- Agent ${req.params.
|
|
6352
|
+
\x1B[41m -- Agent ${req.params.agent} not found or you do not have access to it. --
|
|
6347
6353
|
\x1B[0m`);
|
|
6348
6354
|
res.setHeader("Content-Type", "application/json");
|
|
6349
6355
|
res.end(Buffer.from(arrayBuffer));
|
|
6350
6356
|
return;
|
|
6351
6357
|
}
|
|
6358
|
+
let project = null;
|
|
6359
|
+
if (!req.body.project || req.body.project === "DEFAULT") {
|
|
6360
|
+
project = null;
|
|
6361
|
+
} else {
|
|
6362
|
+
let projectQuery = db3("projects");
|
|
6363
|
+
projectQuery.select("*");
|
|
6364
|
+
projectQuery = applyAccessControl(projectsSchema2(), authenticationResult.user, projectQuery);
|
|
6365
|
+
projectQuery.where({ id: req.params.project });
|
|
6366
|
+
project = await projectQuery.first();
|
|
6367
|
+
if (!project) {
|
|
6368
|
+
const arrayBuffer = createCustomAnthropicStreamingMessage(CLAUDE_MESSAGES.missing_project);
|
|
6369
|
+
res.setHeader("Content-Type", "application/json");
|
|
6370
|
+
res.end(Buffer.from(arrayBuffer));
|
|
6371
|
+
return;
|
|
6372
|
+
}
|
|
6373
|
+
}
|
|
6352
6374
|
console.log("[EXULU] anthropic proxy called for agent:", agent?.name);
|
|
6353
6375
|
if (!process.env.NEXTAUTH_SECRET) {
|
|
6354
6376
|
const arrayBuffer = createCustomAnthropicStreamingMessage(CLAUDE_MESSAGES.missing_nextauth_secret);
|
|
@@ -6392,10 +6414,47 @@ Mood: friendly and intelligent.
|
|
|
6392
6414
|
apiKey: anthropicApiKey
|
|
6393
6415
|
});
|
|
6394
6416
|
const tokens = {};
|
|
6395
|
-
|
|
6396
|
-
|
|
6397
|
-
|
|
6398
|
-
|
|
6417
|
+
const disabledTools = req.body.disabledTools ? req.body.disabledTools : [];
|
|
6418
|
+
let enabledTools = await getEnabledTools(agent, tools, disabledTools, agents, user);
|
|
6419
|
+
let system = req.body.system;
|
|
6420
|
+
if (Array.isArray(req.body.system)) {
|
|
6421
|
+
system = [
|
|
6422
|
+
...req.body.system,
|
|
6423
|
+
...agent ? [
|
|
6424
|
+
{
|
|
6425
|
+
type: "text",
|
|
6426
|
+
text: `
|
|
6427
|
+
You are an agent named: ${agent?.name}
|
|
6428
|
+
Here are some additional instructions for you: ${agent?.instructions}`
|
|
6429
|
+
}
|
|
6430
|
+
] : [],
|
|
6431
|
+
...project ? [
|
|
6432
|
+
{
|
|
6433
|
+
type: "text",
|
|
6434
|
+
text: `Additional information:
|
|
6435
|
+
|
|
6436
|
+
The project you are working on is: ${project?.name}
|
|
6437
|
+
Here is some additional information about the project: ${project?.description}`
|
|
6438
|
+
}
|
|
6439
|
+
] : []
|
|
6440
|
+
];
|
|
6441
|
+
} else {
|
|
6442
|
+
system = `${req.body.system}
|
|
6443
|
+
|
|
6444
|
+
|
|
6445
|
+
${agent ? `You are an agent named: ${agent?.name}
|
|
6446
|
+
Here are some additional instructions for you: ${agent?.instructions}` : ""}
|
|
6447
|
+
|
|
6448
|
+
${project?.id ? `Additional information:
|
|
6449
|
+
|
|
6450
|
+
The project you are working on is: ${project?.name}
|
|
6451
|
+
The project description is: ${project?.description}` : ""}
|
|
6452
|
+
`;
|
|
6453
|
+
}
|
|
6454
|
+
for await (const event of client2.messages.stream({
|
|
6455
|
+
...req.body,
|
|
6456
|
+
system
|
|
6457
|
+
})) {
|
|
6399
6458
|
if (event.message?.id) {
|
|
6400
6459
|
tokens[event.message.id] = {
|
|
6401
6460
|
input_tokens: event.message.usage.input_tokens,
|
|
@@ -6404,11 +6463,45 @@ Mood: friendly and intelligent.
|
|
|
6404
6463
|
output_tokens: event.message.usage.output_tokens
|
|
6405
6464
|
};
|
|
6406
6465
|
}
|
|
6407
|
-
|
|
6466
|
+
if (event.message?.type === "tool_use" && event.message?.name?.includes("exulu_")) {
|
|
6467
|
+
const toolName = event.message?.name;
|
|
6468
|
+
console.log("[EXULU] Using tool", toolName);
|
|
6469
|
+
const inputs = event.message?.input;
|
|
6470
|
+
const id = event.message?.id;
|
|
6471
|
+
const tool2 = enabledTools.find((tool3) => tool3.id === toolName.replace("exulu_", ""));
|
|
6472
|
+
if (!tool2 || !tool2.tool.execute) {
|
|
6473
|
+
console.error("[EXULU] Tool not found or not enabled.", toolName);
|
|
6474
|
+
continue;
|
|
6475
|
+
}
|
|
6476
|
+
const toolResult = await tool2.tool.execute(inputs, {
|
|
6477
|
+
toolCallId: id,
|
|
6478
|
+
messages: [{
|
|
6479
|
+
...event.message,
|
|
6480
|
+
role: "tool"
|
|
6481
|
+
}]
|
|
6482
|
+
});
|
|
6483
|
+
console.log("[EXULU] Tool result", toolResult);
|
|
6484
|
+
const toolResultMessage = {
|
|
6485
|
+
role: "user",
|
|
6486
|
+
content: [
|
|
6487
|
+
{
|
|
6488
|
+
type: "tool_result",
|
|
6489
|
+
tool_use_id: id,
|
|
6490
|
+
content: toolResult
|
|
6491
|
+
}
|
|
6492
|
+
]
|
|
6493
|
+
};
|
|
6494
|
+
res.write(`event: tool_result
|
|
6495
|
+
data: ${JSON.stringify(toolResultMessage)}
|
|
6496
|
+
|
|
6497
|
+
`);
|
|
6498
|
+
} else {
|
|
6499
|
+
const msg = `event: ${event.type}
|
|
6408
6500
|
data: ${JSON.stringify(event)}
|
|
6409
6501
|
|
|
6410
6502
|
`;
|
|
6411
|
-
|
|
6503
|
+
res.write(msg);
|
|
6504
|
+
}
|
|
6412
6505
|
}
|
|
6413
6506
|
let totalInputTokens = 0;
|
|
6414
6507
|
let totalOutputTokens = 0;
|
|
@@ -6428,7 +6521,8 @@ data: ${JSON.stringify(event)}
|
|
|
6428
6521
|
trigger: statistics.trigger,
|
|
6429
6522
|
count: 1,
|
|
6430
6523
|
user: user.id,
|
|
6431
|
-
role: user?.role?.id
|
|
6524
|
+
role: user?.role?.id,
|
|
6525
|
+
...project ? { project: project.id } : {}
|
|
6432
6526
|
}),
|
|
6433
6527
|
...totalInputTokens ? [
|
|
6434
6528
|
updateStatistic({
|
|
@@ -6438,7 +6532,8 @@ data: ${JSON.stringify(event)}
|
|
|
6438
6532
|
trigger: statistics.trigger,
|
|
6439
6533
|
count: totalInputTokens,
|
|
6440
6534
|
user: user.id,
|
|
6441
|
-
role: user?.role?.id
|
|
6535
|
+
role: user?.role?.id,
|
|
6536
|
+
...project ? { project: project.id } : {}
|
|
6442
6537
|
})
|
|
6443
6538
|
] : [],
|
|
6444
6539
|
...totalOutputTokens ? [
|
|
@@ -6447,7 +6542,10 @@ data: ${JSON.stringify(event)}
|
|
|
6447
6542
|
label: statistics.label,
|
|
6448
6543
|
type: STATISTICS_TYPE_ENUM.AGENT_RUN,
|
|
6449
6544
|
trigger: statistics.trigger,
|
|
6450
|
-
count:
|
|
6545
|
+
count: totalInputTokens,
|
|
6546
|
+
user: user.id,
|
|
6547
|
+
role: user?.role?.id,
|
|
6548
|
+
...project ? { project: project.id } : {}
|
|
6451
6549
|
})
|
|
6452
6550
|
] : []
|
|
6453
6551
|
]);
|
|
@@ -7164,99 +7262,161 @@ function getAverage(arr) {
|
|
|
7164
7262
|
}
|
|
7165
7263
|
|
|
7166
7264
|
// src/mcp/index.ts
|
|
7167
|
-
import { McpServer
|
|
7265
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
7168
7266
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
7169
|
-
import { StreamableHTTPServerTransport
|
|
7267
|
+
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
7170
7268
|
import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
|
|
7171
|
-
import { z as z2 } from "zod";
|
|
7172
7269
|
import "express";
|
|
7173
7270
|
import "@opentelemetry/api";
|
|
7271
|
+
import CryptoJS5 from "crypto-js";
|
|
7272
|
+
import { z as z2 } from "zod";
|
|
7174
7273
|
var SESSION_ID_HEADER = "mcp-session-id";
|
|
7175
7274
|
var ExuluMCP = class {
|
|
7176
|
-
server;
|
|
7275
|
+
server = {};
|
|
7177
7276
|
transports = {};
|
|
7178
|
-
express;
|
|
7179
7277
|
constructor() {
|
|
7180
7278
|
}
|
|
7181
|
-
|
|
7182
|
-
|
|
7183
|
-
if (!
|
|
7279
|
+
configure = async ({ user, agentInstance, allTools, allAgents, allContexts, config, tracer }) => {
|
|
7280
|
+
let server = this.server[agentInstance.id];
|
|
7281
|
+
if (!server) {
|
|
7184
7282
|
console.log("[EXULU] Creating MCP server.");
|
|
7185
|
-
|
|
7186
|
-
|
|
7187
|
-
|
|
7283
|
+
server = {
|
|
7284
|
+
mcp: new McpServer({
|
|
7285
|
+
name: "exulu-mcp-server-" + agentInstance.name + "(" + agentInstance.id + ")",
|
|
7286
|
+
version: "1.0.0"
|
|
7287
|
+
}),
|
|
7288
|
+
tools: {}
|
|
7289
|
+
};
|
|
7290
|
+
this.server[agentInstance.id] = server;
|
|
7291
|
+
}
|
|
7292
|
+
const disabledTools = [];
|
|
7293
|
+
let enabledTools = await getEnabledTools(agentInstance, allTools, disabledTools, allAgents, user);
|
|
7294
|
+
const backend = allAgents.find((a) => a.id === agentInstance.backend);
|
|
7295
|
+
if (!backend) {
|
|
7296
|
+
throw new Error("Agent backend not found for agent " + agentInstance.name + " (" + agentInstance.id + ").");
|
|
7297
|
+
}
|
|
7298
|
+
const agentTool = await backend.tool(agentInstance.id, allAgents);
|
|
7299
|
+
if (agentTool) {
|
|
7300
|
+
enabledTools = [
|
|
7301
|
+
...enabledTools,
|
|
7302
|
+
agentTool
|
|
7303
|
+
];
|
|
7304
|
+
}
|
|
7305
|
+
const variableName = agentInstance.providerapikey;
|
|
7306
|
+
const { db: db3 } = await postgresClient();
|
|
7307
|
+
const variable = await db3.from("variables").where({ name: variableName }).first();
|
|
7308
|
+
if (!variable) {
|
|
7309
|
+
throw new Error("Provider API key variable not found.");
|
|
7310
|
+
}
|
|
7311
|
+
let providerapikey = variable.value;
|
|
7312
|
+
if (!variable.encrypted) {
|
|
7313
|
+
throw new Error("Provider API key variable not encrypted, for security reasons you are only allowed to use encrypted variables for provider API keys.");
|
|
7314
|
+
}
|
|
7315
|
+
if (variable.encrypted) {
|
|
7316
|
+
const bytes = CryptoJS5.AES.decrypt(variable.value, process.env.NEXTAUTH_SECRET);
|
|
7317
|
+
providerapikey = bytes.toString(CryptoJS5.enc.Utf8);
|
|
7318
|
+
}
|
|
7319
|
+
console.log("[EXULU] Enabled tools", enabledTools?.map((x) => x.name + " (" + x.id + ")"));
|
|
7320
|
+
for (const tool2 of enabledTools || []) {
|
|
7321
|
+
if (server.tools[tool2.id]) {
|
|
7322
|
+
continue;
|
|
7323
|
+
}
|
|
7324
|
+
server.mcp.registerTool(sanitizeToolName(tool2.name + "_agent_" + tool2.id), {
|
|
7325
|
+
title: tool2.name + " agent",
|
|
7326
|
+
description: tool2.description,
|
|
7327
|
+
inputSchema: {
|
|
7328
|
+
inputs: tool2.inputSchema || z2.object({})
|
|
7329
|
+
}
|
|
7330
|
+
}, async ({ inputs }, args) => {
|
|
7331
|
+
console.log("[EXULU] MCP tool name", tool2.name);
|
|
7332
|
+
console.log("[EXULU] MCP tool inputs", inputs);
|
|
7333
|
+
console.log("[EXULU] MCP tool args", args);
|
|
7334
|
+
const configValues = agentInstance.tools;
|
|
7335
|
+
const tools = convertToolsArrayToObject(
|
|
7336
|
+
[tool2],
|
|
7337
|
+
allTools,
|
|
7338
|
+
configValues,
|
|
7339
|
+
providerapikey,
|
|
7340
|
+
allContexts,
|
|
7341
|
+
user,
|
|
7342
|
+
config
|
|
7343
|
+
);
|
|
7344
|
+
const convertedTool = tools[sanitizeToolName(tool2.name)];
|
|
7345
|
+
if (!convertedTool?.execute) {
|
|
7346
|
+
console.error("[EXULU] Tool not found in converted tools array.", tools);
|
|
7347
|
+
throw new Error("Tool not found in converted tools array.");
|
|
7348
|
+
}
|
|
7349
|
+
const iterator = await convertedTool.execute(inputs, {
|
|
7350
|
+
toolCallId: tool2.id + "_" + randomUUID4(),
|
|
7351
|
+
messages: []
|
|
7352
|
+
});
|
|
7353
|
+
let result;
|
|
7354
|
+
for await (const value of iterator) {
|
|
7355
|
+
result = value;
|
|
7356
|
+
}
|
|
7357
|
+
console.log("[EXULU] MCP tool result", result);
|
|
7358
|
+
return {
|
|
7359
|
+
content: [{ type: "text", text: JSON.stringify(result) }],
|
|
7360
|
+
structuredContent: result
|
|
7361
|
+
};
|
|
7188
7362
|
});
|
|
7363
|
+
server.tools[tool2.id] = tool2.name;
|
|
7189
7364
|
}
|
|
7190
|
-
|
|
7191
|
-
"getAgents",
|
|
7192
|
-
{
|
|
7193
|
-
title: "Get agents",
|
|
7194
|
-
description: "Retrieves a list of all available agents"
|
|
7195
|
-
},
|
|
7196
|
-
async () => ({
|
|
7197
|
-
content: agents ? agents.map((agent) => {
|
|
7198
|
-
return {
|
|
7199
|
-
type: "text",
|
|
7200
|
-
text: `${agent.name} - ${agent.description}`
|
|
7201
|
-
};
|
|
7202
|
-
}) : [{
|
|
7203
|
-
type: "text",
|
|
7204
|
-
text: "No agents found."
|
|
7205
|
-
}]
|
|
7206
|
-
})
|
|
7207
|
-
);
|
|
7208
|
-
this.server.registerResource(
|
|
7209
|
-
"greeting",
|
|
7210
|
-
new ResourceTemplate("greeting://{name}", { list: void 0 }),
|
|
7211
|
-
{
|
|
7212
|
-
title: "Greeting Resource",
|
|
7213
|
-
// Display name for UI
|
|
7214
|
-
description: "Dynamic greeting generator"
|
|
7215
|
-
},
|
|
7216
|
-
async (uri, { name }) => ({
|
|
7217
|
-
contents: [{
|
|
7218
|
-
uri: uri.href,
|
|
7219
|
-
text: `Hello, ${name}!`
|
|
7220
|
-
}]
|
|
7221
|
-
})
|
|
7222
|
-
);
|
|
7223
|
-
this.server.registerPrompt(
|
|
7224
|
-
"review-code",
|
|
7225
|
-
{
|
|
7226
|
-
title: "Code Review",
|
|
7227
|
-
description: "Review code for best practices and potential issues",
|
|
7228
|
-
argsSchema: { code: z2.string() }
|
|
7229
|
-
},
|
|
7230
|
-
({ code }) => ({
|
|
7231
|
-
messages: [{
|
|
7232
|
-
role: "user",
|
|
7233
|
-
content: {
|
|
7234
|
-
type: "text",
|
|
7235
|
-
text: `Please review this code:
|
|
7236
|
-
|
|
7237
|
-
${code}`
|
|
7238
|
-
}
|
|
7239
|
-
}]
|
|
7240
|
-
})
|
|
7241
|
-
);
|
|
7365
|
+
return server.mcp;
|
|
7242
7366
|
};
|
|
7243
|
-
|
|
7244
|
-
if (!
|
|
7367
|
+
create = async ({ express: express3, allTools, allAgents, allContexts, config }) => {
|
|
7368
|
+
if (!express3) {
|
|
7245
7369
|
throw new Error("Express not initialized.");
|
|
7246
7370
|
}
|
|
7247
7371
|
if (!this.server) {
|
|
7248
7372
|
throw new Error("MCP server not initialized.");
|
|
7249
7373
|
}
|
|
7250
|
-
|
|
7251
|
-
|
|
7252
|
-
|
|
7374
|
+
express3.post("/mcp/:agent", async (req, res) => {
|
|
7375
|
+
console.log("[EXULU] MCP request received.", req.params.agent);
|
|
7376
|
+
if (!req.params.agent) {
|
|
7377
|
+
res.status(400).json({
|
|
7378
|
+
error: "Bad Request: No agent ID provided"
|
|
7379
|
+
});
|
|
7380
|
+
return;
|
|
7381
|
+
}
|
|
7382
|
+
const agentInstance = await loadAgent(req.params.agent);
|
|
7383
|
+
if (!agentInstance) {
|
|
7384
|
+
console.error("[EXULU] Agent not found.", req.params.agent);
|
|
7385
|
+
res.status(404).json({
|
|
7386
|
+
error: "Agent not found"
|
|
7387
|
+
});
|
|
7388
|
+
return;
|
|
7389
|
+
}
|
|
7390
|
+
const authenticationResult = await requestValidators.authenticate(req);
|
|
7391
|
+
if (!authenticationResult.user?.id) {
|
|
7392
|
+
res.status(authenticationResult.code || 500).json({ detail: `${authenticationResult.message}` });
|
|
7393
|
+
return;
|
|
7394
|
+
}
|
|
7395
|
+
const user = authenticationResult.user;
|
|
7396
|
+
const hasAccessToAgent = await checkRecordAccess(agentInstance, "read", user);
|
|
7397
|
+
if (!hasAccessToAgent) {
|
|
7398
|
+
res.status(401).json({
|
|
7399
|
+
message: "You don't have access to this agent."
|
|
7400
|
+
});
|
|
7401
|
+
return;
|
|
7402
|
+
}
|
|
7403
|
+
const server = await this.configure({
|
|
7404
|
+
agentInstance,
|
|
7405
|
+
user,
|
|
7406
|
+
allTools,
|
|
7407
|
+
allAgents,
|
|
7408
|
+
allContexts,
|
|
7409
|
+
config
|
|
7410
|
+
});
|
|
7411
|
+
if (!server) {
|
|
7412
|
+
throw new Error("MCP server for agent " + req.params.agent + " not initialized.");
|
|
7253
7413
|
}
|
|
7254
7414
|
const sessionId = req.headers[SESSION_ID_HEADER];
|
|
7255
7415
|
let transport;
|
|
7256
7416
|
if (sessionId && this.transports[sessionId]) {
|
|
7257
7417
|
transport = this.transports[sessionId];
|
|
7258
7418
|
} else if (!sessionId && isInitializeRequest(req.body)) {
|
|
7259
|
-
transport = new
|
|
7419
|
+
transport = new StreamableHTTPServerTransport({
|
|
7260
7420
|
sessionIdGenerator: () => randomUUID4(),
|
|
7261
7421
|
onsessioninitialized: (sessionId2) => {
|
|
7262
7422
|
this.transports[sessionId2] = transport;
|
|
@@ -7271,13 +7431,14 @@ ${code}`
|
|
|
7271
7431
|
delete this.transports[transport.sessionId];
|
|
7272
7432
|
}
|
|
7273
7433
|
};
|
|
7274
|
-
await
|
|
7434
|
+
await server.connect(transport);
|
|
7275
7435
|
} else {
|
|
7276
7436
|
res.status(400).json({
|
|
7277
7437
|
error: "Bad Request: No valid session ID provided"
|
|
7278
7438
|
});
|
|
7279
7439
|
return;
|
|
7280
7440
|
}
|
|
7441
|
+
req.headers["exulu-agent-id"] = req.params.agent;
|
|
7281
7442
|
await transport.handleRequest(req, res, req.body);
|
|
7282
7443
|
});
|
|
7283
7444
|
const handleSessionRequest = async (req, res) => {
|
|
@@ -7290,9 +7451,10 @@ ${code}`
|
|
|
7290
7451
|
const transport = this.transports[sessionId];
|
|
7291
7452
|
await transport.handleRequest(req, res);
|
|
7292
7453
|
};
|
|
7293
|
-
|
|
7294
|
-
|
|
7295
|
-
|
|
7454
|
+
express3.get("/mcp/:agent", handleSessionRequest);
|
|
7455
|
+
express3.delete("/mcp/:agent", handleSessionRequest);
|
|
7456
|
+
console.log("[EXULU] MCP server created.");
|
|
7457
|
+
return express3;
|
|
7296
7458
|
};
|
|
7297
7459
|
};
|
|
7298
7460
|
|
|
@@ -8727,13 +8889,11 @@ var ExuluApp = class {
|
|
|
8727
8889
|
const mcp = new ExuluMCP();
|
|
8728
8890
|
await mcp.create({
|
|
8729
8891
|
express: app,
|
|
8730
|
-
|
|
8731
|
-
|
|
8732
|
-
|
|
8733
|
-
|
|
8734
|
-
tracer
|
|
8892
|
+
allTools: this._tools,
|
|
8893
|
+
allAgents: this._agents,
|
|
8894
|
+
allContexts: Object.values(this._contexts ?? {}),
|
|
8895
|
+
config: this._config
|
|
8735
8896
|
});
|
|
8736
|
-
await mcp.connect();
|
|
8737
8897
|
}
|
|
8738
8898
|
return app;
|
|
8739
8899
|
}
|
|
@@ -10170,7 +10330,7 @@ var create = ({
|
|
|
10170
10330
|
};
|
|
10171
10331
|
|
|
10172
10332
|
// src/index.ts
|
|
10173
|
-
import
|
|
10333
|
+
import CryptoJS6 from "crypto-js";
|
|
10174
10334
|
var ExuluJobs = {
|
|
10175
10335
|
redis: redisClient,
|
|
10176
10336
|
jobs: {
|
|
@@ -10207,8 +10367,8 @@ var ExuluVariables = {
|
|
|
10207
10367
|
throw new Error(`Variable ${name} not found.`);
|
|
10208
10368
|
}
|
|
10209
10369
|
if (variable.encrypted) {
|
|
10210
|
-
const bytes =
|
|
10211
|
-
variable.value = bytes.toString(
|
|
10370
|
+
const bytes = CryptoJS6.AES.decrypt(variable.value, process.env.NEXTAUTH_SECRET);
|
|
10371
|
+
variable.value = bytes.toString(CryptoJS6.enc.Utf8);
|
|
10212
10372
|
}
|
|
10213
10373
|
return variable.value;
|
|
10214
10374
|
}
|