From 9ccb23cc5ee77210bfea576ada03ddad49989d05 Mon Sep 17 00:00:00 2001 From: Mikei386 <44135113+Mikei386@users.noreply.github.com> Date: Sun, 23 Aug 2026 15:06:32 +0200 Subject: [PATCH] Add isolated Navidrome MCP service --- compose.yaml | 2 +- config/navidrome-mcp.env.example | 5 +++ docs/ARCHITECTURE.md | 2 ++ docs/COMPONENTS.md | 1 + docs/INSTALLATION.md | 1 + platform/mcp/README.md | 28 +++++++++++++++++ platform/mcp/compose.yaml | 23 ++++++++++++++ platform/mcp/install-tools.sh | 5 +++ platform/openwebui/install-filters.sh | 44 +++++++++++++++++++++++++-- 9 files changed, 108 insertions(+), 3 deletions(-) create mode 100644 config/navidrome-mcp.env.example diff --git a/compose.yaml b/compose.yaml index ddc38af..03b0be4 100644 --- a/compose.yaml +++ b/compose.yaml @@ -747,7 +747,7 @@ services: # Seed native MCP connections on a fresh Open WebUI database. Secrets # stay inside the tool containers, so these internal URLs need no keys. TOOL_SERVER_CONNECTIONS: >- - [{"url":"http://mike-ai-mcp-web:8000/mcp","path":"","type":"mcp","auth_type":"none","headers":null,"key":"","config":{"enable":true,"access_grants":[]},"info":{"id":"web-local","name":"Web (öffentlich, read-only)","description":"Für aktuelle öffentliche Internetdaten, Quellenprüfung, GitHub/Hugging Face und Produktsuche. Nicht für Home Assistant, Medienverwaltung oder NAS-Diagnose."}},{"url":"http://mike-ai-mcp-homeassistant:8000/mcp","path":"","type":"mcp","auth_type":"none","headers":null,"key":"","config":{"enable":true,"access_grants":[]},"info":{"id":"homeassistant-local","name":"Home Assistant (lokal)","description":"Für Home-Assistant-Entitäten, Zustände, Historie, Automationen, Dashboards, HA-Diagnose und freigegebene YAML-Dateien. YAML-Lesen ist begrenzt; Änderungen benötigen serverseitige Vorschau, explizite Freigabe, Sicherung und Validierung. Nicht für Unraid, Sonarr/Radarr oder allgemeine Websuche."}},{"url":"http://mike-ai-mcp-arr:8000/mcp","path":"","type":"mcp","auth_type":"none","headers":null,"key":"","config":{"enable":true,"access_grants":[]},"info":{"id":"arr-local","name":"Sonarr und Radarr (lokal)","description":"Nur für verwaltete Serien/Filme, fehlende Episoden, Queue und Suche über konfigurierte Indexer. Keine allgemeine Websuche; Schreibaktionen benötigen Vorschau und Freigabe."}},{"url":"http://mike-ai-mcp-unraid-official:8000/mcp","path":"","type":"mcp","auth_type":"none","headers":null,"key":"","config":{"enable":true,"access_grants":[]},"info":{"id":"unraid-readonly-local","name":"Unraid (Systemdiagnose)","description":"Nur für Unraid-Host, Array, Datenträger, Docker-Container, Shares, Netzwerk, UPS und Systemlogs. Nicht für Home Assistant oder Medieninhalte; Standardzugriff read-only."}}] + [{"url":"http://mike-ai-mcp-web:8000/mcp","path":"","type":"mcp","auth_type":"none","headers":null,"key":"","config":{"enable":true,"access_grants":[]},"info":{"id":"web-local","name":"Web (öffentlich, read-only)","description":"Für aktuelle öffentliche Internetdaten, Quellenprüfung, GitHub/Hugging Face und Produktsuche. Nicht für Home Assistant, Medienverwaltung oder NAS-Diagnose."}},{"url":"http://mike-ai-mcp-homeassistant:8000/mcp","path":"","type":"mcp","auth_type":"none","headers":null,"key":"","config":{"enable":true,"access_grants":[]},"info":{"id":"homeassistant-local","name":"Home Assistant (lokal)","description":"Für Home-Assistant-Entitäten, Zustände, Historie, Automationen, Dashboards, HA-Diagnose und freigegebene YAML-Dateien. YAML-Lesen ist begrenzt; Änderungen benötigen serverseitige Vorschau, explizite Freigabe, Sicherung und Validierung. Nicht für Unraid, Sonarr/Radarr oder allgemeine Websuche."}},{"url":"http://mike-ai-mcp-arr:8000/mcp","path":"","type":"mcp","auth_type":"none","headers":null,"key":"","config":{"enable":true,"access_grants":[]},"info":{"id":"arr-local","name":"Sonarr und Radarr (lokal)","description":"Nur für verwaltete Serien/Filme, fehlende Episoden, Queue und Suche über konfigurierte Indexer. Keine allgemeine Websuche; Schreibaktionen benötigen Vorschau und Freigabe."}},{"url":"http://mike-ai-mcp-navidrome:3000/mcp","path":"","type":"mcp","auth_type":"none","headers":null,"key":"","config":{"enable":true,"access_grants":[]},"info":{"id":"navidrome-local","name":"Navidrome (Musikbibliothek)","description":"Nur für die persönliche Navidrome-Musikbibliothek: Titel, Alben, Künstler, Playlists, Favoriten und Hörverlauf. Nicht für Sonarr/Radarr, allgemeine Websuche oder Audioausgabe auf dem KI-Host. Wegen des großen Werkzeugkatalogs nur bei Musikaufgaben aktivieren."}},{"url":"http://mike-ai-mcp-unraid-official:8000/mcp","path":"","type":"mcp","auth_type":"none","headers":null,"key":"","config":{"enable":true,"access_grants":[]},"info":{"id":"unraid-readonly-local","name":"Unraid (Systemdiagnose)","description":"Nur für Unraid-Host, Array, Datenträger, Docker-Container, Shares, Netzwerk, UPS und Systemlogs. Nicht für Home Assistant oder Medieninhalte; Standardzugriff read-only."}}] DO_NOT_TRACK: "true" SCARF_NO_ANALYTICS: "true" dns: ["${AI_DNS:-1.1.1.1}"] diff --git a/config/navidrome-mcp.env.example b/config/navidrome-mcp.env.example new file mode 100644 index 0000000..42fb305 --- /dev/null +++ b/config/navidrome-mcp.env.example @@ -0,0 +1,5 @@ +# Root-only deployment secret. Copy to /etc/mike-ai/navidrome-mcp.env, +# replace the two placeholders and chmod 600. Never commit the real file. +NAVIDROME_URL=http://192.168.1.2:4533 +NAVIDROME_USERNAME=REPLACE_WITH_DEDICATED_USER +NAVIDROME_PASSWORD=REPLACE_WITH_DEDICATED_PASSWORD diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index bef750f..e4bae36 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -145,6 +145,7 @@ Allzweck-MCP mit sämtlichen Zugangsdaten. Open WebUI ── internes Netz ───────────┬── web-mcp ├── home-assistant-mcp ├── arr-mcp + ├── navidrome-mcp └── unraid-mcp-read Hermes Agent ─ WireGuard ─┐ @@ -158,6 +159,7 @@ weitere MCP-Clients ──────┴── mcp-gateway (später) ── das | `home-assistant-mcp-write` | kontrollierte HA-Änderungen | Preview/Approval | | `arr-mcp-read` | Sonarr/Radarr-Status und Releasesuche | nur lesen | | `arr-mcp-write` | Suche fehlender Episoden über Sonarr-Indexer | Preview/Approval; Monitoring unverändert | +| `navidrome-mcp` | Musikbibliothek, Suche, Playlists, Favoriten, Hörverlauf | eigener Navidrome-Benutzer; gezielt aktivieren | | `unraid-mcp-read` | System-, Container- und begrenzte Logdiagnose | nur lesen | | `unraid-mcp-admin` | eng definierte Verwaltungsaktionen | bewusst aktivieren | | `sandbox-mcp` | temporäre Code- und Dateiarbeit | isolierter Arbeitsraum | diff --git a/docs/COMPONENTS.md b/docs/COMPONENTS.md index d0bf325..b50c54d 100644 --- a/docs/COMPONENTS.md +++ b/docs/COMPONENTS.md @@ -10,6 +10,7 @@ | Web-MCP-Fassade | `platform/web-search/web_search_mcp.py` | eigener Container | Kern | | Home-Assistant-MCP | HA-Endpunkt plus lokaler Relay | eigener optionaler Container | optional | | ARR-MCP | `arr-mcp` 1.0.1 plus dokumentierter Sonarr-Patch | eigener optionaler Container | optional | +| Navidrome-MCP | Blakeem/Navidrome-MCP 2.2.0, Image per OCI-Digest | eigener optionaler Container ohne mpv | optional | | Unraid-MCP | lokales `runraid`-Binary | eigener optionaler Container | optional | | Whisper | ggml-org/whisper.cpp | Service im Router-Deploy | optional | | XTTS-v2 | Coqui, offizielles CUDA-12.1-Image per Digest | RTX-3060-Container, Stimme `Annmarie Nele`, CPML | Kern | diff --git a/docs/INSTALLATION.md b/docs/INSTALLATION.md index c44f3cb..8ddfc25 100644 --- a/docs/INSTALLATION.md +++ b/docs/INSTALLATION.md @@ -161,6 +161,7 @@ vorhanden ist: ```text /etc/mike-ai/homeassistant-admin-mcp.env /etc/mike-ai/arr-mcp.env +/etc/mike-ai/navidrome-mcp.env /etc/mike-ai/runraid/.env /usr/local/bin/runraid Version 0.4.2 ``` diff --git a/platform/mcp/README.md b/platform/mcp/README.md index b4d87e2..4f00a18 100644 --- a/platform/mcp/README.md +++ b/platform/mcp/README.md @@ -13,6 +13,7 @@ Prompts heraus, verhindert den früher beobachteten Kontextverbrauch von über | `mcp-web` | `http://mike-ai-mcp-web:8000/mcp` | kompakte Websuche und Quellenvergleich | an | | `mcp-homeassistant` | `http://mike-ai-mcp-homeassistant:8000/mcp` | Relay zum nativen HA-MCP; Token bleibt serverseitig | Profil `homeassistant` | | `mcp-arr` | `http://mike-ai-mcp-arr:8000/mcp` | Sonarr/Radarr/Prowlarr mit serverseitiger Policy | Profil `arr` | +| `mcp-navidrome` | `http://mike-ai-mcp-navidrome:3000/mcp` | Navidrome-Bibliothek, Suche, Playlists, Favoriten und Hörverlauf | Profil `navidrome` | | `mcp-unraid-official` | `http://mike-ai-mcp-unraid-official:8000/mcp` | offizieller, read-only begrenzter Unraid-Zugang | Profil `unraid` | | `mcp-unraid-ssh` | `http://mike-ai-mcp-unraid-ssh:8000/mcp` | erweiterte Diagnose über einen erzwungenen SSH-Befehl | optional (`extended`) | @@ -55,6 +56,7 @@ passenden Server wählen: | Aktuelle öffentliche Informationen, Quellen, GitHub/Hugging Face, Produkte | Web | HA, ARR, Unraid | | Entitäten, Zustände, Historie, Automationen und Dashboards | Home Assistant | Web, Unraid | | Serien, Filme, fehlende Episoden und Indexer-Releases | Sonarr und Radarr | Web | +| Persönliche Musikbibliothek, Titel, Alben, Künstler und Playlists | Navidrome | Web, ARR | | Lesende NAS-, Docker-, Array-, Netzwerk- und Logdiagnose | Unraid (Systemdiagnose) | MUA | | Ausdrücklich benötigte MUA-Verwaltungsaktion | MUA | Unraid-Diagnose nicht parallel | @@ -95,6 +97,7 @@ Die lokale Installation benötigt die vorhandenen Secret-Dateien: ```text /etc/mike-ai/homeassistant-admin-mcp.env /etc/mike-ai/arr-mcp.env +/etc/mike-ai/navidrome-mcp.env /etc/mike-ai/runraid/.env ``` @@ -106,6 +109,31 @@ TinySearch speichert sein lokales Embedding-Modell in einem Docker-Volume. Nach einer Erstinstallation wird das Modell einmalig im Container mit `tinysearch setup` geladen. Das Volume bleibt bei Containerupdates erhalten. +## Navidrome + +Der Navidrome-MCP basiert auf `Blakeem/Navidrome-MCP` 2.2.0; das amd64-Image +ist per OCI-Digest festgeschrieben. Der Container veröffentlicht keinen +Host-Port, besitzt keinen Dateizugriff auf die Musikbibliothek und enthält +bewusst kein `mpv`. Er kann daher nicht auf Athena selbst Musik wiedergeben. + +Navidrome sollte einen eigenen normalen Benutzer `mcp` erhalten. Dessen +Zugangsdaten liegen ausschließlich in der root-only Datei +`/etc/mike-ai/navidrome-mcp.env`; die Vorlage steht unter +`config/navidrome-mcp.env.example`. Anschließend genügt: + +```bash +sudo install -m 0600 config/navidrome-mcp.env.example /etc/mike-ai/navidrome-mcp.env +sudoedit /etc/mike-ai/navidrome-mcp.env +sudo platform/mcp/install-tools.sh +sudo platform/openwebui/install-filters.sh +``` + +Der Upstream-Server stellt ohne Playback noch immer über 40 Werkzeuge bereit. +Darum wird Navidrome **nicht** als Standardwerkzeug an jedes Modellprofil +gehängt. Es wird in OpenWebUI nur für konkrete Musikaufgaben ausgewählt und +danach wieder ausgeschaltet. Schreibende Funktionen wie Playlist-Änderungen, +Favoriten und Bewertungen wirken unmittelbar im Konto des MCP-Benutzers. + ## Client-Auswahl Werkzeuge werden nicht pauschal an jedes Modell gehängt. Für Home-Assistant- diff --git a/platform/mcp/compose.yaml b/platform/mcp/compose.yaml index 175c6c9..ca9ebc9 100644 --- a/platform/mcp/compose.yaml +++ b/platform/mcp/compose.yaml @@ -111,6 +111,29 @@ services: - ${ARR_RADARR_PATCH:-./patches/mcp_radarr.py}:/usr/local/lib/python3.13/site-packages/arr_mcp/mcp/mcp_radarr.py:ro networks: [tools, egress] + mcp-navidrome: + <<: *tool-common + # Version and amd64 manifest are pinned. The image contains no mpv, so it + # cannot play audio on the headless AI host and does not expose playback + # controls. It talks to Navidrome only through its authenticated API. + image: ghcr.io/blakeem/navidrome-mcp:2.2.0@sha256:047f911a5a8f7cc8f185bb4d6e7ca6c435542edefff4694a00c2f718ab0ee7f5 + container_name: mike-ai-mcp-navidrome + profiles: [navidrome] + env_file: + - ${NAVIDROME_MCP_ENV_FILE:-/etc/mike-ai/navidrome-mcp.env} + environment: + MCP_TRANSPORT: http + MCP_HTTP_EXPOSE: "true" + MCP_HTTP_PORT: "3000" + # The endpoint is not published on the host. Host filtering still makes + # accidental access from any other Docker name fail closed. + MCP_HTTP_ALLOWED_HOSTS: "mike-ai-mcp-navidrome:3000,mike-ai-mcp-navidrome" + WEBUI_ENABLED: "false" + tmpfs: + - /tmp:rw,noexec,nosuid,nodev,size=64m + - /config:rw,noexec,nosuid,nodev,size=4m,mode=0700 + networks: [tools, egress] + mcp-unraid-official: <<: *tool-common image: debian:13-slim diff --git a/platform/mcp/install-tools.sh b/platform/mcp/install-tools.sh index 3ed143f..8ab11d4 100755 --- a/platform/mcp/install-tools.sh +++ b/platform/mcp/install-tools.sh @@ -28,6 +28,11 @@ if [[ -s /etc/mike-ai/arr-mcp.env ]]; then else echo "ARR bleibt aus: Secret-Datei fehlt." fi +if [[ -s /etc/mike-ai/navidrome-mcp.env ]]; then + profiles+=(--profile navidrome) +else + echo "Navidrome bleibt aus: Secret-Datei fehlt." +fi if [[ -s /etc/mike-ai/runraid/.env && -x /usr/local/bin/runraid ]]; then profiles+=(--profile unraid) else diff --git a/platform/openwebui/install-filters.sh b/platform/openwebui/install-filters.sh index bdd9d2c..2a4f028 100755 --- a/platform/openwebui/install-filters.sh +++ b/platform/openwebui/install-filters.sh @@ -35,14 +35,17 @@ backup=$volume_path/webui.db.before-filter-install-$stamp cp -a "$db" "$backup" rm -f "$volume_path/webui.db-wal" "$volume_path/webui.db-shm" -python3 - "$db" "$FILTER_DIR" "$ACTION_DIR" "${OPENWEBUI_FILTER_OWNER_ID:-}" <<'PY' +navidrome_enabled=false +[[ -s /etc/mike-ai/navidrome-mcp.env ]] && navidrome_enabled=true +python3 - "$db" "$FILTER_DIR" "$ACTION_DIR" "${OPENWEBUI_FILTER_OWNER_ID:-}" "$navidrome_enabled" <<'PY' import json import pathlib import sqlite3 import sys import time -db, filter_dir, action_dir, requested_owner = sys.argv[1:] +db, filter_dir, action_dir, requested_owner, navidrome_enabled_raw = sys.argv[1:] +navidrome_enabled = navidrome_enabled_raw.lower() == "true" con = sqlite3.connect(db) columns = {row[1] for row in con.execute("pragma table_info(function)")} required = { @@ -182,6 +185,13 @@ with con: "Verwaltungsaktionen. Für reine Statusabfragen und Diagnosen stattdessen " "Unraid (Systemdiagnose) verwenden; niemals beide parallel ausprobieren.", ), + "navidrome-local": ( + "Navidrome (Musikbibliothek)", + "Nur für die persönliche Navidrome-Musikbibliothek: Titel, Alben, Künstler, " + "Playlists, Favoriten und Hörverlauf. Nicht für Sonarr/Radarr, allgemeine " + "Websuche oder Audioausgabe auf dem KI-Host. Wegen des großen Werkzeugkatalogs " + "nur bei Musikaufgaben aktivieren.", + ), } changed = False for connection in connections: @@ -203,6 +213,8 @@ with con: match = "homeassistant-local" elif "mike-ai-mcp-arr" in url: match = "arr-local" + elif "mike-ai-mcp-navidrome" in url: + match = "navidrome-local" elif "mike-ai-mcp-unraid-official" in url: match = "unraid-readonly-local" else: @@ -212,6 +224,34 @@ with con: info["name"] = name info["description"] = description changed = True + if navidrome_enabled and not any( + isinstance(connection, dict) + and ( + str(connection.get("url", "")).lower() + == "http://mike-ai-mcp-navidrome:3000/mcp" + or str((connection.get("info") or {}).get("id", "")).lower() + == "navidrome-local" + ) + for connection in connections + ): + name, description = descriptions["navidrome-local"] + connections.append( + { + "url": "http://mike-ai-mcp-navidrome:3000/mcp", + "path": "", + "type": "mcp", + "auth_type": "none", + "headers": None, + "key": "", + "config": {"enable": True, "access_grants": []}, + "info": { + "id": "navidrome-local", + "name": name, + "description": description, + }, + } + ) + changed = True if changed: con.execute( """