r026: add dynamic agent toolbox integration

This commit is contained in:
Mikei386
2026-08-29 12:44:29 +02:00
parent 3c03924110
commit 63542bb29e
13 changed files with 184 additions and 11 deletions
+64 -1
View File
@@ -17,6 +17,7 @@ import {
MUA_SERVER_NAME,
MUA_VERSION,
MUA_PROTOCOL_VERSION,
discoverToolboxContainers,
} from "./helpers";
import { TOOLS, toolByName, getToolRisk } from "./tools";
import {
@@ -27,6 +28,8 @@ import {
getMaskedApiKey,
generateApiKey,
setEnabledTools,
getToolboxEnabled,
setToolboxEnabled,
} from "./auth";
import { randomBytes } from "node:crypto";
import { spawn } from "node:child_process";
@@ -40,6 +43,40 @@ const MAX_CONCURRENT_TOOL_CALLS = Number(process.env["MUA_MAX_CONCURRENT_TOOL_CA
const CORS_ORIGIN = process.env["MUA_CORS_ORIGIN"] ?? "";
const AUDIT_LOG = process.env["MUA_AUDIT_LOG"] ?? "/var/log/plugins/mua-audit.log";
async function toolboxGuidance(): Promise<{
available: boolean;
enabled: boolean;
containers: Awaited<ReturnType<typeof discoverToolboxContainers>>;
instruction: string;
}> {
let containers: Awaited<ReturnType<typeof discoverToolboxContainers>> = [];
try {
containers = await discoverToolboxContainers();
} catch {
// Docker can be temporarily unavailable during Unraid startup.
}
const available = containers.length > 0;
const enabled = getToolboxEnabled() && available;
let instruction = "";
if (enabled) {
const running = containers.filter((c) => c.state === "running");
if (running.length > 0) {
instruction = isToolEnabled("unraid_system_shell")
? `Optional agent toolbox available: ${running.map((c) => c.name).join(", ")}. ` +
"When a command-line utility is missing, use unraid_system_shell and run it with " +
"docker exec inside a labeled toolbox container. Install missing utilities there, " +
"not in the MCP client or Hermes container."
: `Agent toolbox detected (${running.map((c) => c.name).join(", ")}), but ` +
"unraid_system_shell is disabled, so the toolbox cannot currently be used through MUA.";
} else {
instruction =
`Agent toolbox configured but currently stopped: ${containers.map((c) => c.name).join(", ")}. ` +
"Do not assume its tools are available until the container is running.";
}
}
return { available, enabled, containers, instruction };
}
// ── JSON-RPC Helpers ────────────────────────────────────────────────────
function rpcResult(id: number | string | null, result: unknown) {
return { jsonrpc: "2.0", id, result };
@@ -104,11 +141,13 @@ async function handleMcpRequest(
if (method === "initialize") {
const sid = newSessionId();
touchSession(sid);
const toolbox = await toolboxGuidance();
return {
response: rpcResult(id, {
protocolVersion: MUA_PROTOCOL_VERSION,
capabilities: { tools: {} },
serverInfo: { name: MUA_SERVER_NAME, version: MUA_VERSION },
...(toolbox.instruction ? { instructions: toolbox.instruction } : {}),
}),
sessionId: sid,
};
@@ -123,13 +162,17 @@ async function handleMcpRequest(
// ── tools/list ────────────────────────────────────────────────────────
if (method === "tools/list") {
if (sessionId) touchSession(sessionId);
const toolbox = await toolboxGuidance();
// Tool-Filter: nur aktive Tools anzeigen
const activeTools = TOOLS.filter((t) => isToolEnabled(t.name));
return {
response: rpcResult(id, {
tools: activeTools.map((t) => ({
name: t.name,
description: t.description,
description:
t.name === "unraid_system_shell" && toolbox.instruction
? `${t.description} ${toolbox.instruction}`
: t.description,
inputSchema: t.inputSchema,
})),
}),
@@ -421,6 +464,7 @@ try {
// GET: Config lesen
if (method === "GET") {
const toolbox = await toolboxGuidance();
return Response.json({
apiKeyConfigured: true,
apiKeyMasked: getMaskedApiKey(),
@@ -431,6 +475,13 @@ try {
description: t.description,
risk: getToolRisk(t.name),
})),
toolbox: {
configured: getToolboxEnabled(),
available: toolbox.available,
enabled: toolbox.enabled,
label: "mike.ai.role=toolbox",
containers: toolbox.containers,
},
});
}
@@ -452,13 +503,25 @@ try {
.filter((t): t is string => typeof t === "string" && known.has(t));
setEnabledTools(tools, body["allToolsEnabled"] === true);
}
if (typeof body["toolboxEnabled"] === "boolean") {
const toolbox = await toolboxGuidance();
setToolboxEnabled(body["toolboxEnabled"] === true && toolbox.available);
}
const toolbox = await toolboxGuidance();
return Response.json({
ok: true,
generatedApiKey,
apiKeyMasked: getMaskedApiKey(),
enabledTools: getEnabledTools(),
allToolsEnabled: getAllToolsEnabled(),
toolbox: {
configured: getToolboxEnabled(),
available: toolbox.available,
enabled: getToolboxEnabled() && toolbox.available,
label: "mike.ai.role=toolbox",
containers: toolbox.containers,
},
});
}