r024: compact Docker inspect output
This commit is contained in:
BIN
Binary file not shown.
+16
-7
@@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+14
-3
@@ -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: {
|
||||||
|
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.",
|
||||||
|
},
|
||||||
|
},
|
||||||
required: ["container"],
|
required: ["container"],
|
||||||
|
additionalProperties: false,
|
||||||
},
|
},
|
||||||
handler: (a) => compactContainerInspect(validateName(a["container"], "container")),
|
handler: (a) => compactContainerInspect(
|
||||||
|
validateName(a["container"], "container"),
|
||||||
|
(a["detail"] as "summary" | "full" | undefined),
|
||||||
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "unraid_docker_logs",
|
name: "unraid_docker_logs",
|
||||||
|
|||||||
Reference in New Issue
Block a user