r024: compact Docker inspect output

This commit is contained in:
Mikei386 committed 2026-08-25 16:02:02 +02:00
1 parent 6876e495d6
commit 8977ced7cf
10 files changed
+85 -32

No files matched your search

Binary file not shown.
+16 -7
View File
@@ -197,7 +197,7 @@ function sanitize_log_output(string $text, int $max_chars = 50000): string {
/** /**
* Kompakte Container-Inspect-Ausgabe. * Kompakte Container-Inspect-Ausgabe.
*/ */
function compact_container_inspect(string $raw): string { function compact_container_inspect(string $raw, string $detail = 'summary'): string {
$data = json_decode($raw, true); $data = json_decode($raw, true);
if (json_last_error() !== JSON_ERROR_NONE || !is_array($data)) { if (json_last_error() !== JSON_ERROR_NONE || !is_array($data)) {
return $raw; return $raw;
@@ -218,17 +218,26 @@ function compact_container_inspect(string $raw): string {
'Image' => $data['Config']['Image'] ?? '', 'Image' => $data['Config']['Image'] ?? '',
'NetworkMode' => $data['HostConfig']['NetworkMode'] ?? '', 'NetworkMode' => $data['HostConfig']['NetworkMode'] ?? '',
'Ports' => $data['NetworkSettings']['Ports'] ?? [], 'Ports' => $data['NetworkSettings']['Ports'] ?? [],
'Env' => $data['Config']['Env'] ?? [], 'RestartCount' => $data['RestartCount'] ?? 0,
'Mounts' => array_map(function ($m) { 'Created' => $data['Created'] ?? '',
];
if ($detail === 'full') {
$compact['Env'] = array_map(function ($entry) {
if (!is_string($entry) || !str_contains($entry, '=')) return $entry;
[$name, $value] = explode('=', $entry, 2);
if (preg_match('/(KEY|TOKEN|SECRET|PASS|AUTH|COOKIE|ARL)/i', $name)) {
return $name . '=<redacted>';
}
return $name . '=' . $value;
}, $data['Config']['Env'] ?? []);
$compact['Mounts'] = array_map(function ($m) {
return [ return [
'Type' => $m['Type'] ?? '', 'Type' => $m['Type'] ?? '',
'Source' => $m['Source'] ?? '', 'Source' => $m['Source'] ?? '',
'Destination' => $m['Destination'] ?? '', 'Destination' => $m['Destination'] ?? '',
]; ];
}, $data['Mounts'] ?? []), }, $data['Mounts'] ?? []);
'RestartCount' => $data['RestartCount'] ?? 0, }
'Created' => $data['Created'] ?? '',
];
return json_encode($compact, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); return json_encode($compact, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
} }
+18
View File
@@ -68,6 +68,24 @@ try {
check('container_runtime_summary() funktioniert', false, $e->getMessage()); check('container_runtime_summary() funktioniert', false, $e->getMessage());
} }
// ── 3b. Kompakte Inspect-Ausgabe ─────────────────────────────────────────
$fixture = json_encode([[
'Id' => str_repeat('a', 64),
'Name' => '/test',
'State' => ['Status' => 'running', 'Running' => true, 'Pid' => 42, 'ExitCode' => 0],
'Config' => ['Image' => 'example:latest', 'Env' => ['SECRET=value']],
'HostConfig' => ['NetworkMode' => 'bridge'],
'NetworkSettings' => ['Ports' => []],
'Mounts' => [['Type' => 'bind', 'Source' => '/secret', 'Destination' => '/data']],
'RestartCount' => 0,
'Created' => 'now',
]]);
$summaryInspect = json_decode(compact_container_inspect($fixture), true);
$fullInspect = json_decode(compact_container_inspect($fixture, 'full'), true);
check('Inspect summary laesst Env aus', !isset($summaryInspect['Env']) && !isset($summaryInspect['Mounts']));
check('Inspect full enthaelt Env und Mounts', isset($fullInspect['Env']) && isset($fullInspect['Mounts']));
check('Inspect full maskiert Secrets', ($fullInspect['Env'][0] ?? '') === 'SECRET=<redacted>');
// ── 4. Netzwerk-Inventur ───────────────────────────────────────────────── // ── 4. Netzwerk-Inventur ─────────────────────────────────────────────────
echo "\n[4] Netzwerk-Inventur\n"; echo "\n[4] Netzwerk-Inventur\n";
try { try {
+5 -1
View File
@@ -95,8 +95,12 @@ function call_tool(string $name, array $args): string {
case 'unraid_docker_inspect': case 'unraid_docker_inspect':
$container = validate_name($args['container'] ?? null, 'container'); $container = validate_name($args['container'] ?? null, 'container');
$detail = $args['detail'] ?? 'summary';
if (!in_array($detail, ['summary', 'full'], true)) {
throw new InvalidArgumentException('detail must be summary or full');
}
$raw = docker_exec("inspect --type container -- $container"); $raw = docker_exec("inspect --type container -- $container");
return compact_container_inspect($raw); return compact_container_inspect($raw, $detail);
case 'unraid_docker_logs': case 'unraid_docker_logs':
$container = validate_name($args['container'] ?? null, 'container'); $container = validate_name($args['container'] ?? null, 'container');
+3 -1
View File
@@ -27,13 +27,15 @@ function mua_tools(): array {
], ],
[ [
'name' => 'unraid_docker_inspect', 'name' => 'unraid_docker_inspect',
'description' => 'Inspect a single Docker container in detail (state, image, ports, env, mounts).', 'description' => "Inspect one known Docker container directly. Default summary returns state, image, network mode and ports only; request detail='full' only when environment variables or mounts are required.",
'inputSchema' => [ 'inputSchema' => [
'type' => 'object', 'type' => 'object',
'properties' => [ 'properties' => [
'container' => ['type' => 'string', 'description' => 'Container name or ID'], 'container' => ['type' => 'string', 'description' => 'Container name or ID'],
'detail' => ['type' => 'string', 'enum' => ['summary', 'full'], 'description' => "Use 'summary' by default. 'full' additionally returns redacted environment and mounts."],
], ],
'required' => ['container'], 'required' => ['container'],
'additionalProperties' => false,
], ],
], ],
[ [
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "mua", "name": "mua",
"version": "2026.08.24.r023", "version": "2026.08.25.r024",
"description": "Mikes Unraid Agent - MCP over HTTP (Streamable HTTP) for Unraid", "description": "Mikes Unraid Agent - MCP over HTTP (Streamable HTTP) for Unraid",
"type": "module", "type": "module",
"main": "src/index.ts", "main": "src/index.ts",
+8 -4
View File
@@ -2,13 +2,13 @@
<!DOCTYPE PLUGIN [ <!DOCTYPE PLUGIN [
<!ENTITY name "mua"> <!ENTITY name "mua">
<!ENTITY author "Michael"> <!ENTITY author "Michael">
<!ENTITY version "2026.08.24.r023"> <!ENTITY version "2026.08.25.r024">
<!ENTITY launch "Settings/mua"> <!ENTITY launch "Settings/mua">
<!ENTITY pluginURL "http://192.168.1.2:4000/michael/MUA-Mikes-Unraid-Agent/raw/branch/main/plugin/mua.plg"> <!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 pluginLOC "/boot/config/plugins/&name;">
<!ENTITY emhttpLOC "/usr/local/emhttp/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.24.r023-x86_64-1.txz"> <!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 "acc41140d7fdc9d720843a50051e7a018767225f4165525f70fe83ff297a73a7"> <!ENTITY txzSHA256 "f8101b3d24fc9ebb02ce01b6f6481281578910f7a0cbe11b05bc484387ae1a90">
]> ]>
<PLUGIN name="&name;" <PLUGIN name="&name;"
@@ -23,6 +23,10 @@
> >
<CHANGES> <CHANGES>
### 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.
### 2026.08.24.r023 ### 2026.08.24.r023
- Begrenzt die Ausgabe der Nur-Lese-Shell bereits serverseitig auf standardmäßig 12.000 Zeichen und erlaubt ein explizites Limit von 1.000 bis 30.000 Zeichen. - Begrenzt die Ausgabe der Nur-Lese-Shell bereits serverseitig auf standardmäßig 12.000 Zeichen und erlaubt ein explizites Limit von 1.000 bis 30.000 Zeichen.
- Präzisiert die Werkzeugbeschreibung für eine zielgerichtete Diagnosekette statt breiter Konfigurations- und Verzeichnisabfragen. - Präzisiert die Werkzeugbeschreibung für eine zielgerichtete Diagnosekette statt breiter Konfigurations- und Verzeichnisabfragen.
@@ -149,7 +153,7 @@ Das .txz enthält:
install/doinst.sh (läuft nach Installation) install/doinst.sh (läuft nach Installation)
=========================================== ===========================================
--> -->
<FILE Name="/boot/config/plugins/&name;/mua-2026.08.24.r023-x86_64-1.txz" Run="upgradepkg --install-new" Mode="755" Min="7.0.0"> <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">
<URL>&txzURL;</URL> <URL>&txzURL;</URL>
<SHA256>&txzSHA256;</SHA256> <SHA256>&txzSHA256;</SHA256>
</FILE> </FILE>
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"name": "mua", "name": "mua",
"author": "Michael", "author": "Michael",
"version": "2026.08.24.r023", "version": "2026.08.25.r024",
"minver": "7.0.0", "minver": "7.0.0",
"pluginDirectory": "/usr/local/emhttp/plugins/mua", "pluginDirectory": "/usr/local/emhttp/plugins/mua",
"configDirectory": "/boot/config/plugins/mua", "configDirectory": "/boot/config/plugins/mua",
+18 -13
View File
@@ -15,7 +15,7 @@ import { existsSync, mkdirSync, readFileSync, rmSync, statSync, writeFileSync }
// ── Konstanten ────────────────────────────────────────────────────────── // ── Konstanten ──────────────────────────────────────────────────────────
export const MUA_SERVER_NAME = "mua"; export const MUA_SERVER_NAME = "mua";
export const MUA_VERSION = "2026.08.24.r023"; export const MUA_VERSION = "2026.08.25.r024";
export const MUA_PROTOCOL_VERSION = "2025-03-26"; export const MUA_PROTOCOL_VERSION = "2025-03-26";
export const PHP_HELPER = "/usr/local/bin/unraid-docker-mcp-helper.php"; export const PHP_HELPER = "/usr/local/bin/unraid-docker-mcp-helper.php";
export const STATUS_HELPER = "/usr/local/bin/unraid-mcp-status-helper.php"; export const STATUS_HELPER = "/usr/local/bin/unraid-mcp-status-helper.php";
@@ -475,7 +475,10 @@ export function sanitizeLogOutput(text: string, maxChars = 50000): string {
} }
// ── Compact Container Inspect ─────────────────────────────────────────── // ── Compact Container Inspect ───────────────────────────────────────────
export async function compactContainerInspect(container: string): Promise<string> { export async function compactContainerInspect(
container: string,
detail: "summary" | "full" = "summary",
): Promise<string> {
const raw = await dockerExec(`inspect --type container -- ${container}`); const raw = await dockerExec(`inspect --type container -- ${container}`);
let data: unknown; let data: unknown;
try { try {
@@ -485,7 +488,7 @@ export async function compactContainerInspect(container: string): Promise<string
} }
if (Array.isArray(data)) data = data[0]; if (Array.isArray(data)) data = data[0];
const d = data as Record<string, any>; const d = data as Record<string, any>;
const compact = { const compact: Record<string, unknown> = {
Id: (d.Id ?? "").slice(0, 12), Id: (d.Id ?? "").slice(0, 12),
Name: d.Name ?? "", Name: d.Name ?? "",
State: { State: {
@@ -497,19 +500,21 @@ export async function compactContainerInspect(container: string): Promise<string
Image: d.Config?.Image ?? "", Image: d.Config?.Image ?? "",
NetworkMode: d.HostConfig?.NetworkMode ?? "", NetworkMode: d.HostConfig?.NetworkMode ?? "",
Ports: d.NetworkSettings?.Ports ?? [], Ports: d.NetworkSettings?.Ports ?? [],
// Environment-Werte enthalten sehr häufig API-Keys, Passwörter und
// interne URLs. Für Diagnosezwecke reichen die vorhandenen Variablennamen.
EnvNames: (d.Config?.Env ?? []).map((entry: unknown) =>
typeof entry === "string" ? entry.split("=", 1)[0] : "",
).filter((name: string) => name !== ""),
Mounts: (d.Mounts ?? []).map((m: any) => ({
Type: m.Type ?? "",
Source: m.Source ?? "",
Destination: m.Destination ?? "",
})),
RestartCount: d.RestartCount ?? 0, RestartCount: d.RestartCount ?? 0,
Created: d.Created ?? "", Created: d.Created ?? "",
}; };
if (detail === "full") {
// Environment-Werte enthalten sehr häufig API-Keys, Passwörter und
// interne URLs. Für Diagnosezwecke reichen die Variablennamen.
compact.EnvNames = (d.Config?.Env ?? []).map((entry: unknown) =>
typeof entry === "string" ? entry.split("=", 1)[0] : "",
).filter((name: string) => name !== "");
compact.Mounts = (d.Mounts ?? []).map((m: any) => ({
Type: m.Type ?? "",
Source: m.Source ?? "",
Destination: m.Destination ?? "",
}));
}
return JSON.stringify(compact); return JSON.stringify(compact);
} }
+15 -4
View File
@@ -107,13 +107,24 @@ export const TOOLS: ToolDef[] = [
{ {
name: "unraid_docker_inspect", name: "unraid_docker_inspect",
description: description:
"Inspect a single Docker container in detail (state, image, ports, redacted environment variable names, mounts). Secret values are never returned.", "Inspect one known Docker container directly. Default summary returns state, image, network mode and ports only. Use detail='full' only when redacted environment variable names or mounts are required. Secret values are never returned.",
inputSchema: { inputSchema: {
type: "object", type: "object",
properties: { container: str("Container name or ID") }, properties: {
required: ["container"], container: str("Container name or ID"),
detail: {
type: "string",
enum: ["summary", "full"],
description: "summary is compact; full additionally returns environment variable names and mounts. Default: summary.",
}, },
handler: (a) => compactContainerInspect(validateName(a["container"], "container")), },
required: ["container"],
additionalProperties: false,
},
handler: (a) => compactContainerInspect(
validateName(a["container"], "container"),
(a["detail"] as "summary" | "full" | undefined),
),
}, },
{ {
name: "unraid_docker_logs", name: "unraid_docker_logs",