Refactor: PHP-Built-in-Server durch TypeScript-Binary (Bun Runtime) ersetzt

- src/*.ts: Port von mcp/*.php (helpers, tools, server)
- Bun.serve() statt php -S — production-grade HTTP, POST funktioniert
- dist/mua-1.0.0-x86_64-1.txz: Slackware-Paket (Binary + rc.mua + mua.page + PHP-Helper)
- plugin/mua.plg: .txz-Installation via upgradepkg
- scripts/rc.mua: Binary statt php -S
- scripts/mua.page: Binary-Info statt PHP-Version
- scripts/package.sh: .txz-Builder

Fix: MCP-POST-Endpunkt lieferte leeren Body (php -S Limitation).
Bun.serve() löst das Problem.
This commit is contained in:
Mikei386
2026-08-18 08:02:39 +02:00
parent 3de8cb055a
commit 7e925153fc
12 changed files with 1305 additions and 69 deletions
+309
View File
@@ -0,0 +1,309 @@
/**
* MUA — Mikes Unraid Agent
* tools.ts — MCP Tool-Definitionen (21 Tools)
*
* Portiert von mcp/tools.php. Schema: unraid_<kategorie>_<aktion>
* Kategorien: docker (14), network (6), system (1).
*/
import {
containerRuntimeSummary,
compactContainerInspect,
dockerExec,
sanitizeLogOutput,
analyzeContainerLogs,
runPhpHelper,
compactNetworkInventory,
hostState,
auditAllTcpEndpoints,
probeDualstack,
connectionTest,
validateName,
} from "./helpers";
export interface ToolDef {
name: string;
description: string;
inputSchema: object;
handler: (args: Record<string, unknown>) => Promise<string>;
}
const str = (desc: string) => ({ type: "string", description: desc });
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;
export const TOOLS: ToolDef[] = [
// ── Docker (14) ───────────────────────────────────────────────────────
{
name: "unraid_docker_list",
description:
"List all Docker containers with runtime stats (CPU, memory, network I/O). Returns a compact JSON summary.",
inputSchema: empty,
handler: () => containerRuntimeSummary(),
},
{
name: "unraid_docker_inspect",
description:
"Inspect a single Docker container in detail (state, image, ports, env, mounts).",
inputSchema: {
type: "object",
properties: { container: str("Container name or ID") },
required: ["container"],
},
handler: (a) => compactContainerInspect(validateName(a["container"], "container")),
},
{
name: "unraid_docker_logs",
description: "Get recent logs from a Docker container (with timestamps).",
inputSchema: {
type: "object",
properties: {
container: str("Container name or ID"),
tail: int("Number of lines (1-2000, default 200)"),
},
required: ["container"],
},
handler: async (a) => {
const container = validateName(a["container"], "container");
const tail = Number(a["tail"] ?? 200);
if (tail < 1 || tail > 2000) throw new Error("tail must be between 1 and 2000");
const raw = await dockerExec(`logs --timestamps --tail ${tail} ${container}`, 60);
return sanitizeLogOutput(raw);
},
},
{
name: "unraid_docker_analyze_logs",
description:
"Analyze container logs server-side for errors/warnings. Returns pattern counts and sample matches.",
inputSchema: {
type: "object",
properties: {
severity: {
type: "string",
enum: ["error", "warn", "info"],
description: "Log severity to scan for",
},
container: str("Container name (optional, scans all if omitted)"),
since: str("Time filter (default 24h)"),
scan_tail: int("Max lines to scan (default 1000)"),
max_results: int("Max sample matches (default 50)"),
},
required: ["severity"],
},
handler: (a) => {
const severity = (a["severity"] as string) ?? "error";
const container = a["container"] as string | null | undefined;
if (container !== undefined && container !== null && typeof container !== "string") {
throw new Error("container must be a string");
}
const since = (a["since"] as string) ?? "24h";
const scanTail = Number(a["scan_tail"] ?? 1000);
const maxResults = Number(a["max_results"] ?? 50);
return analyzeContainerLogs(severity, container ?? null, since, scanTail, maxResults);
},
},
{
name: "unraid_docker_processes",
description: "List processes running inside a Docker container (docker top).",
inputSchema: {
type: "object",
properties: { container: str("Container name or ID") },
required: ["container"],
},
handler: (a) =>
dockerExec(
`top ${validateName(a["container"], "container")} -eo pid,ppid,user,stat,lstart,etime,args`,
),
},
{
name: "unraid_docker_stats",
description: "Get live CPU/memory/network/block I/O stats for all running containers.",
inputSchema: empty,
handler: () => dockerExec(`stats --no-stream --format '{{json .}}'`),
},
{
name: "unraid_docker_info",
description:
"Get Docker daemon information (version, storage driver, container counts, etc.).",
inputSchema: empty,
handler: () => dockerExec(`info --format '{{json .}}'`),
},
{
name: "unraid_docker_start",
description: "Start a Docker container.",
inputSchema: {
type: "object",
properties: { container: str("Container name or ID") },
required: ["container"],
},
handler: (a) => dockerExec(`start ${validateName(a["container"], "container")}`),
},
{
name: "unraid_docker_stop",
description: "Stop a Docker container.",
inputSchema: {
type: "object",
properties: { container: str("Container name or ID") },
required: ["container"],
},
handler: (a) => dockerExec(`stop ${validateName(a["container"], "container")}`),
},
{
name: "unraid_docker_restart",
description: "Restart a Docker container.",
inputSchema: {
type: "object",
properties: { container: str("Container name or ID") },
required: ["container"],
},
handler: (a) => dockerExec(`restart ${validateName(a["container"], "container")}`),
},
{
name: "unraid_docker_create",
description: "Create a Docker container from a template (pulls image, creates container).",
inputSchema: {
type: "object",
properties: {
template_name: str('Template name (e.g. "linuxserver/sonarr")'),
},
required: ["template_name"],
},
handler: (a) => runPhpHelper("create", validateName(a["template_name"], "template_name")),
},
{
name: "unraid_docker_modify",
description:
"Modify a container template (port, env, volume, network, privileged) and rebuild.",
inputSchema: {
type: "object",
properties: {
container: str("Container/template name"),
field: {
type: "string",
enum: ["port", "env", "volume", "network", "privileged"],
},
value: str("New value (format depends on field)"),
},
required: ["container", "field", "value"],
},
handler: (a) => {
const container = validateName(a["container"], "container");
const field = (a["field"] as string) ?? "";
const value = (a["value"] as string) ?? "";
if (!["port", "env", "volume", "network", "privileged"].includes(field)) {
throw new Error("field must be one of: port, env, volume, network, privileged");
}
return runPhpHelper("modify", container, field, value);
},
},
{
name: "unraid_docker_update",
description: "Update a container (pull latest image, rebuild).",
inputSchema: {
type: "object",
properties: { container: str("Container/template name") },
required: ["container"],
},
handler: (a) => runPhpHelper("update", validateName(a["container"], "container")),
},
{
name: "unraid_docker_rebuild",
description:
"Rebuild a container from its template (without pulling new image).",
inputSchema: {
type: "object",
properties: { container: str("Container/template name") },
required: ["container"],
},
handler: (a) => runPhpHelper("rebuild", validateName(a["container"], "container")),
},
// ── Netzwerk (6) ──────────────────────────────────────────────────────
{
name: "unraid_network_inventory",
description: "Compact Docker network inventory (all networks with container counts).",
inputSchema: empty,
handler: () => compactNetworkInventory(),
},
{
name: "unraid_network_list",
description: "List all Docker networks.",
inputSchema: empty,
handler: () => dockerExec(`network ls --no-trunc --format '{{json .}}'`),
},
{
name: "unraid_network_inspect",
description: "Inspect a Docker network in detail.",
inputSchema: {
type: "object",
properties: { network: str("Network name or ID") },
required: ["network"],
},
handler: (a) => dockerExec(`network inspect -- ${validateName(a["network"], "network")}`),
},
{
name: "unraid_network_host_state",
description:
"Get host network state (IPv4/IPv6 addresses, routes, listening sockets).",
inputSchema: empty,
handler: () => hostState(),
},
{
name: "unraid_network_audit_tcp",
description:
"Audit all TCP endpoints: probe IPv4/IPv6 reachability for every published port. Returns classification (dualstack/ipv4-only/ipv6-only/unreachable).",
inputSchema: {
type: "object",
properties: {
timeout_seconds: num("Probe timeout (0.2-10, default 2)"),
include_all_endpoints: bool("Include all endpoints (default false, only problems)"),
},
},
handler: (a) => {
const timeout = Number(a["timeout_seconds"] ?? 2);
if (timeout < 0.2 || timeout > 10) {
throw new Error("timeout_seconds must be between 0.2 and 10");
}
const includeAll = Boolean(a["include_all_endpoints"] ?? false);
return auditAllTcpEndpoints(timeout, includeAll);
},
},
{
name: "unraid_network_lan_probe",
description:
"Probe a specific host:port for IPv4 and IPv6 reachability (dualstack test).",
inputSchema: {
type: "object",
properties: {
host: str("Hostname or IP"),
port: int("Port (1-65535)"),
timeout_seconds: num("Timeout (0.2-10, default 3)"),
},
required: ["host", "port"],
},
handler: (a) => {
const host = validateName(a["host"], "host");
const port = Number(a["port"] ?? 0);
const timeout = Number(a["timeout_seconds"] ?? 3);
if (port < 1 || port > 65535 || timeout < 0.2 || timeout > 10) {
throw new Error("Invalid port or timeout");
}
return probeDualstack(host, port, timeout);
},
},
// ── System (1) ────────────────────────────────────────────────────────
{
name: "unraid_system_connection_test",
description:
"Test connection to the Unraid host (hostname, kernel, Unraid version).",
inputSchema: empty,
handler: () => connectionTest(),
},
];
export function toolByName(name: string): ToolDef | undefined {
return TOOLS.find((t) => t.name === name);
}