feat: add bounded file inventory for media audits
This commit is contained in:
+12
@@ -57,6 +57,7 @@ export const SAFE_DEFAULT_TOOLS = [
|
||||
"unraid_notifications_list",
|
||||
"unraid_shares_list",
|
||||
"unraid_share_inspect",
|
||||
"unraid_files_inventory",
|
||||
];
|
||||
|
||||
// ── Config laden ────────────────────────────────────────────────────────
|
||||
@@ -104,6 +105,17 @@ export function parseConfig(content: string): MUAConfig {
|
||||
) {
|
||||
cfg.enabledTools.push("unraid_docker_update_verified_batch");
|
||||
}
|
||||
// r020 adds a bounded metadata-only filesystem inventory. It is strictly
|
||||
// less powerful than the already-enabled read-only shell and avoids many
|
||||
// repeated ls/find calls, so existing read-only installations may receive
|
||||
// it without granting a new write capability.
|
||||
if (
|
||||
!cfg.allToolsEnabled
|
||||
&& cfg.enabledTools.includes("unraid_system_shell_readonly")
|
||||
&& !cfg.enabledTools.includes("unraid_files_inventory")
|
||||
) {
|
||||
cfg.enabledTools.push("unraid_files_inventory");
|
||||
}
|
||||
return cfg;
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -14,7 +14,7 @@ import { createHash, randomUUID } from "node:crypto";
|
||||
|
||||
// ── Konstanten ──────────────────────────────────────────────────────────
|
||||
export const MUA_SERVER_NAME = "mua";
|
||||
export const MUA_VERSION = "2026.08.24.r019";
|
||||
export const MUA_VERSION = "2026.08.24.r020";
|
||||
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";
|
||||
|
||||
@@ -30,6 +30,14 @@ describe("secure tool configuration", () => {
|
||||
expect(cfg.enabledTools).toContain("unraid_docker_update");
|
||||
expect(cfg.enabledTools).toContain("unraid_docker_update_verified_batch");
|
||||
});
|
||||
|
||||
test("an enabled read-only shell also exposes bounded file inventory", () => {
|
||||
const cfg = parseConfig(
|
||||
"MUA_API_KEY=test\nMUA_ENABLED_TOOLS=unraid_system_shell_readonly\n",
|
||||
);
|
||||
expect(cfg.enabledTools).toContain("unraid_files_inventory");
|
||||
expect(cfg.enabledTools).not.toContain("unraid_system_shell");
|
||||
});
|
||||
});
|
||||
|
||||
describe("secret handling", () => {
|
||||
@@ -53,6 +61,7 @@ describe("risk classification", () => {
|
||||
expect(getToolRisk("unraid_network_lan_probe")).toBe("active");
|
||||
expect(getToolRisk("unraid_docker_list")).toBe("read");
|
||||
expect(getToolRisk("unraid_system_shell_readonly")).toBe("read");
|
||||
expect(getToolRisk("unraid_files_inventory")).toBe("read");
|
||||
expect(getToolRisk("unraid_system_health")).toBe("read");
|
||||
expect(getToolRisk("unraid_ca_search")).toBe("active");
|
||||
expect(getToolRisk("unraid_ca_install_preview")).toBe("active");
|
||||
|
||||
@@ -533,6 +533,35 @@ export const TOOLS: ToolDef[] = [
|
||||
},
|
||||
handler: (a) => runStatusHelper("share-inspect", String(a["share"] ?? "")),
|
||||
},
|
||||
{
|
||||
name: "unraid_files_inventory",
|
||||
description:
|
||||
"Inventory file and directory names below one exact Unraid share in one bounded read-only call. Use this instead of repeated ls/find calls for media-library audits, missing-episode checks and locating a named collection. It never reads file contents. If name_contains matches a directory, that directory and its descendants are returned; unrelated trees are omitted.",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
share: str("Exact Unraid share name, for example Audiobooks"),
|
||||
relative_path: str("Optional path below the share; never use /mnt/user or an absolute path"),
|
||||
name_contains: str("Optional case-insensitive name fragment, for example 'drei'. Matching directories include their descendants"),
|
||||
max_depth: int("Maximum directory depth below relative_path (1-20, default 10)"),
|
||||
max_entries: int("Maximum returned entries (1-5000, default 2000)"),
|
||||
include_files: { type: "boolean", description: "Include files (default true)" },
|
||||
include_directories: { type: "boolean", description: "Include directories (default true)" },
|
||||
},
|
||||
required: ["share"],
|
||||
additionalProperties: false,
|
||||
},
|
||||
handler: (a) => runStatusHelper(
|
||||
"files-inventory",
|
||||
String(a["share"] ?? ""),
|
||||
String(a["relative_path"] ?? ""),
|
||||
String(a["name_contains"] ?? ""),
|
||||
String(Math.max(1, Math.min(20, Number(a["max_depth"] ?? 10)))),
|
||||
String(Math.max(1, Math.min(5000, Number(a["max_entries"] ?? 2000)))),
|
||||
a["include_files"] === false ? "0" : "1",
|
||||
a["include_directories"] === false ? "0" : "1",
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "unraid_system_connection_test",
|
||||
description:
|
||||
|
||||
Reference in New Issue
Block a user