r026: add dynamic agent toolbox integration
This commit is contained in:
+64
-1
@@ -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,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user