release: r015 compact filtered Docker inventory

This commit is contained in:
Mikei386
2026-08-22 21:34:28 +02:00
parent 37de236f5a
commit c4c04ae7af
10 changed files with 136 additions and 34 deletions
+49 -9
View File
@@ -14,7 +14,7 @@ import { createHash, randomUUID } from "node:crypto";
// ── Konstanten ──────────────────────────────────────────────────────────
export const MUA_SERVER_NAME = "mua";
export const MUA_VERSION = "2026.08.21.r014";
export const MUA_VERSION = "2026.08.22.r015";
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";
@@ -428,33 +428,67 @@ export async function compactContainerInspect(container: string): Promise<string
}
// ── Container Runtime Summary ───────────────────────────────────────────
export async function containerRuntimeSummary(): Promise<string> {
export interface ContainerListOptions {
state?: "all" | "running" | "stopped" | "created" | "exited";
includeStats?: boolean;
detail?: "compact" | "full";
}
export async function containerRuntimeSummary(
options: ContainerListOptions = {},
): Promise<string> {
const stateFilter = options.state ?? "all";
const includeStats = options.includeStats ?? false;
const detail = options.detail ?? "compact";
const allowedStates = new Set(["all", "running", "stopped", "created", "exited"]);
if (!allowedStates.has(stateFilter)) {
throw new Error("state must be one of: all, running, stopped, created, exited");
}
if (detail !== "compact" && detail !== "full") {
throw new Error("detail must be compact or full");
}
const ps = await dockerExec(`ps -a --format '{{json .}}'`);
const containers = jsonLines(ps);
const runningIds = containers
.filter((c) => c["State"] === "running")
.map((c) => (c["ID"] as string) ?? "")
.filter((id) => id !== "");
const stats: Record<string, Record<string, unknown>> = {};
if (runningIds.length > 0) {
if (includeStats && runningIds.length > 0) {
const statsRaw = await dockerExec(`stats --no-stream --format '{{json .}}'`);
for (const s of jsonLines(statsRaw)) {
stats[(s["ID"] as string) ?? ""] = s;
}
}
const result = containers.map((c) => {
const stateCounts: Record<string, number> = {};
for (const c of containers) {
const state = String(c["State"] ?? "unknown");
stateCounts[state] = (stateCounts[state] ?? 0) + 1;
}
const matchesState = (c: Record<string, unknown>) => {
const state = String(c["State"] ?? "");
if (stateFilter === "all") return true;
if (stateFilter === "stopped") return state !== "running";
return state === stateFilter;
};
const result = containers.filter(matchesState).map((c) => {
const id = (c["ID"] as string) ?? "";
const entry: Record<string, unknown> = {
id: id.slice(0, 12),
name: c["Names"] ?? "",
image: c["Image"] ?? "",
status: c["Status"] ?? "",
state: c["State"] ?? "",
ports: c["Ports"] ?? "",
};
if (stats[id]) {
if (detail === "full") {
entry.id = id.slice(0, 12);
entry.image = c["Image"] ?? "";
entry.ports = c["Ports"] ?? "";
}
if (includeStats && stats[id]) {
entry.cpu_percent = stats[id]["CPUPerc"] ?? "";
entry.mem_usage = stats[id]["MemUsage"] ?? "";
entry.mem_percent = stats[id]["MemPerc"] ?? "";
@@ -465,8 +499,14 @@ export async function containerRuntimeSummary(): Promise<string> {
});
return JSON.stringify({
schema_version: "1.0",
schema_version: "1.1",
container_count: result.length,
returned_count: result.length,
total_count: containers.length,
running_count: stateCounts["running"] ?? 0,
stopped_count: containers.length - (stateCounts["running"] ?? 0),
state_counts: stateCounts,
filter: { state: stateFilter, include_stats: includeStats, detail },
containers: result,
});
}
+23 -3
View File
@@ -74,9 +74,29 @@ export const TOOLS: ToolDef[] = [
{
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(),
"List Docker containers and return authoritative server-side counts. For questions about active/currently running containers, ALWAYS use state='running'. Defaults to a compact response without performance statistics to avoid large tool output. Use include_stats=true only when CPU, RAM or I/O values are explicitly needed.",
inputSchema: {
type: "object",
properties: {
state: {
type: "string",
enum: ["all", "running", "stopped", "created", "exited"],
description: "Filter by container state. Use 'running' for active/running containers; 'stopped' means every non-running container. Default: all.",
},
include_stats: bool("Include live CPU, RAM, network and block-I/O statistics. Expensive and verbose; default false."),
detail: {
type: "string",
enum: ["compact", "full"],
description: "compact returns name/status/state; full additionally returns ID, image and ports. Default: compact.",
},
},
additionalProperties: false,
},
handler: (a) => containerRuntimeSummary({
state: (a["state"] as "all" | "running" | "stopped" | "created" | "exited" | undefined),
includeStats: a["include_stats"] === true,
detail: (a["detail"] as "compact" | "full" | undefined),
}),
},
{
name: "unraid_docker_inspect",