Files
AI-Profile-Router/platform/mcphub/configure-settings.py
T

164 lines
5.8 KiB
Python

#!/usr/bin/env python3
"""Replace demo MCPHub entries with CasaDeRoll's declarative server set.
The existing users, bearer keys, prompts and resources are preserved. Secret
values are read locally and written only to the runtime settings file.
"""
from __future__ import annotations
import argparse
import json
import os
import pathlib
import secrets
import tempfile
import uuid
def env_file(path: pathlib.Path) -> dict[str, str]:
values: dict[str, str] = {}
if not path.is_file():
return values
for raw in path.read_text(encoding="utf-8").splitlines():
line = raw.strip()
if not line or line.startswith("#") or "=" not in line:
continue
key, value = line.split("=", 1)
key, value = key.strip(), value.strip()
if key.startswith("export "):
key = key[7:].strip()
if len(value) >= 2 and value[0] == value[-1] and value[0] in "\"'":
value = value[1:-1]
values[key] = value
return values
def required(values: dict[str, str], key: str, source: pathlib.Path) -> str:
value = values.get(key, "").strip()
if not value:
raise SystemExit(f"{key} is missing in {source}")
return value
def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument("settings", type=pathlib.Path)
parser.add_argument("secrets", type=pathlib.Path)
parser.add_argument("--web-backend", default="", help="TinySearch MCP URL; empty keeps web disabled")
parser.add_argument("--searxng", default="", help="SearXNG base URL")
args = parser.parse_args()
settings = json.loads(args.settings.read_text(encoding="utf-8")) if args.settings.exists() else {}
ha_path = args.secrets / "homeassistant.env"
mua_path = args.secrets / "mua.env"
ha = env_file(ha_path)
mua = env_file(mua_path)
servers: dict[str, object] = {
"athena-operator": {
"type": "streamable-http",
"url": "http://192.168.1.212:8202/mcp",
"owner": "admin",
"enabled": True,
},
"arr": {
"type": "stdio",
"command": "/usr/local/bin/run-with-env",
"args": ["/run/secrets/mcphub/arr.env", "--", "arr-mcp", "--transport", "stdio", "--auth-type", "none"],
"enabled": True,
},
"deemix": {
"type": "stdio",
"command": "/usr/local/bin/run-with-env",
"args": ["/run/secrets/mcphub/deemix.env", "--", "python3", "/opt/casaderoll/mcps/deemix_mcp.py"],
"env": {"MCP_TRANSPORT": "stdio"},
"enabled": True,
},
"navidrome": {
"type": "stdio",
"command": "/usr/local/bin/run-with-env",
"args": ["/run/secrets/mcphub/navidrome.env", "--", "node", "/opt/casaderoll/navidrome/dist/index.js"],
"env": {"MCP_TRANSPORT": "stdio", "MCP_HTTP_EXPOSE": "false", "WEBUI_ENABLED": "false"},
"enabled": True,
},
"github": {
"type": "stdio",
"command": "/usr/local/bin/run-with-env",
"args": ["/run/secrets/mcphub/github.env", "--", "/usr/local/bin/github-mcp-server", "stdio", "--read-only", "--tools", "search_repositories,get_file_contents,search_code"],
"enabled": True,
},
"homeassistant": {
"type": "streamable-http",
"url": required(ha, "HASS_URL", ha_path).rstrip("/") + "/api/hass_mcp",
"headers": {"Authorization": "Bearer " + required(ha, "HASS_TOKEN", ha_path)},
"owner": "admin",
"enabled": True,
},
"unraid": {
"type": "streamable-http",
"url": required(mua, "MUA_MCP_URL", mua_path),
"headers": {"Authorization": "Bearer " + required(mua, "MUA_MCP_BEARER_TOKEN", mua_path)},
"owner": "admin",
"enabled": True,
},
}
if args.web_backend:
servers["web"] = {
"type": "stdio",
"command": "python3",
"args": ["/opt/casaderoll/mcps/web_search_mcp.py"],
"env": {
"TINYSEARCH_MCP_URL": args.web_backend,
"SEARXNG_URL": args.searxng,
},
"enabled": True,
}
settings["mcpServers"] = servers
settings.setdefault("users", [])
token_path = args.settings.parent / "client-token"
keys = settings.setdefault("bearerKeys", [])
client_key = next((item for item in keys if item.get("name") == "casaderoll-clients"), None)
if client_key is None:
token = secrets.token_urlsafe(48)
client_key = {
"id": str(uuid.uuid4()),
"name": "casaderoll-clients",
"token": token,
"enabled": True,
"kind": "system",
"accessType": "all",
"allowedGroups": [],
"allowedServers": [],
}
keys.append(client_key)
else:
token = str(client_key["token"])
client_key["enabled"] = True
client_key["accessType"] = "all"
token_path.write_text(token + "\n", encoding="utf-8")
os.chmod(token_path, 0o600)
settings.setdefault("prompts", [])
settings.setdefault("resources", [])
system = settings.setdefault("systemConfig", {})
system.setdefault("routing", {})["skipAuth"] = False
args.settings.parent.mkdir(parents=True, exist_ok=True)
fd, temporary = tempfile.mkstemp(prefix=".mcp-settings-", dir=args.settings.parent)
try:
with os.fdopen(fd, "w", encoding="utf-8") as handle:
json.dump(settings, handle, indent=2, ensure_ascii=False)
handle.write("\n")
os.chmod(temporary, 0o600)
os.replace(temporary, args.settings)
finally:
if os.path.exists(temporary):
os.unlink(temporary)
print("MCPHUB_SETTINGS_CONFIGURED")
if __name__ == "__main__":
main()