release: r015 compact filtered Docker inventory
This commit is contained in:
+49
-9
@@ -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
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user