r025: expose direct long-running Unraid terminal

This commit is contained in:
Mikei386 committed 2026-08-28 00:15:22 +02:00
1 parent b0070861d2
commit 3c03924110
9 files changed
+73 -21

No files matched your search

+8 -1
View File
@@ -162,7 +162,7 @@ mcp_servers:
url: http://192.168.1.2:3002/mcp
headers:
Authorization: Bearer <dein-api-key>
timeout: 300
timeout: 1800
connect_timeout: 30
enabled: true
```
@@ -231,6 +231,13 @@ Zusätzlich kann jedes Werkzeug einzeln nach Risikostufe freigegeben werden:
Deaktivierte Tools werden vom MCP-Server gefiltert — sie erscheinen nicht in
`tools/list` und können nicht aufgerufen werden (→ `ERROR: Tool disabled`).
Bei freigegebenem Vollzugriff ist `unraid_system_shell` das direkte Terminal
auf dem Unraid-Host. Agenten sollen es für normale Docker-, Datei- und
Administrationsaufträge unmittelbar verwenden und weder den MUA-Quellcode
untersuchen noch Befehle über ein lokales Client-Terminal weiterreichen. Das
Werkzeug akzeptiert mehrzeilige Skripte bis 256 KiB und Laufzeiten bis 30
Minuten. Nur noch längere Arbeiten gehören in `unraid_system_job_start`.
---
## Service-Verwaltung
Binary file not shown.
+8
View File
@@ -110,6 +110,14 @@ Abschließend genau einen unschädlichen Leseauftrag verwenden, beispielsweise:
Erst wenn Verbindungstest, Werkzeugerkennung und dieser Leseaufruf erfolgreich
sind, ist die Einbindung abgeschlossen.
Wenn der Benutzer MUA in der Unraid-WebGUI bewusst auf **Vollzugriff** gestellt
hat, darf Hermes das angebotene `unraid_system_shell` für ausdrücklich
beauftragte Änderungen direkt verwenden. Das ist bereits das Terminal des
Unraid-Hosts. Dafür weder MUA-Quellcode untersuchen noch das lokale
Hermes-Containerterminal als Zwischenstation verwenden. Normale Befehle und
mehrzeilige Skripte laufen synchron; nur Arbeiten über 30 Minuten verwenden
die asynchronen Job-Werkzeuge.
## 5. Entfernen
Nur die Hermes-Verbindung entfernen, nicht das MUA-Plugin auf Unraid:
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "mua",
"version": "2026.08.25.r024",
"version": "2026.08.28.r025",
"description": "Mikes Unraid Agent - MCP over HTTP (Streamable HTTP) for Unraid",
"type": "module",
"main": "src/index.ts",
+8 -4
View File
@@ -2,13 +2,13 @@
<!DOCTYPE PLUGIN [
<!ENTITY name "mua">
<!ENTITY author "Michael">
<!ENTITY version "2026.08.25.r024">
<!ENTITY version "2026.08.28.r025">
<!ENTITY launch "Settings/mua">
<!ENTITY pluginURL "http://192.168.1.2:4000/michael/MUA-Mikes-Unraid-Agent/raw/branch/main/plugin/mua.plg">
<!ENTITY pluginLOC "/boot/config/plugins/&name;">
<!ENTITY emhttpLOC "/usr/local/emhttp/plugins/&name;">
<!ENTITY txzURL "http://192.168.1.2:4000/michael/MUA-Mikes-Unraid-Agent/raw/branch/main/dist/mua-2026.08.25.r024-x86_64-1.txz">
<!ENTITY txzSHA256 "f8101b3d24fc9ebb02ce01b6f6481281578910f7a0cbe11b05bc484387ae1a90">
<!ENTITY txzURL "http://192.168.1.2:4000/michael/MUA-Mikes-Unraid-Agent/raw/branch/main/dist/mua-2026.08.28.r025-x86_64-1.txz">
<!ENTITY txzSHA256 "fec9b849f9c5527ddad31c47d60fa3dd0950b6be1794b162dfec25e83207c870">
]>
<PLUGIN name="&name;"
@@ -23,6 +23,10 @@
>
<CHANGES>
### 2026.08.28.r025
- Die freie Unraid-Shell ist als direkter Host-Terminalweg beschrieben, damit Agenten sie statt lokaler Proxy-Skripte oder MUA-Quellcodeanalyse verwenden.
- Mehrzeilige Skripte bis 256 KiB und synchrone Laufzeiten bis 30 Minuten werden unterstützt; nur noch längere Arbeiten benötigen die vorhandenen asynchronen Job-Werkzeuge.
### 2026.08.25.r024
- Container-Inspect liefert standardmäßig nur Status, Image, Netzwerkmodus und Ports; Umgebungsvariablennamen und Mounts sind nur noch über `detail=full` enthalten.
- Reduziert große MCP-Ausgaben bei gezielten Containerdiagnosen und hält Secrets weiterhin vollständig aus den Antworten heraus.
@@ -153,7 +157,7 @@ Das .txz enthält:
install/doinst.sh (läuft nach Installation)
===========================================
-->
<FILE Name="/boot/config/plugins/&name;/mua-2026.08.25.r024-x86_64-1.txz" Run="upgradepkg --install-new" Mode="755" Min="7.0.0">
<FILE Name="/boot/config/plugins/&name;/mua-2026.08.28.r025-x86_64-1.txz" Run="upgradepkg --install-new" Mode="755" Min="7.0.0">
<URL>&txzURL;</URL>
<SHA256>&txzSHA256;</SHA256>
</FILE>
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "mua",
"author": "Michael",
"version": "2026.08.25.r024",
"version": "2026.08.28.r025",
"minver": "7.0.0",
"pluginDirectory": "/usr/local/emhttp/plugins/mua",
"configDirectory": "/boot/config/plugins/mua",
+1 -1
View File
@@ -15,7 +15,7 @@ import { existsSync, mkdirSync, readFileSync, rmSync, statSync, writeFileSync }
// ── Konstanten ──────────────────────────────────────────────────────────
export const MUA_SERVER_NAME = "mua";
export const MUA_VERSION = "2026.08.25.r024";
export const MUA_VERSION = "2026.08.28.r025";
export const MUA_PROTOCOL_VERSION = "2025-03-26";
export const PHP_HELPER = "/usr/local/bin/unraid-docker-mcp-helper.php";
export const STATUS_HELPER = "/usr/local/bin/unraid-mcp-status-helper.php";
+18 -1
View File
@@ -1,7 +1,7 @@
import { describe, expect, test } from "bun:test";
import { parseConfig, SAFE_DEFAULT_TOOLS } from "./auth";
import { installCommunityApp, runReadOnlyCommand, sanitizeLogOutput } from "./helpers";
import { getToolRisk } from "./tools";
import { getToolRisk, toolByName } from "./tools";
describe("secure tool configuration", () => {
test("new and incomplete configs use the read-only baseline", () => {
@@ -81,6 +81,23 @@ describe("risk classification", () => {
});
});
describe("direct Unraid terminal", () => {
test("accepts a normal command with the extended timeout", async () => {
const tool = toolByName("unraid_system_shell");
expect(tool).toBeDefined();
const result = JSON.parse(await tool!.handler({ command: "printf ready", timeout_seconds: 301 }));
expect(result.exit_code).toBe(0);
expect(result.stdout).toBe("ready");
});
test("rejects excessive timeouts and oversized scripts", async () => {
const tool = toolByName("unraid_system_shell");
expect(tool).toBeDefined();
expect(() => tool!.handler({ command: "true", timeout_seconds: 1801 })).toThrow();
expect(() => tool!.handler({ command: "x".repeat(262_145) })).toThrow();
});
});
describe("read-only shell", () => {
test("executes an allowlisted program without a shell", async () => {
const result = JSON.parse(await runReadOnlyCommand("ls", ["-ld", "/"], 5));
+28 -12
View File
@@ -74,6 +74,22 @@ const int = (desc: string) => ({ type: "integer", description: desc });
const num = (desc: string) => ({ type: "number", description: desc });
const bool = (desc: string) => ({ type: "boolean", description: desc });
const empty = { type: "object", properties: {}, additionalProperties: false } as const;
const MAX_SHELL_COMMAND_CHARS = 262_144;
const shellCommand = (description: string) => ({
type: "string",
minLength: 1,
maxLength: MAX_SHELL_COMMAND_CHARS,
description,
});
function validateShellCommand(value: unknown): string {
const command = typeof value === "string" ? value : "";
if (command.trim() === "") throw new Error("command is required");
if (command.length > MAX_SHELL_COMMAND_CHARS) {
throw new Error(`command must not exceed ${MAX_SHELL_COMMAND_CHARS} characters`);
}
return command;
}
export const TOOLS: ToolDef[] = [
// ── Docker (14) ───────────────────────────────────────────────────────
@@ -634,23 +650,23 @@ export const TOOLS: ToolDef[] = [
{
name: "unraid_system_shell",
description:
"CRITICAL: Execute an unrestricted shell command on the Unraid host as root. Keep this tool disabled unless explicitly needed for a supervised maintenance session.",
"Direct terminal on the Unraid host as root. When the user explicitly asks to create or edit host files, run Docker commands, install software, or administer Unraid, use this tool directly. Do not inspect MUA internals and do not proxy the command through Hermes' local container terminal. Multi-line shell scripts are accepted. Commands may run for up to 30 minutes; use unraid_system_job_start only for work expected to take longer.",
inputSchema: {
type: "object",
properties: {
command: str(
"Shell command to execute on the Unraid host (run via /bin/sh -c)",
command: shellCommand(
"Command or multi-line shell script to execute directly on the Unraid host via /bin/sh -c (maximum 262144 characters)",
),
timeout_seconds: int("Timeout in seconds (1-300, default 60)"),
timeout_seconds: int("Timeout in seconds (1-1800, default 300)"),
},
required: ["command"],
additionalProperties: false,
},
handler: (a) => {
const command = (a["command"] as string) ?? "";
if (command.trim() === "") throw new Error("command is required");
const timeout = Number(a["timeout_seconds"] ?? 60);
if (timeout < 1 || timeout > 300) {
throw new Error("timeout_seconds must be between 1 and 300");
const command = validateShellCommand(a["command"]);
const timeout = Number(a["timeout_seconds"] ?? 300);
if (timeout < 1 || timeout > 1800) {
throw new Error("timeout_seconds must be between 1 and 1800");
}
return runShell(command, timeout);
},
@@ -658,16 +674,16 @@ export const TOOLS: ToolDef[] = [
{
name: "unraid_system_job_start",
description:
"CRITICAL: Start one explicitly authorized long-running unrestricted shell command on Unraid as an asynchronous job. Use this instead of unraid_system_shell when work may exceed an HTTP/tool timeout. Returns a job_id immediately; poll only with unraid_system_job_status and reserve calls for verification and cleanup.",
"Start a direct asynchronous terminal job on the Unraid host for work expected to exceed 30 minutes. For normal Docker, file and administration commands use unraid_system_shell instead. Accepts multi-line scripts and returns a job_id immediately.",
inputSchema: {
type: "object",
properties: {
command: str("Long-running shell command to execute on Unraid via /bin/sh"),
command: shellCommand("Long-running command or multi-line shell script to execute on Unraid via /bin/sh (maximum 262144 characters)"),
},
required: ["command"],
additionalProperties: false,
},
handler: (a) => startShellJob(String(a["command"] ?? "")),
handler: (a) => startShellJob(validateShellCommand(a["command"])),
},
{
name: "unraid_system_job_status",