@exulu/backend 1.30.5 → 1.31.0
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 +3 -3
- package/dist/index.cjs +263 -109
- package/dist/index.js +264 -110
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
# [1.31.0](https://github.com/Qventu/exulu-backend/compare/v1.30.5...v1.31.0) (2025-11-06)
|
|
2
2
|
|
|
3
3
|
|
|
4
|
-
###
|
|
4
|
+
### Features
|
|
5
5
|
|
|
6
|
-
*
|
|
6
|
+
* implement per-agent MCP servers with project-level tracking ([63c8887](https://github.com/Qventu/exulu-backend/commit/63c88870d344e430625530d6e8eb93004d9af898))
|
package/dist/index.cjs
CHANGED
|
@@ -437,8 +437,6 @@ var sanitizeName = (name) => {
|
|
|
437
437
|
|
|
438
438
|
// src/registry/classes.ts
|
|
439
439
|
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
440
|
|
|
443
441
|
// src/registry/utils/graphql.ts
|
|
444
442
|
var import_schema = require("@graphql-tools/schema");
|
|
@@ -1141,6 +1139,10 @@ var statisticsSchema = {
|
|
|
1141
1139
|
{
|
|
1142
1140
|
name: "role",
|
|
1143
1141
|
type: "uuid"
|
|
1142
|
+
},
|
|
1143
|
+
{
|
|
1144
|
+
name: "project",
|
|
1145
|
+
type: "uuid"
|
|
1144
1146
|
}
|
|
1145
1147
|
]
|
|
1146
1148
|
};
|
|
@@ -4040,7 +4042,6 @@ type StatisticsResult {
|
|
|
4040
4042
|
}
|
|
4041
4043
|
`;
|
|
4042
4044
|
const fullSDL = typeDefs + mutationDefs + modelDefs + genericTypes;
|
|
4043
|
-
console.log("[EXULU] Full SDL:", fullSDL);
|
|
4044
4045
|
const schema = (0, import_schema.makeExecutableSchema)({
|
|
4045
4046
|
typeDefs: fullSDL,
|
|
4046
4047
|
resolvers
|
|
@@ -4633,7 +4634,6 @@ var createUppyRoutes = async (app, config) => {
|
|
|
4633
4634
|
};
|
|
4634
4635
|
|
|
4635
4636
|
// src/registry/classes.ts
|
|
4636
|
-
var import_streamableHttp2 = require("@modelcontextprotocol/sdk/server/streamableHttp.js");
|
|
4637
4637
|
var s3Client2;
|
|
4638
4638
|
function sanitizeToolName(name) {
|
|
4639
4639
|
if (typeof name !== "string") return "";
|
|
@@ -4875,7 +4875,6 @@ var ExuluAgent2 = class {
|
|
|
4875
4875
|
return typeof model === "string" ? model : model?.modelId || "";
|
|
4876
4876
|
}
|
|
4877
4877
|
// Exports the agent as a tool that can be used by another agent
|
|
4878
|
-
// todo test this
|
|
4879
4878
|
tool = async (instance, agents) => {
|
|
4880
4879
|
const agentInstance = await loadAgent(instance);
|
|
4881
4880
|
if (!agentInstance) {
|
|
@@ -5794,6 +5793,7 @@ var updateStatistic = async (statistic) => {
|
|
|
5794
5793
|
const existing = await db3.from("tracking").where({
|
|
5795
5794
|
...statistic.user ? { user: statistic.user } : {},
|
|
5796
5795
|
...statistic.role ? { role: statistic.role } : {},
|
|
5796
|
+
...statistic.project ? { project: statistic.project } : {},
|
|
5797
5797
|
name: statistic.name,
|
|
5798
5798
|
label: statistic.label,
|
|
5799
5799
|
type: statistic.type,
|
|
@@ -5807,16 +5807,14 @@ var updateStatistic = async (statistic) => {
|
|
|
5807
5807
|
total: statistic.count ?? 1,
|
|
5808
5808
|
createdAt: currentDate,
|
|
5809
5809
|
...statistic.user ? { user: statistic.user } : {},
|
|
5810
|
-
...statistic.role ? { role: statistic.role } : {}
|
|
5810
|
+
...statistic.role ? { role: statistic.role } : {},
|
|
5811
|
+
...statistic.project ? { project: statistic.project } : {}
|
|
5811
5812
|
});
|
|
5812
5813
|
} else {
|
|
5813
5814
|
await db3.from("tracking").update({
|
|
5814
5815
|
total: db3.raw("total + ?", [statistic.count ?? 1])
|
|
5815
5816
|
}).where({
|
|
5816
|
-
|
|
5817
|
-
label: statistic.label,
|
|
5818
|
-
type: statistic.type,
|
|
5819
|
-
createdAt: currentDate
|
|
5817
|
+
id: existing.id
|
|
5820
5818
|
});
|
|
5821
5819
|
}
|
|
5822
5820
|
};
|
|
@@ -5906,6 +5904,9 @@ var CLAUDE_MESSAGES = {
|
|
|
5906
5904
|
\x1B[0m`,
|
|
5907
5905
|
not_enabled: `
|
|
5908
5906
|
\x1B[41m -- The agent you selected does not have a valid API key set for it. --
|
|
5907
|
+
\x1B[0m`,
|
|
5908
|
+
missing_project: `
|
|
5909
|
+
\x1B[41m -- Project not found or you do not have access to it. --
|
|
5909
5910
|
\x1B[0m`
|
|
5910
5911
|
};
|
|
5911
5912
|
|
|
@@ -6208,7 +6209,6 @@ Mood: friendly and intelligent.
|
|
|
6208
6209
|
console.log("[EXULU] agent tools", agentInstance.tools?.map((x) => x.name + " (" + x.id + ")"));
|
|
6209
6210
|
const disabledTools = req.body.disabledTools ? req.body.disabledTools : [];
|
|
6210
6211
|
let enabledTools = await getEnabledTools(agentInstance, tools, disabledTools, agents, user);
|
|
6211
|
-
console.log("[EXULU] enabled tools", enabledTools?.map((x) => x.name + " (" + x.id + ")"));
|
|
6212
6212
|
const variableName = agentInstance.providerapikey;
|
|
6213
6213
|
const variable = await db3.from("variables").where({ name: variableName }).first();
|
|
6214
6214
|
if (!variable) {
|
|
@@ -6356,11 +6356,12 @@ Mood: friendly and intelligent.
|
|
|
6356
6356
|
}
|
|
6357
6357
|
});
|
|
6358
6358
|
});
|
|
6359
|
-
app.use("/gateway/anthropic/:
|
|
6359
|
+
app.use("/gateway/anthropic/:agent/:project", import_express3.default.raw({ type: "*/*", limit: REQUEST_SIZE_LIMIT }), async (req, res) => {
|
|
6360
6360
|
try {
|
|
6361
6361
|
if (!req.body.tools) {
|
|
6362
6362
|
req.body.tools = [];
|
|
6363
6363
|
}
|
|
6364
|
+
const { db: db3 } = await postgresClient();
|
|
6364
6365
|
const authenticationResult = await requestValidators.authenticate(req);
|
|
6365
6366
|
if (!authenticationResult.user?.id) {
|
|
6366
6367
|
console.log("[EXULU] failed authentication result", authenticationResult);
|
|
@@ -6368,20 +6369,35 @@ Mood: friendly and intelligent.
|
|
|
6368
6369
|
return;
|
|
6369
6370
|
}
|
|
6370
6371
|
const user = authenticationResult.user;
|
|
6371
|
-
|
|
6372
|
-
|
|
6373
|
-
|
|
6374
|
-
|
|
6375
|
-
|
|
6376
|
-
const agent = await query.first();
|
|
6372
|
+
let agentQuery = db3("agents");
|
|
6373
|
+
agentQuery.select("*");
|
|
6374
|
+
agentQuery = applyAccessControl(agentsSchema2(), authenticationResult.user, agentQuery);
|
|
6375
|
+
agentQuery.where({ id: req.params.agent });
|
|
6376
|
+
const agent = await agentQuery.first();
|
|
6377
6377
|
if (!agent) {
|
|
6378
6378
|
const arrayBuffer = createCustomAnthropicStreamingMessage(`
|
|
6379
|
-
\x1B[41m -- Agent ${req.params.
|
|
6379
|
+
\x1B[41m -- Agent ${req.params.agent} not found or you do not have access to it. --
|
|
6380
6380
|
\x1B[0m`);
|
|
6381
6381
|
res.setHeader("Content-Type", "application/json");
|
|
6382
6382
|
res.end(Buffer.from(arrayBuffer));
|
|
6383
6383
|
return;
|
|
6384
6384
|
}
|
|
6385
|
+
let project = null;
|
|
6386
|
+
if (!req.body.project || req.body.project === "DEFAULT") {
|
|
6387
|
+
project = null;
|
|
6388
|
+
} else {
|
|
6389
|
+
let projectQuery = db3("projects");
|
|
6390
|
+
projectQuery.select("*");
|
|
6391
|
+
projectQuery = applyAccessControl(projectsSchema2(), authenticationResult.user, projectQuery);
|
|
6392
|
+
projectQuery.where({ id: req.params.project });
|
|
6393
|
+
project = await projectQuery.first();
|
|
6394
|
+
if (!project) {
|
|
6395
|
+
const arrayBuffer = createCustomAnthropicStreamingMessage(CLAUDE_MESSAGES.missing_project);
|
|
6396
|
+
res.setHeader("Content-Type", "application/json");
|
|
6397
|
+
res.end(Buffer.from(arrayBuffer));
|
|
6398
|
+
return;
|
|
6399
|
+
}
|
|
6400
|
+
}
|
|
6385
6401
|
console.log("[EXULU] anthropic proxy called for agent:", agent?.name);
|
|
6386
6402
|
if (!process.env.NEXTAUTH_SECRET) {
|
|
6387
6403
|
const arrayBuffer = createCustomAnthropicStreamingMessage(CLAUDE_MESSAGES.missing_nextauth_secret);
|
|
@@ -6425,10 +6441,47 @@ Mood: friendly and intelligent.
|
|
|
6425
6441
|
apiKey: anthropicApiKey
|
|
6426
6442
|
});
|
|
6427
6443
|
const tokens = {};
|
|
6428
|
-
|
|
6429
|
-
|
|
6430
|
-
|
|
6431
|
-
|
|
6444
|
+
const disabledTools = req.body.disabledTools ? req.body.disabledTools : [];
|
|
6445
|
+
let enabledTools = await getEnabledTools(agent, tools, disabledTools, agents, user);
|
|
6446
|
+
let system = req.body.system;
|
|
6447
|
+
if (Array.isArray(req.body.system)) {
|
|
6448
|
+
system = [
|
|
6449
|
+
...req.body.system,
|
|
6450
|
+
...agent ? [
|
|
6451
|
+
{
|
|
6452
|
+
type: "text",
|
|
6453
|
+
text: `
|
|
6454
|
+
You are an agent named: ${agent?.name}
|
|
6455
|
+
Here are some additional instructions for you: ${agent?.instructions}`
|
|
6456
|
+
}
|
|
6457
|
+
] : [],
|
|
6458
|
+
...project ? [
|
|
6459
|
+
{
|
|
6460
|
+
type: "text",
|
|
6461
|
+
text: `Additional information:
|
|
6462
|
+
|
|
6463
|
+
The project you are working on is: ${project?.name}
|
|
6464
|
+
Here is some additional information about the project: ${project?.description}`
|
|
6465
|
+
}
|
|
6466
|
+
] : []
|
|
6467
|
+
];
|
|
6468
|
+
} else {
|
|
6469
|
+
system = `${req.body.system}
|
|
6470
|
+
|
|
6471
|
+
|
|
6472
|
+
${agent ? `You are an agent named: ${agent?.name}
|
|
6473
|
+
Here are some additional instructions for you: ${agent?.instructions}` : ""}
|
|
6474
|
+
|
|
6475
|
+
${project?.id ? `Additional information:
|
|
6476
|
+
|
|
6477
|
+
The project you are working on is: ${project?.name}
|
|
6478
|
+
The project description is: ${project?.description}` : ""}
|
|
6479
|
+
`;
|
|
6480
|
+
}
|
|
6481
|
+
for await (const event of client2.messages.stream({
|
|
6482
|
+
...req.body,
|
|
6483
|
+
system
|
|
6484
|
+
})) {
|
|
6432
6485
|
if (event.message?.id) {
|
|
6433
6486
|
tokens[event.message.id] = {
|
|
6434
6487
|
input_tokens: event.message.usage.input_tokens,
|
|
@@ -6437,11 +6490,45 @@ Mood: friendly and intelligent.
|
|
|
6437
6490
|
output_tokens: event.message.usage.output_tokens
|
|
6438
6491
|
};
|
|
6439
6492
|
}
|
|
6440
|
-
|
|
6493
|
+
if (event.message?.type === "tool_use" && event.message?.name?.includes("exulu_")) {
|
|
6494
|
+
const toolName = event.message?.name;
|
|
6495
|
+
console.log("[EXULU] Using tool", toolName);
|
|
6496
|
+
const inputs = event.message?.input;
|
|
6497
|
+
const id = event.message?.id;
|
|
6498
|
+
const tool2 = enabledTools.find((tool3) => tool3.id === toolName.replace("exulu_", ""));
|
|
6499
|
+
if (!tool2 || !tool2.tool.execute) {
|
|
6500
|
+
console.error("[EXULU] Tool not found or not enabled.", toolName);
|
|
6501
|
+
continue;
|
|
6502
|
+
}
|
|
6503
|
+
const toolResult = await tool2.tool.execute(inputs, {
|
|
6504
|
+
toolCallId: id,
|
|
6505
|
+
messages: [{
|
|
6506
|
+
...event.message,
|
|
6507
|
+
role: "tool"
|
|
6508
|
+
}]
|
|
6509
|
+
});
|
|
6510
|
+
console.log("[EXULU] Tool result", toolResult);
|
|
6511
|
+
const toolResultMessage = {
|
|
6512
|
+
role: "user",
|
|
6513
|
+
content: [
|
|
6514
|
+
{
|
|
6515
|
+
type: "tool_result",
|
|
6516
|
+
tool_use_id: id,
|
|
6517
|
+
content: toolResult
|
|
6518
|
+
}
|
|
6519
|
+
]
|
|
6520
|
+
};
|
|
6521
|
+
res.write(`event: tool_result
|
|
6522
|
+
data: ${JSON.stringify(toolResultMessage)}
|
|
6523
|
+
|
|
6524
|
+
`);
|
|
6525
|
+
} else {
|
|
6526
|
+
const msg = `event: ${event.type}
|
|
6441
6527
|
data: ${JSON.stringify(event)}
|
|
6442
6528
|
|
|
6443
6529
|
`;
|
|
6444
|
-
|
|
6530
|
+
res.write(msg);
|
|
6531
|
+
}
|
|
6445
6532
|
}
|
|
6446
6533
|
let totalInputTokens = 0;
|
|
6447
6534
|
let totalOutputTokens = 0;
|
|
@@ -6461,7 +6548,8 @@ data: ${JSON.stringify(event)}
|
|
|
6461
6548
|
trigger: statistics.trigger,
|
|
6462
6549
|
count: 1,
|
|
6463
6550
|
user: user.id,
|
|
6464
|
-
role: user?.role?.id
|
|
6551
|
+
role: user?.role?.id,
|
|
6552
|
+
...project ? { project: project.id } : {}
|
|
6465
6553
|
}),
|
|
6466
6554
|
...totalInputTokens ? [
|
|
6467
6555
|
updateStatistic({
|
|
@@ -6471,7 +6559,8 @@ data: ${JSON.stringify(event)}
|
|
|
6471
6559
|
trigger: statistics.trigger,
|
|
6472
6560
|
count: totalInputTokens,
|
|
6473
6561
|
user: user.id,
|
|
6474
|
-
role: user?.role?.id
|
|
6562
|
+
role: user?.role?.id,
|
|
6563
|
+
...project ? { project: project.id } : {}
|
|
6475
6564
|
})
|
|
6476
6565
|
] : [],
|
|
6477
6566
|
...totalOutputTokens ? [
|
|
@@ -6480,7 +6569,10 @@ data: ${JSON.stringify(event)}
|
|
|
6480
6569
|
label: statistics.label,
|
|
6481
6570
|
type: STATISTICS_TYPE_ENUM.AGENT_RUN,
|
|
6482
6571
|
trigger: statistics.trigger,
|
|
6483
|
-
count:
|
|
6572
|
+
count: totalInputTokens,
|
|
6573
|
+
user: user.id,
|
|
6574
|
+
role: user?.role?.id,
|
|
6575
|
+
...project ? { project: project.id } : {}
|
|
6484
6576
|
})
|
|
6485
6577
|
] : []
|
|
6486
6578
|
]);
|
|
@@ -7199,97 +7291,159 @@ function getAverage(arr) {
|
|
|
7199
7291
|
// src/mcp/index.ts
|
|
7200
7292
|
var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
7201
7293
|
var import_node_crypto4 = require("crypto");
|
|
7202
|
-
var
|
|
7294
|
+
var import_streamableHttp = require("@modelcontextprotocol/sdk/server/streamableHttp.js");
|
|
7203
7295
|
var import_types = require("@modelcontextprotocol/sdk/types.js");
|
|
7204
|
-
var import_zod2 = require("zod");
|
|
7205
7296
|
var import_express4 = require("express");
|
|
7206
7297
|
var import_api3 = require("@opentelemetry/api");
|
|
7298
|
+
var import_crypto_js5 = __toESM(require("crypto-js"), 1);
|
|
7299
|
+
var import_zod2 = require("zod");
|
|
7207
7300
|
var SESSION_ID_HEADER = "mcp-session-id";
|
|
7208
7301
|
var ExuluMCP = class {
|
|
7209
|
-
server;
|
|
7302
|
+
server = {};
|
|
7210
7303
|
transports = {};
|
|
7211
|
-
express;
|
|
7212
7304
|
constructor() {
|
|
7213
7305
|
}
|
|
7214
|
-
|
|
7215
|
-
|
|
7216
|
-
if (!
|
|
7306
|
+
configure = async ({ user, agentInstance, allTools, allAgents, allContexts, config, tracer }) => {
|
|
7307
|
+
let server = this.server[agentInstance.id];
|
|
7308
|
+
if (!server) {
|
|
7217
7309
|
console.log("[EXULU] Creating MCP server.");
|
|
7218
|
-
|
|
7219
|
-
|
|
7220
|
-
|
|
7310
|
+
server = {
|
|
7311
|
+
mcp: new import_mcp.McpServer({
|
|
7312
|
+
name: "exulu-mcp-server-" + agentInstance.name + "(" + agentInstance.id + ")",
|
|
7313
|
+
version: "1.0.0"
|
|
7314
|
+
}),
|
|
7315
|
+
tools: {}
|
|
7316
|
+
};
|
|
7317
|
+
this.server[agentInstance.id] = server;
|
|
7318
|
+
}
|
|
7319
|
+
const disabledTools = [];
|
|
7320
|
+
let enabledTools = await getEnabledTools(agentInstance, allTools, disabledTools, allAgents, user);
|
|
7321
|
+
const backend = allAgents.find((a) => a.id === agentInstance.backend);
|
|
7322
|
+
if (!backend) {
|
|
7323
|
+
throw new Error("Agent backend not found for agent " + agentInstance.name + " (" + agentInstance.id + ").");
|
|
7324
|
+
}
|
|
7325
|
+
const agentTool = await backend.tool(agentInstance.id, allAgents);
|
|
7326
|
+
if (agentTool) {
|
|
7327
|
+
enabledTools = [
|
|
7328
|
+
...enabledTools,
|
|
7329
|
+
agentTool
|
|
7330
|
+
];
|
|
7331
|
+
}
|
|
7332
|
+
const variableName = agentInstance.providerapikey;
|
|
7333
|
+
const { db: db3 } = await postgresClient();
|
|
7334
|
+
const variable = await db3.from("variables").where({ name: variableName }).first();
|
|
7335
|
+
if (!variable) {
|
|
7336
|
+
throw new Error("Provider API key variable not found.");
|
|
7337
|
+
}
|
|
7338
|
+
let providerapikey = variable.value;
|
|
7339
|
+
if (!variable.encrypted) {
|
|
7340
|
+
throw new Error("Provider API key variable not encrypted, for security reasons you are only allowed to use encrypted variables for provider API keys.");
|
|
7341
|
+
}
|
|
7342
|
+
if (variable.encrypted) {
|
|
7343
|
+
const bytes = import_crypto_js5.default.AES.decrypt(variable.value, process.env.NEXTAUTH_SECRET);
|
|
7344
|
+
providerapikey = bytes.toString(import_crypto_js5.default.enc.Utf8);
|
|
7345
|
+
}
|
|
7346
|
+
console.log("[EXULU] Enabled tools", enabledTools?.map((x) => x.name + " (" + x.id + ")"));
|
|
7347
|
+
for (const tool2 of enabledTools || []) {
|
|
7348
|
+
if (server.tools[tool2.id]) {
|
|
7349
|
+
continue;
|
|
7350
|
+
}
|
|
7351
|
+
server.mcp.registerTool(sanitizeToolName(tool2.name + "_agent_" + tool2.id), {
|
|
7352
|
+
title: tool2.name + " agent",
|
|
7353
|
+
description: tool2.description,
|
|
7354
|
+
inputSchema: {
|
|
7355
|
+
inputs: tool2.inputSchema || import_zod2.z.object({})
|
|
7356
|
+
}
|
|
7357
|
+
}, async ({ inputs }, args) => {
|
|
7358
|
+
console.log("[EXULU] MCP tool name", tool2.name);
|
|
7359
|
+
console.log("[EXULU] MCP tool inputs", inputs);
|
|
7360
|
+
console.log("[EXULU] MCP tool args", args);
|
|
7361
|
+
const configValues = agentInstance.tools;
|
|
7362
|
+
const tools = convertToolsArrayToObject(
|
|
7363
|
+
[tool2],
|
|
7364
|
+
allTools,
|
|
7365
|
+
configValues,
|
|
7366
|
+
providerapikey,
|
|
7367
|
+
allContexts,
|
|
7368
|
+
user,
|
|
7369
|
+
config
|
|
7370
|
+
);
|
|
7371
|
+
const convertedTool = tools[sanitizeToolName(tool2.name)];
|
|
7372
|
+
if (!convertedTool?.execute) {
|
|
7373
|
+
console.error("[EXULU] Tool not found in converted tools array.", tools);
|
|
7374
|
+
throw new Error("Tool not found in converted tools array.");
|
|
7375
|
+
}
|
|
7376
|
+
const iterator = await convertedTool.execute(inputs, {
|
|
7377
|
+
toolCallId: tool2.id + "_" + (0, import_node_crypto4.randomUUID)(),
|
|
7378
|
+
messages: []
|
|
7379
|
+
});
|
|
7380
|
+
let result;
|
|
7381
|
+
for await (const value of iterator) {
|
|
7382
|
+
result = value;
|
|
7383
|
+
}
|
|
7384
|
+
console.log("[EXULU] MCP tool result", result);
|
|
7385
|
+
return {
|
|
7386
|
+
content: [{ type: "text", text: JSON.stringify(result) }],
|
|
7387
|
+
structuredContent: result
|
|
7388
|
+
};
|
|
7221
7389
|
});
|
|
7390
|
+
server.tools[tool2.id] = tool2.name;
|
|
7222
7391
|
}
|
|
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
|
-
);
|
|
7392
|
+
return server.mcp;
|
|
7275
7393
|
};
|
|
7276
|
-
|
|
7277
|
-
if (!
|
|
7394
|
+
create = async ({ express: express3, allTools, allAgents, allContexts, config }) => {
|
|
7395
|
+
if (!express3) {
|
|
7278
7396
|
throw new Error("Express not initialized.");
|
|
7279
7397
|
}
|
|
7280
7398
|
if (!this.server) {
|
|
7281
7399
|
throw new Error("MCP server not initialized.");
|
|
7282
7400
|
}
|
|
7283
|
-
|
|
7284
|
-
|
|
7285
|
-
|
|
7401
|
+
express3.post("/mcp/:agent", async (req, res) => {
|
|
7402
|
+
console.log("[EXULU] MCP request received.", req.params.agent);
|
|
7403
|
+
if (!req.params.agent) {
|
|
7404
|
+
res.status(400).json({
|
|
7405
|
+
error: "Bad Request: No agent ID provided"
|
|
7406
|
+
});
|
|
7407
|
+
return;
|
|
7408
|
+
}
|
|
7409
|
+
const agentInstance = await loadAgent(req.params.agent);
|
|
7410
|
+
if (!agentInstance) {
|
|
7411
|
+
console.error("[EXULU] Agent not found.", req.params.agent);
|
|
7412
|
+
res.status(404).json({
|
|
7413
|
+
error: "Agent not found"
|
|
7414
|
+
});
|
|
7415
|
+
return;
|
|
7416
|
+
}
|
|
7417
|
+
const authenticationResult = await requestValidators.authenticate(req);
|
|
7418
|
+
if (!authenticationResult.user?.id) {
|
|
7419
|
+
res.status(authenticationResult.code || 500).json({ detail: `${authenticationResult.message}` });
|
|
7420
|
+
return;
|
|
7421
|
+
}
|
|
7422
|
+
const user = authenticationResult.user;
|
|
7423
|
+
const hasAccessToAgent = await checkRecordAccess(agentInstance, "read", user);
|
|
7424
|
+
if (!hasAccessToAgent) {
|
|
7425
|
+
res.status(401).json({
|
|
7426
|
+
message: "You don't have access to this agent."
|
|
7427
|
+
});
|
|
7428
|
+
return;
|
|
7429
|
+
}
|
|
7430
|
+
const server = await this.configure({
|
|
7431
|
+
agentInstance,
|
|
7432
|
+
user,
|
|
7433
|
+
allTools,
|
|
7434
|
+
allAgents,
|
|
7435
|
+
allContexts,
|
|
7436
|
+
config
|
|
7437
|
+
});
|
|
7438
|
+
if (!server) {
|
|
7439
|
+
throw new Error("MCP server for agent " + req.params.agent + " not initialized.");
|
|
7286
7440
|
}
|
|
7287
7441
|
const sessionId = req.headers[SESSION_ID_HEADER];
|
|
7288
7442
|
let transport;
|
|
7289
7443
|
if (sessionId && this.transports[sessionId]) {
|
|
7290
7444
|
transport = this.transports[sessionId];
|
|
7291
7445
|
} else if (!sessionId && (0, import_types.isInitializeRequest)(req.body)) {
|
|
7292
|
-
transport = new
|
|
7446
|
+
transport = new import_streamableHttp.StreamableHTTPServerTransport({
|
|
7293
7447
|
sessionIdGenerator: () => (0, import_node_crypto4.randomUUID)(),
|
|
7294
7448
|
onsessioninitialized: (sessionId2) => {
|
|
7295
7449
|
this.transports[sessionId2] = transport;
|
|
@@ -7304,13 +7458,14 @@ ${code}`
|
|
|
7304
7458
|
delete this.transports[transport.sessionId];
|
|
7305
7459
|
}
|
|
7306
7460
|
};
|
|
7307
|
-
await
|
|
7461
|
+
await server.connect(transport);
|
|
7308
7462
|
} else {
|
|
7309
7463
|
res.status(400).json({
|
|
7310
7464
|
error: "Bad Request: No valid session ID provided"
|
|
7311
7465
|
});
|
|
7312
7466
|
return;
|
|
7313
7467
|
}
|
|
7468
|
+
req.headers["exulu-agent-id"] = req.params.agent;
|
|
7314
7469
|
await transport.handleRequest(req, res, req.body);
|
|
7315
7470
|
});
|
|
7316
7471
|
const handleSessionRequest = async (req, res) => {
|
|
@@ -7323,9 +7478,10 @@ ${code}`
|
|
|
7323
7478
|
const transport = this.transports[sessionId];
|
|
7324
7479
|
await transport.handleRequest(req, res);
|
|
7325
7480
|
};
|
|
7326
|
-
|
|
7327
|
-
|
|
7328
|
-
|
|
7481
|
+
express3.get("/mcp/:agent", handleSessionRequest);
|
|
7482
|
+
express3.delete("/mcp/:agent", handleSessionRequest);
|
|
7483
|
+
console.log("[EXULU] MCP server created.");
|
|
7484
|
+
return express3;
|
|
7329
7485
|
};
|
|
7330
7486
|
};
|
|
7331
7487
|
|
|
@@ -8760,13 +8916,11 @@ var ExuluApp = class {
|
|
|
8760
8916
|
const mcp = new ExuluMCP();
|
|
8761
8917
|
await mcp.create({
|
|
8762
8918
|
express: app,
|
|
8763
|
-
|
|
8764
|
-
|
|
8765
|
-
|
|
8766
|
-
|
|
8767
|
-
tracer
|
|
8919
|
+
allTools: this._tools,
|
|
8920
|
+
allAgents: this._agents,
|
|
8921
|
+
allContexts: Object.values(this._contexts ?? {}),
|
|
8922
|
+
config: this._config
|
|
8768
8923
|
});
|
|
8769
|
-
await mcp.connect();
|
|
8770
8924
|
}
|
|
8771
8925
|
return app;
|
|
8772
8926
|
}
|
|
@@ -10203,7 +10357,7 @@ var create = ({
|
|
|
10203
10357
|
};
|
|
10204
10358
|
|
|
10205
10359
|
// src/index.ts
|
|
10206
|
-
var
|
|
10360
|
+
var import_crypto_js6 = __toESM(require("crypto-js"), 1);
|
|
10207
10361
|
var ExuluJobs = {
|
|
10208
10362
|
redis: redisClient,
|
|
10209
10363
|
jobs: {
|
|
@@ -10240,8 +10394,8 @@ var ExuluVariables = {
|
|
|
10240
10394
|
throw new Error(`Variable ${name} not found.`);
|
|
10241
10395
|
}
|
|
10242
10396
|
if (variable.encrypted) {
|
|
10243
|
-
const bytes =
|
|
10244
|
-
variable.value = bytes.toString(
|
|
10397
|
+
const bytes = import_crypto_js6.default.AES.decrypt(variable.value, process.env.NEXTAUTH_SECRET);
|
|
10398
|
+
variable.value = bytes.toString(import_crypto_js6.default.enc.Utf8);
|
|
10245
10399
|
}
|
|
10246
10400
|
return variable.value;
|
|
10247
10401
|
}
|
package/dist/index.js
CHANGED
|
@@ -385,8 +385,6 @@ var sanitizeName = (name) => {
|
|
|
385
385
|
|
|
386
386
|
// src/registry/classes.ts
|
|
387
387
|
import CryptoJS2 from "crypto-js";
|
|
388
|
-
import "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
389
|
-
import "ai/mcp-stdio";
|
|
390
388
|
|
|
391
389
|
// src/registry/utils/graphql.ts
|
|
392
390
|
import { makeExecutableSchema } from "@graphql-tools/schema";
|
|
@@ -1089,6 +1087,10 @@ var statisticsSchema = {
|
|
|
1089
1087
|
{
|
|
1090
1088
|
name: "role",
|
|
1091
1089
|
type: "uuid"
|
|
1090
|
+
},
|
|
1091
|
+
{
|
|
1092
|
+
name: "project",
|
|
1093
|
+
type: "uuid"
|
|
1092
1094
|
}
|
|
1093
1095
|
]
|
|
1094
1096
|
};
|
|
@@ -3988,7 +3990,6 @@ type StatisticsResult {
|
|
|
3988
3990
|
}
|
|
3989
3991
|
`;
|
|
3990
3992
|
const fullSDL = typeDefs + mutationDefs + modelDefs + genericTypes;
|
|
3991
|
-
console.log("[EXULU] Full SDL:", fullSDL);
|
|
3992
3993
|
const schema = makeExecutableSchema({
|
|
3993
3994
|
typeDefs: fullSDL,
|
|
3994
3995
|
resolvers
|
|
@@ -4600,7 +4601,6 @@ var createUppyRoutes = async (app, config) => {
|
|
|
4600
4601
|
};
|
|
4601
4602
|
|
|
4602
4603
|
// src/registry/classes.ts
|
|
4603
|
-
import "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
4604
4604
|
var s3Client2;
|
|
4605
4605
|
function sanitizeToolName(name) {
|
|
4606
4606
|
if (typeof name !== "string") return "";
|
|
@@ -4842,7 +4842,6 @@ var ExuluAgent2 = class {
|
|
|
4842
4842
|
return typeof model === "string" ? model : model?.modelId || "";
|
|
4843
4843
|
}
|
|
4844
4844
|
// Exports the agent as a tool that can be used by another agent
|
|
4845
|
-
// todo test this
|
|
4846
4845
|
tool = async (instance, agents) => {
|
|
4847
4846
|
const agentInstance = await loadAgent(instance);
|
|
4848
4847
|
if (!agentInstance) {
|
|
@@ -5761,6 +5760,7 @@ var updateStatistic = async (statistic) => {
|
|
|
5761
5760
|
const existing = await db3.from("tracking").where({
|
|
5762
5761
|
...statistic.user ? { user: statistic.user } : {},
|
|
5763
5762
|
...statistic.role ? { role: statistic.role } : {},
|
|
5763
|
+
...statistic.project ? { project: statistic.project } : {},
|
|
5764
5764
|
name: statistic.name,
|
|
5765
5765
|
label: statistic.label,
|
|
5766
5766
|
type: statistic.type,
|
|
@@ -5774,16 +5774,14 @@ var updateStatistic = async (statistic) => {
|
|
|
5774
5774
|
total: statistic.count ?? 1,
|
|
5775
5775
|
createdAt: currentDate,
|
|
5776
5776
|
...statistic.user ? { user: statistic.user } : {},
|
|
5777
|
-
...statistic.role ? { role: statistic.role } : {}
|
|
5777
|
+
...statistic.role ? { role: statistic.role } : {},
|
|
5778
|
+
...statistic.project ? { project: statistic.project } : {}
|
|
5778
5779
|
});
|
|
5779
5780
|
} else {
|
|
5780
5781
|
await db3.from("tracking").update({
|
|
5781
5782
|
total: db3.raw("total + ?", [statistic.count ?? 1])
|
|
5782
5783
|
}).where({
|
|
5783
|
-
|
|
5784
|
-
label: statistic.label,
|
|
5785
|
-
type: statistic.type,
|
|
5786
|
-
createdAt: currentDate
|
|
5784
|
+
id: existing.id
|
|
5787
5785
|
});
|
|
5788
5786
|
}
|
|
5789
5787
|
};
|
|
@@ -5873,6 +5871,9 @@ var CLAUDE_MESSAGES = {
|
|
|
5873
5871
|
\x1B[0m`,
|
|
5874
5872
|
not_enabled: `
|
|
5875
5873
|
\x1B[41m -- The agent you selected does not have a valid API key set for it. --
|
|
5874
|
+
\x1B[0m`,
|
|
5875
|
+
missing_project: `
|
|
5876
|
+
\x1B[41m -- Project not found or you do not have access to it. --
|
|
5876
5877
|
\x1B[0m`
|
|
5877
5878
|
};
|
|
5878
5879
|
|
|
@@ -6175,7 +6176,6 @@ Mood: friendly and intelligent.
|
|
|
6175
6176
|
console.log("[EXULU] agent tools", agentInstance.tools?.map((x) => x.name + " (" + x.id + ")"));
|
|
6176
6177
|
const disabledTools = req.body.disabledTools ? req.body.disabledTools : [];
|
|
6177
6178
|
let enabledTools = await getEnabledTools(agentInstance, tools, disabledTools, agents, user);
|
|
6178
|
-
console.log("[EXULU] enabled tools", enabledTools?.map((x) => x.name + " (" + x.id + ")"));
|
|
6179
6179
|
const variableName = agentInstance.providerapikey;
|
|
6180
6180
|
const variable = await db3.from("variables").where({ name: variableName }).first();
|
|
6181
6181
|
if (!variable) {
|
|
@@ -6323,11 +6323,12 @@ Mood: friendly and intelligent.
|
|
|
6323
6323
|
}
|
|
6324
6324
|
});
|
|
6325
6325
|
});
|
|
6326
|
-
app.use("/gateway/anthropic/:
|
|
6326
|
+
app.use("/gateway/anthropic/:agent/:project", express.raw({ type: "*/*", limit: REQUEST_SIZE_LIMIT }), async (req, res) => {
|
|
6327
6327
|
try {
|
|
6328
6328
|
if (!req.body.tools) {
|
|
6329
6329
|
req.body.tools = [];
|
|
6330
6330
|
}
|
|
6331
|
+
const { db: db3 } = await postgresClient();
|
|
6331
6332
|
const authenticationResult = await requestValidators.authenticate(req);
|
|
6332
6333
|
if (!authenticationResult.user?.id) {
|
|
6333
6334
|
console.log("[EXULU] failed authentication result", authenticationResult);
|
|
@@ -6335,20 +6336,35 @@ Mood: friendly and intelligent.
|
|
|
6335
6336
|
return;
|
|
6336
6337
|
}
|
|
6337
6338
|
const user = authenticationResult.user;
|
|
6338
|
-
|
|
6339
|
-
|
|
6340
|
-
|
|
6341
|
-
|
|
6342
|
-
|
|
6343
|
-
const agent = await query.first();
|
|
6339
|
+
let agentQuery = db3("agents");
|
|
6340
|
+
agentQuery.select("*");
|
|
6341
|
+
agentQuery = applyAccessControl(agentsSchema2(), authenticationResult.user, agentQuery);
|
|
6342
|
+
agentQuery.where({ id: req.params.agent });
|
|
6343
|
+
const agent = await agentQuery.first();
|
|
6344
6344
|
if (!agent) {
|
|
6345
6345
|
const arrayBuffer = createCustomAnthropicStreamingMessage(`
|
|
6346
|
-
\x1B[41m -- Agent ${req.params.
|
|
6346
|
+
\x1B[41m -- Agent ${req.params.agent} not found or you do not have access to it. --
|
|
6347
6347
|
\x1B[0m`);
|
|
6348
6348
|
res.setHeader("Content-Type", "application/json");
|
|
6349
6349
|
res.end(Buffer.from(arrayBuffer));
|
|
6350
6350
|
return;
|
|
6351
6351
|
}
|
|
6352
|
+
let project = null;
|
|
6353
|
+
if (!req.body.project || req.body.project === "DEFAULT") {
|
|
6354
|
+
project = null;
|
|
6355
|
+
} else {
|
|
6356
|
+
let projectQuery = db3("projects");
|
|
6357
|
+
projectQuery.select("*");
|
|
6358
|
+
projectQuery = applyAccessControl(projectsSchema2(), authenticationResult.user, projectQuery);
|
|
6359
|
+
projectQuery.where({ id: req.params.project });
|
|
6360
|
+
project = await projectQuery.first();
|
|
6361
|
+
if (!project) {
|
|
6362
|
+
const arrayBuffer = createCustomAnthropicStreamingMessage(CLAUDE_MESSAGES.missing_project);
|
|
6363
|
+
res.setHeader("Content-Type", "application/json");
|
|
6364
|
+
res.end(Buffer.from(arrayBuffer));
|
|
6365
|
+
return;
|
|
6366
|
+
}
|
|
6367
|
+
}
|
|
6352
6368
|
console.log("[EXULU] anthropic proxy called for agent:", agent?.name);
|
|
6353
6369
|
if (!process.env.NEXTAUTH_SECRET) {
|
|
6354
6370
|
const arrayBuffer = createCustomAnthropicStreamingMessage(CLAUDE_MESSAGES.missing_nextauth_secret);
|
|
@@ -6392,10 +6408,47 @@ Mood: friendly and intelligent.
|
|
|
6392
6408
|
apiKey: anthropicApiKey
|
|
6393
6409
|
});
|
|
6394
6410
|
const tokens = {};
|
|
6395
|
-
|
|
6396
|
-
|
|
6397
|
-
|
|
6398
|
-
|
|
6411
|
+
const disabledTools = req.body.disabledTools ? req.body.disabledTools : [];
|
|
6412
|
+
let enabledTools = await getEnabledTools(agent, tools, disabledTools, agents, user);
|
|
6413
|
+
let system = req.body.system;
|
|
6414
|
+
if (Array.isArray(req.body.system)) {
|
|
6415
|
+
system = [
|
|
6416
|
+
...req.body.system,
|
|
6417
|
+
...agent ? [
|
|
6418
|
+
{
|
|
6419
|
+
type: "text",
|
|
6420
|
+
text: `
|
|
6421
|
+
You are an agent named: ${agent?.name}
|
|
6422
|
+
Here are some additional instructions for you: ${agent?.instructions}`
|
|
6423
|
+
}
|
|
6424
|
+
] : [],
|
|
6425
|
+
...project ? [
|
|
6426
|
+
{
|
|
6427
|
+
type: "text",
|
|
6428
|
+
text: `Additional information:
|
|
6429
|
+
|
|
6430
|
+
The project you are working on is: ${project?.name}
|
|
6431
|
+
Here is some additional information about the project: ${project?.description}`
|
|
6432
|
+
}
|
|
6433
|
+
] : []
|
|
6434
|
+
];
|
|
6435
|
+
} else {
|
|
6436
|
+
system = `${req.body.system}
|
|
6437
|
+
|
|
6438
|
+
|
|
6439
|
+
${agent ? `You are an agent named: ${agent?.name}
|
|
6440
|
+
Here are some additional instructions for you: ${agent?.instructions}` : ""}
|
|
6441
|
+
|
|
6442
|
+
${project?.id ? `Additional information:
|
|
6443
|
+
|
|
6444
|
+
The project you are working on is: ${project?.name}
|
|
6445
|
+
The project description is: ${project?.description}` : ""}
|
|
6446
|
+
`;
|
|
6447
|
+
}
|
|
6448
|
+
for await (const event of client2.messages.stream({
|
|
6449
|
+
...req.body,
|
|
6450
|
+
system
|
|
6451
|
+
})) {
|
|
6399
6452
|
if (event.message?.id) {
|
|
6400
6453
|
tokens[event.message.id] = {
|
|
6401
6454
|
input_tokens: event.message.usage.input_tokens,
|
|
@@ -6404,11 +6457,45 @@ Mood: friendly and intelligent.
|
|
|
6404
6457
|
output_tokens: event.message.usage.output_tokens
|
|
6405
6458
|
};
|
|
6406
6459
|
}
|
|
6407
|
-
|
|
6460
|
+
if (event.message?.type === "tool_use" && event.message?.name?.includes("exulu_")) {
|
|
6461
|
+
const toolName = event.message?.name;
|
|
6462
|
+
console.log("[EXULU] Using tool", toolName);
|
|
6463
|
+
const inputs = event.message?.input;
|
|
6464
|
+
const id = event.message?.id;
|
|
6465
|
+
const tool2 = enabledTools.find((tool3) => tool3.id === toolName.replace("exulu_", ""));
|
|
6466
|
+
if (!tool2 || !tool2.tool.execute) {
|
|
6467
|
+
console.error("[EXULU] Tool not found or not enabled.", toolName);
|
|
6468
|
+
continue;
|
|
6469
|
+
}
|
|
6470
|
+
const toolResult = await tool2.tool.execute(inputs, {
|
|
6471
|
+
toolCallId: id,
|
|
6472
|
+
messages: [{
|
|
6473
|
+
...event.message,
|
|
6474
|
+
role: "tool"
|
|
6475
|
+
}]
|
|
6476
|
+
});
|
|
6477
|
+
console.log("[EXULU] Tool result", toolResult);
|
|
6478
|
+
const toolResultMessage = {
|
|
6479
|
+
role: "user",
|
|
6480
|
+
content: [
|
|
6481
|
+
{
|
|
6482
|
+
type: "tool_result",
|
|
6483
|
+
tool_use_id: id,
|
|
6484
|
+
content: toolResult
|
|
6485
|
+
}
|
|
6486
|
+
]
|
|
6487
|
+
};
|
|
6488
|
+
res.write(`event: tool_result
|
|
6489
|
+
data: ${JSON.stringify(toolResultMessage)}
|
|
6490
|
+
|
|
6491
|
+
`);
|
|
6492
|
+
} else {
|
|
6493
|
+
const msg = `event: ${event.type}
|
|
6408
6494
|
data: ${JSON.stringify(event)}
|
|
6409
6495
|
|
|
6410
6496
|
`;
|
|
6411
|
-
|
|
6497
|
+
res.write(msg);
|
|
6498
|
+
}
|
|
6412
6499
|
}
|
|
6413
6500
|
let totalInputTokens = 0;
|
|
6414
6501
|
let totalOutputTokens = 0;
|
|
@@ -6428,7 +6515,8 @@ data: ${JSON.stringify(event)}
|
|
|
6428
6515
|
trigger: statistics.trigger,
|
|
6429
6516
|
count: 1,
|
|
6430
6517
|
user: user.id,
|
|
6431
|
-
role: user?.role?.id
|
|
6518
|
+
role: user?.role?.id,
|
|
6519
|
+
...project ? { project: project.id } : {}
|
|
6432
6520
|
}),
|
|
6433
6521
|
...totalInputTokens ? [
|
|
6434
6522
|
updateStatistic({
|
|
@@ -6438,7 +6526,8 @@ data: ${JSON.stringify(event)}
|
|
|
6438
6526
|
trigger: statistics.trigger,
|
|
6439
6527
|
count: totalInputTokens,
|
|
6440
6528
|
user: user.id,
|
|
6441
|
-
role: user?.role?.id
|
|
6529
|
+
role: user?.role?.id,
|
|
6530
|
+
...project ? { project: project.id } : {}
|
|
6442
6531
|
})
|
|
6443
6532
|
] : [],
|
|
6444
6533
|
...totalOutputTokens ? [
|
|
@@ -6447,7 +6536,10 @@ data: ${JSON.stringify(event)}
|
|
|
6447
6536
|
label: statistics.label,
|
|
6448
6537
|
type: STATISTICS_TYPE_ENUM.AGENT_RUN,
|
|
6449
6538
|
trigger: statistics.trigger,
|
|
6450
|
-
count:
|
|
6539
|
+
count: totalInputTokens,
|
|
6540
|
+
user: user.id,
|
|
6541
|
+
role: user?.role?.id,
|
|
6542
|
+
...project ? { project: project.id } : {}
|
|
6451
6543
|
})
|
|
6452
6544
|
] : []
|
|
6453
6545
|
]);
|
|
@@ -7164,99 +7256,161 @@ function getAverage(arr) {
|
|
|
7164
7256
|
}
|
|
7165
7257
|
|
|
7166
7258
|
// src/mcp/index.ts
|
|
7167
|
-
import { McpServer
|
|
7259
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
7168
7260
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
7169
|
-
import { StreamableHTTPServerTransport
|
|
7261
|
+
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
7170
7262
|
import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
|
|
7171
|
-
import { z as z2 } from "zod";
|
|
7172
7263
|
import "express";
|
|
7173
7264
|
import "@opentelemetry/api";
|
|
7265
|
+
import CryptoJS5 from "crypto-js";
|
|
7266
|
+
import { z as z2 } from "zod";
|
|
7174
7267
|
var SESSION_ID_HEADER = "mcp-session-id";
|
|
7175
7268
|
var ExuluMCP = class {
|
|
7176
|
-
server;
|
|
7269
|
+
server = {};
|
|
7177
7270
|
transports = {};
|
|
7178
|
-
express;
|
|
7179
7271
|
constructor() {
|
|
7180
7272
|
}
|
|
7181
|
-
|
|
7182
|
-
|
|
7183
|
-
if (!
|
|
7273
|
+
configure = async ({ user, agentInstance, allTools, allAgents, allContexts, config, tracer }) => {
|
|
7274
|
+
let server = this.server[agentInstance.id];
|
|
7275
|
+
if (!server) {
|
|
7184
7276
|
console.log("[EXULU] Creating MCP server.");
|
|
7185
|
-
|
|
7186
|
-
|
|
7187
|
-
|
|
7277
|
+
server = {
|
|
7278
|
+
mcp: new McpServer({
|
|
7279
|
+
name: "exulu-mcp-server-" + agentInstance.name + "(" + agentInstance.id + ")",
|
|
7280
|
+
version: "1.0.0"
|
|
7281
|
+
}),
|
|
7282
|
+
tools: {}
|
|
7283
|
+
};
|
|
7284
|
+
this.server[agentInstance.id] = server;
|
|
7285
|
+
}
|
|
7286
|
+
const disabledTools = [];
|
|
7287
|
+
let enabledTools = await getEnabledTools(agentInstance, allTools, disabledTools, allAgents, user);
|
|
7288
|
+
const backend = allAgents.find((a) => a.id === agentInstance.backend);
|
|
7289
|
+
if (!backend) {
|
|
7290
|
+
throw new Error("Agent backend not found for agent " + agentInstance.name + " (" + agentInstance.id + ").");
|
|
7291
|
+
}
|
|
7292
|
+
const agentTool = await backend.tool(agentInstance.id, allAgents);
|
|
7293
|
+
if (agentTool) {
|
|
7294
|
+
enabledTools = [
|
|
7295
|
+
...enabledTools,
|
|
7296
|
+
agentTool
|
|
7297
|
+
];
|
|
7298
|
+
}
|
|
7299
|
+
const variableName = agentInstance.providerapikey;
|
|
7300
|
+
const { db: db3 } = await postgresClient();
|
|
7301
|
+
const variable = await db3.from("variables").where({ name: variableName }).first();
|
|
7302
|
+
if (!variable) {
|
|
7303
|
+
throw new Error("Provider API key variable not found.");
|
|
7304
|
+
}
|
|
7305
|
+
let providerapikey = variable.value;
|
|
7306
|
+
if (!variable.encrypted) {
|
|
7307
|
+
throw new Error("Provider API key variable not encrypted, for security reasons you are only allowed to use encrypted variables for provider API keys.");
|
|
7308
|
+
}
|
|
7309
|
+
if (variable.encrypted) {
|
|
7310
|
+
const bytes = CryptoJS5.AES.decrypt(variable.value, process.env.NEXTAUTH_SECRET);
|
|
7311
|
+
providerapikey = bytes.toString(CryptoJS5.enc.Utf8);
|
|
7312
|
+
}
|
|
7313
|
+
console.log("[EXULU] Enabled tools", enabledTools?.map((x) => x.name + " (" + x.id + ")"));
|
|
7314
|
+
for (const tool2 of enabledTools || []) {
|
|
7315
|
+
if (server.tools[tool2.id]) {
|
|
7316
|
+
continue;
|
|
7317
|
+
}
|
|
7318
|
+
server.mcp.registerTool(sanitizeToolName(tool2.name + "_agent_" + tool2.id), {
|
|
7319
|
+
title: tool2.name + " agent",
|
|
7320
|
+
description: tool2.description,
|
|
7321
|
+
inputSchema: {
|
|
7322
|
+
inputs: tool2.inputSchema || z2.object({})
|
|
7323
|
+
}
|
|
7324
|
+
}, async ({ inputs }, args) => {
|
|
7325
|
+
console.log("[EXULU] MCP tool name", tool2.name);
|
|
7326
|
+
console.log("[EXULU] MCP tool inputs", inputs);
|
|
7327
|
+
console.log("[EXULU] MCP tool args", args);
|
|
7328
|
+
const configValues = agentInstance.tools;
|
|
7329
|
+
const tools = convertToolsArrayToObject(
|
|
7330
|
+
[tool2],
|
|
7331
|
+
allTools,
|
|
7332
|
+
configValues,
|
|
7333
|
+
providerapikey,
|
|
7334
|
+
allContexts,
|
|
7335
|
+
user,
|
|
7336
|
+
config
|
|
7337
|
+
);
|
|
7338
|
+
const convertedTool = tools[sanitizeToolName(tool2.name)];
|
|
7339
|
+
if (!convertedTool?.execute) {
|
|
7340
|
+
console.error("[EXULU] Tool not found in converted tools array.", tools);
|
|
7341
|
+
throw new Error("Tool not found in converted tools array.");
|
|
7342
|
+
}
|
|
7343
|
+
const iterator = await convertedTool.execute(inputs, {
|
|
7344
|
+
toolCallId: tool2.id + "_" + randomUUID4(),
|
|
7345
|
+
messages: []
|
|
7346
|
+
});
|
|
7347
|
+
let result;
|
|
7348
|
+
for await (const value of iterator) {
|
|
7349
|
+
result = value;
|
|
7350
|
+
}
|
|
7351
|
+
console.log("[EXULU] MCP tool result", result);
|
|
7352
|
+
return {
|
|
7353
|
+
content: [{ type: "text", text: JSON.stringify(result) }],
|
|
7354
|
+
structuredContent: result
|
|
7355
|
+
};
|
|
7188
7356
|
});
|
|
7357
|
+
server.tools[tool2.id] = tool2.name;
|
|
7189
7358
|
}
|
|
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
|
-
);
|
|
7359
|
+
return server.mcp;
|
|
7242
7360
|
};
|
|
7243
|
-
|
|
7244
|
-
if (!
|
|
7361
|
+
create = async ({ express: express3, allTools, allAgents, allContexts, config }) => {
|
|
7362
|
+
if (!express3) {
|
|
7245
7363
|
throw new Error("Express not initialized.");
|
|
7246
7364
|
}
|
|
7247
7365
|
if (!this.server) {
|
|
7248
7366
|
throw new Error("MCP server not initialized.");
|
|
7249
7367
|
}
|
|
7250
|
-
|
|
7251
|
-
|
|
7252
|
-
|
|
7368
|
+
express3.post("/mcp/:agent", async (req, res) => {
|
|
7369
|
+
console.log("[EXULU] MCP request received.", req.params.agent);
|
|
7370
|
+
if (!req.params.agent) {
|
|
7371
|
+
res.status(400).json({
|
|
7372
|
+
error: "Bad Request: No agent ID provided"
|
|
7373
|
+
});
|
|
7374
|
+
return;
|
|
7375
|
+
}
|
|
7376
|
+
const agentInstance = await loadAgent(req.params.agent);
|
|
7377
|
+
if (!agentInstance) {
|
|
7378
|
+
console.error("[EXULU] Agent not found.", req.params.agent);
|
|
7379
|
+
res.status(404).json({
|
|
7380
|
+
error: "Agent not found"
|
|
7381
|
+
});
|
|
7382
|
+
return;
|
|
7383
|
+
}
|
|
7384
|
+
const authenticationResult = await requestValidators.authenticate(req);
|
|
7385
|
+
if (!authenticationResult.user?.id) {
|
|
7386
|
+
res.status(authenticationResult.code || 500).json({ detail: `${authenticationResult.message}` });
|
|
7387
|
+
return;
|
|
7388
|
+
}
|
|
7389
|
+
const user = authenticationResult.user;
|
|
7390
|
+
const hasAccessToAgent = await checkRecordAccess(agentInstance, "read", user);
|
|
7391
|
+
if (!hasAccessToAgent) {
|
|
7392
|
+
res.status(401).json({
|
|
7393
|
+
message: "You don't have access to this agent."
|
|
7394
|
+
});
|
|
7395
|
+
return;
|
|
7396
|
+
}
|
|
7397
|
+
const server = await this.configure({
|
|
7398
|
+
agentInstance,
|
|
7399
|
+
user,
|
|
7400
|
+
allTools,
|
|
7401
|
+
allAgents,
|
|
7402
|
+
allContexts,
|
|
7403
|
+
config
|
|
7404
|
+
});
|
|
7405
|
+
if (!server) {
|
|
7406
|
+
throw new Error("MCP server for agent " + req.params.agent + " not initialized.");
|
|
7253
7407
|
}
|
|
7254
7408
|
const sessionId = req.headers[SESSION_ID_HEADER];
|
|
7255
7409
|
let transport;
|
|
7256
7410
|
if (sessionId && this.transports[sessionId]) {
|
|
7257
7411
|
transport = this.transports[sessionId];
|
|
7258
7412
|
} else if (!sessionId && isInitializeRequest(req.body)) {
|
|
7259
|
-
transport = new
|
|
7413
|
+
transport = new StreamableHTTPServerTransport({
|
|
7260
7414
|
sessionIdGenerator: () => randomUUID4(),
|
|
7261
7415
|
onsessioninitialized: (sessionId2) => {
|
|
7262
7416
|
this.transports[sessionId2] = transport;
|
|
@@ -7271,13 +7425,14 @@ ${code}`
|
|
|
7271
7425
|
delete this.transports[transport.sessionId];
|
|
7272
7426
|
}
|
|
7273
7427
|
};
|
|
7274
|
-
await
|
|
7428
|
+
await server.connect(transport);
|
|
7275
7429
|
} else {
|
|
7276
7430
|
res.status(400).json({
|
|
7277
7431
|
error: "Bad Request: No valid session ID provided"
|
|
7278
7432
|
});
|
|
7279
7433
|
return;
|
|
7280
7434
|
}
|
|
7435
|
+
req.headers["exulu-agent-id"] = req.params.agent;
|
|
7281
7436
|
await transport.handleRequest(req, res, req.body);
|
|
7282
7437
|
});
|
|
7283
7438
|
const handleSessionRequest = async (req, res) => {
|
|
@@ -7290,9 +7445,10 @@ ${code}`
|
|
|
7290
7445
|
const transport = this.transports[sessionId];
|
|
7291
7446
|
await transport.handleRequest(req, res);
|
|
7292
7447
|
};
|
|
7293
|
-
|
|
7294
|
-
|
|
7295
|
-
|
|
7448
|
+
express3.get("/mcp/:agent", handleSessionRequest);
|
|
7449
|
+
express3.delete("/mcp/:agent", handleSessionRequest);
|
|
7450
|
+
console.log("[EXULU] MCP server created.");
|
|
7451
|
+
return express3;
|
|
7296
7452
|
};
|
|
7297
7453
|
};
|
|
7298
7454
|
|
|
@@ -8727,13 +8883,11 @@ var ExuluApp = class {
|
|
|
8727
8883
|
const mcp = new ExuluMCP();
|
|
8728
8884
|
await mcp.create({
|
|
8729
8885
|
express: app,
|
|
8730
|
-
|
|
8731
|
-
|
|
8732
|
-
|
|
8733
|
-
|
|
8734
|
-
tracer
|
|
8886
|
+
allTools: this._tools,
|
|
8887
|
+
allAgents: this._agents,
|
|
8888
|
+
allContexts: Object.values(this._contexts ?? {}),
|
|
8889
|
+
config: this._config
|
|
8735
8890
|
});
|
|
8736
|
-
await mcp.connect();
|
|
8737
8891
|
}
|
|
8738
8892
|
return app;
|
|
8739
8893
|
}
|
|
@@ -10170,7 +10324,7 @@ var create = ({
|
|
|
10170
10324
|
};
|
|
10171
10325
|
|
|
10172
10326
|
// src/index.ts
|
|
10173
|
-
import
|
|
10327
|
+
import CryptoJS6 from "crypto-js";
|
|
10174
10328
|
var ExuluJobs = {
|
|
10175
10329
|
redis: redisClient,
|
|
10176
10330
|
jobs: {
|
|
@@ -10207,8 +10361,8 @@ var ExuluVariables = {
|
|
|
10207
10361
|
throw new Error(`Variable ${name} not found.`);
|
|
10208
10362
|
}
|
|
10209
10363
|
if (variable.encrypted) {
|
|
10210
|
-
const bytes =
|
|
10211
|
-
variable.value = bytes.toString(
|
|
10364
|
+
const bytes = CryptoJS6.AES.decrypt(variable.value, process.env.NEXTAUTH_SECRET);
|
|
10365
|
+
variable.value = bytes.toString(CryptoJS6.enc.Utf8);
|
|
10212
10366
|
}
|
|
10213
10367
|
return variable.value;
|
|
10214
10368
|
}
|