From 9ae7d22618a66c113ec68cea8908c996e5d33bc3 Mon Sep 17 00:00:00 2001 From: Mikei386 <44135113+Mikei386@users.noreply.github.com> Date: Wed, 26 Aug 2026 11:42:39 +0200 Subject: [PATCH] fix MCPHub group publishing and recovery --- config/unraid-templates/my-MCPHub.xml | 2 +- dev/test_mcphub_settings.py | 13 ++++++++++ platform/hermes/install-profiles.sh | 2 +- platform/mcphub/configure-settings.py | 37 +++++++++++++++++++++++++++ platform/mcphub/mcphub_admin_mcp.py | 17 ++++++++++-- 5 files changed, 67 insertions(+), 4 deletions(-) diff --git a/config/unraid-templates/my-MCPHub.xml b/config/unraid-templates/my-MCPHub.xml index a543b19..61614e9 100644 --- a/config/unraid-templates/my-MCPHub.xml +++ b/config/unraid-templates/my-MCPHub.xml @@ -1,7 +1,7 @@ MCPHub - casaderoll/mcphub:1.2.3 + casaderoll/mcphub:1.2.4 https://hub.docker.com/r/samanhappy/mcphub bridge diff --git a/dev/test_mcphub_settings.py b/dev/test_mcphub_settings.py index fd99e6e..c4629e1 100644 --- a/dev/test_mcphub_settings.py +++ b/dev/test_mcphub_settings.py @@ -76,6 +76,19 @@ class MCPHubSettingsTests(unittest.TestCase): with self.assertRaises(SystemExit): self.module.registry_servers(self.registry, self.secrets, {}) + def test_hermes_group_is_recoverable_and_bounded(self): + settings = {"groups": [{"id": "keep", "name": "other", "servers": []}]} + self.module.ensure_hermes_group(settings) + self.module.ensure_hermes_group(settings) + groups = settings["groups"] + self.assertEqual(len([group for group in groups if group["name"] == "hermes"]), 1) + hermes = next(group for group in groups if group["name"] == "hermes") + fritzbox = next(item for item in hermes["servers"] if item["name"] == "fritzbox") + self.assertEqual( + fritzbox["tools"], + ["list_services", "list_actions", "describe_action", "call_action"], + ) + if __name__ == "__main__": unittest.main() diff --git a/platform/hermes/install-profiles.sh b/platform/hermes/install-profiles.sh index 22e5341..414e77d 100755 --- a/platform/hermes/install-profiles.sh +++ b/platform/hermes/install-profiles.sh @@ -95,7 +95,7 @@ docker run --rm --entrypoint python \ -v "$HERMES_DATA_DIR:/hermes:rw" \ -v "$mcphub_registry:/run/input/mcp-registry.json:ro" \ "${token_mount[@]}" \ - casaderoll/mcphub:1.2.3 \ + casaderoll/mcphub:1.2.4 \ /stack/platform/mcp/sync-clients.py "${sync_args[@]}" "$STACK_DIR/platform/hermes/install-skills.sh" diff --git a/platform/mcphub/configure-settings.py b/platform/mcphub/configure-settings.py index 08b902a..ef39c2a 100644 --- a/platform/mcphub/configure-settings.py +++ b/platform/mcphub/configure-settings.py @@ -82,6 +82,42 @@ def registry_servers(registry: pathlib.Path, secrets_dir: pathlib.Path, return result +def ensure_hermes_group(settings: dict[str, object]) -> None: + """Seed the bounded Hermes aggregate route on fresh installs/recovery.""" + server_names = [ + "mcphub-admin", "athena-operator", "github", "arr", "navidrome", + "deemix", "unraid", + ] + members: list[dict[str, object]] = [ + {"name": name, "tools": "all", "prompts": "all", "resources": "all"} + for name in server_names + ] + members.append({ + "name": "fritzbox", + "tools": ["list_services", "list_actions", "describe_action", "call_action"], + "prompts": [], + "resources": [], + }) + groups = settings.setdefault("groups", []) + if not isinstance(groups, list): + groups = [] + settings["groups"] = groups + current = next( + (group for group in groups + if isinstance(group, dict) and group.get("name") == "hermes"), + None, + ) + if current is None: + current = { + "id": str(uuid.uuid4()), + "owner": "system", + "name": "hermes", + } + groups.append(current) + current["description"] = "Gefilterte zentrale MCP-Auswahl für alle Hermes-Clients" + current["servers"] = members + + def main() -> None: parser = argparse.ArgumentParser() parser.add_argument("settings", type=pathlib.Path) @@ -114,6 +150,7 @@ def main() -> None: } settings["mcpServers"] = servers + ensure_hermes_group(settings) settings.setdefault("users", []) token_path = args.settings.parent / "client-token" keys = settings.setdefault("bearerKeys", []) diff --git a/platform/mcphub/mcphub_admin_mcp.py b/platform/mcphub/mcphub_admin_mcp.py index 725bea2..b6a4afb 100644 --- a/platform/mcphub/mcphub_admin_mcp.py +++ b/platform/mcphub/mcphub_admin_mcp.py @@ -149,10 +149,23 @@ def _install(name: str, config: dict[str, Any]) -> str: }, ensure_ascii=False) +def _group_id(name: str) -> str: + result = _request("GET", "/groups") + rows = result.get("data", []) if isinstance(result, dict) else [] + match = next( + (row for row in rows + if isinstance(row, dict) and (row.get("name") == name or row.get("id") == name)), + None, + ) + if not isinstance(match, dict) or not match.get("id"): + raise HubError(f"MCPHub group does not exist: {name}") + return str(match["id"]) + + def _add_to_group(name: str, tools: list[str] | None = None) -> Any: if not CLIENT_GROUP: return {"skipped": "no-client-group"} - group = urllib.parse.quote(CLIENT_GROUP, safe="") + group = urllib.parse.quote(_group_id(CLIENT_GROUP), safe="") configs = _request("GET", f"/groups/{group}/server-configs") rows = configs.get("data", []) if isinstance(configs, dict) else [] present = any(isinstance(row, dict) and row.get("name") == name for row in rows) @@ -238,7 +251,7 @@ def mcphub_admin_publish_to_hermes(name: str, tools: list[str] | None = None) -> def mcphub_admin_unpublish_from_hermes(name: str) -> str: """Remove one server from the central Hermes group without uninstalling or disabling the server itself.""" name = _name(name) - group = urllib.parse.quote(CLIENT_GROUP, safe="") + group = urllib.parse.quote(_group_id(CLIENT_GROUP), safe="") result = _request("DELETE", f"/groups/{group}/servers/{urllib.parse.quote(name, safe='')}") return json.dumps({"unpublished": name, "group": CLIENT_GROUP, "result": _redact(result)}, ensure_ascii=False)