Add standalone Unraid ARR MCP package

This commit is contained in:
Mikei386
2026-08-27 21:49:01 +02:00
parent 588f24894d
commit d570a06911
10 changed files with 472 additions and 12 deletions
+37
View File
@@ -0,0 +1,37 @@
FROM python:3.13-slim AS builder
ARG ARR_MCP_VERSION=1.0.1
RUN python -m venv /opt/arr-mcp/venv \
&& /opt/arr-mcp/venv/bin/pip install --no-cache-dir --upgrade pip \
&& /opt/arr-mcp/venv/bin/pip install --no-cache-dir "arr-mcp[mcp]==${ARR_MCP_VERSION}"
COPY platform/mcp/patches/mcp_sonarr.py /tmp/mcp_sonarr.py
COPY platform/mcp/patches/mcp_radarr.py /tmp/mcp_radarr.py
RUN site_packages="$(/opt/arr-mcp/venv/bin/python -c 'import site; print(site.getsitepackages()[0])')" \
&& install -m 0644 /tmp/mcp_sonarr.py "$site_packages/arr_mcp/mcp/mcp_sonarr.py" \
&& install -m 0644 /tmp/mcp_radarr.py "$site_packages/arr_mcp/mcp/mcp_radarr.py" \
&& /opt/arr-mcp/venv/bin/python -m compileall -q "$site_packages/arr_mcp/mcp"
FROM python:3.13-slim
RUN groupadd --system --gid 10001 arrmcp \
&& useradd --system --uid 10001 --gid 10001 --no-create-home arrmcp
COPY --from=builder /opt/arr-mcp /opt/arr-mcp
COPY services/arr-mcp/run.sh /usr/local/bin/run-arr-mcp
ENV PATH="/opt/arr-mcp/venv/bin:${PATH}" \
PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1
RUN chmod 0755 /usr/local/bin/run-arr-mcp
USER 10001:10001
EXPOSE 8000
HEALTHCHECK --interval=30s --timeout=5s --start-period=20s --retries=3 \
CMD python -c "import socket; s=socket.create_connection(('127.0.0.1',8000),3); s.close()"
ENTRYPOINT ["/usr/local/bin/run-arr-mcp"]
+155
View File
@@ -0,0 +1,155 @@
# ARR-MCP auf Unraid
Dieser Ordner baut **einen eigenständigen Container nur für Sonarr und Radarr**.
Hermes, Pi Coding und andere MCP-Clients erreichen ihn anschließend über
`http://<UNRAID-IP>:8207/mcp`.
## Warum dieser Fork bleibt
Geprüft am 27. August 2026:
- [`Knuckles-Team/arr-mcp`](https://github.com/Knuckles-Team/arr-mcp) 2.1.0
ist der moderne Upstream unseres Pakets, stellt
in der kompakten Oberfläche aber weiterhin generische `*_action`-Werkzeuge
mit frei wählbaren API-Methoden bereit. Das führte bei lokalen Modellen zu
falschen Aktionen, unnötigen Schemaabfragen und großen Antworten.
- Andere öffentliche ARR-MCPs bieten teilweise mehr Dienste oder eine eigene
Weboberfläche, ersetzen aber nicht unser kompaktes Radarr-Codec-Inventar und
den an eine Vorschau gebundenen Sonarr-Freigabeablauf.
Wir bleiben deshalb vorläufig bei `arr-mcp` 1.0.1 plus zwei kleinen,
versionierten Patches. Ein späterer Wechsel ist sinnvoll, sobald ein Upstream
diese Eigenschaften ohne lokale Anpassungen anbietet.
## Besondere Werkzeuge
- `radarr_movie_codec_inventory`: kompakte, paginierte Liste mit Codec,
Auflösung, Sprachen und Dateigröße; keine riesigen Radarr-Rohantworten.
- `sonarr_action`: begrenzte, kompakte Sonarr-Aktionen wie `find_series`,
`get_season_summary` und `search_releases`.
- Sonarr-Schreibaktionen sind standardmäßig abgeschaltet. Im Schreibmodus
benötigen `start_episode_search` und `grab_release` zuerst eine passende
Vorschau, danach eine ausdrückliche Freigabe und ein kurzlebiges Ticket.
## Empfohlene Installation auf Unraid
Im vollständigen Checkout des Repositories als root:
```bash
./services/arr-mcp/install-on-unraid.sh
```
Beim ersten Aufruf entsteht:
```text
/mnt/nvme-storage/appdata/ARR-MCP/arr-mcp.env
```
Dort Sonarr- und Radarr-Schlüssel eintragen. Danach denselben Installationsbefehl
erneut ausführen. Das Skript:
1. baut das lokale Image `mike-ai/arr-mcp:1.0.0` aus `python:3.13-slim`,
2. startet ausschließlich den Container `ARR-MCP` im normalen Bridge-Netz,
3. wartet auf den Healthcheck und
4. installiert das DockerMan-Template für spätere Bedienung in der Unraid-GUI.
Es liest oder druckt keine Schlüssel. Eine vorhandene Konfiguration wird nie
überschrieben.
### Kurzer Auftrag für Hermes/Qwen
```text
Klone auf Unraid das Repository
ssh://git@192.168.1.2:33/michael/AI-Profile-Router.git, lies vollständig
services/arr-mcp/README.md und installiere ausschließlich nach dem dort
dokumentierten Weg. Verwende services/arr-mcp/install-on-unraid.sh. Zeige keine
API-Schlüssel an und überschreibe keine vorhandene arr-mcp.env. Stoppe, falls
Werte fehlen, und sage mir nur, welche Variablennamen ich eintragen muss.
Entferne den bisherigen ARR-Endpunkt erst nach einem erfolgreichen Healthcheck
und MCP-Handshake des neuen Containers.
```
## Installation in einem vorhandenen Debian-Slim-Container
Nur verwenden, wenn bereits bewusst ein nackter Debian-Slim-Container mit dem
kompletten Git-Checkout läuft:
```bash
./services/arr-mcp/install-in-debian-slim.sh
```
Danach startet `/usr/local/bin/run-arr-mcp` den Server. Dieser Weg funktioniert,
ist aber weniger reproduzierbar als der Dockerfile-Build: Eine Neuerstellung des
nackten Containers entfernt die Installation. Für den Produktivbetrieb deshalb
den ersten Weg verwenden.
## Hermes oder anderer MCP-Client
Direkte Registrierung:
```yaml
mcp_servers:
arr:
url: http://192.168.1.2:8207/mcp
timeout: 600
```
Nach dem Eintragen die MCP-Liste des Clients neu laden beziehungsweise einen
neuen Chat öffnen. Sonarr und Radarr erscheinen als Werkzeuge desselben
Fach-MCPs; sie sind nicht Bestandteil des Hermes-Containers.
## Schreiben aktivieren
In `arr-mcp.env`:
```text
ARR_MCP_WRITE=1
```
Danach nur diesen Container neu starten. Selbst dann erlaubt der Sonarr-Patch
nur die dokumentierten, ticketgebundenen Aktionen. Radarr besitzt derzeit noch
keinen gleichwertigen Freigabeablauf; Änderungen dort nur auf einen eindeutigen
Benutzerauftrag ausführen.
## Update und Test
Nach einem Git-Update denselben Befehl erneut ausführen:
```bash
./services/arr-mcp/install-on-unraid.sh
```
Status und Logs:
```bash
docker inspect --format '{{.State.Health.Status}}' ARR-MCP
docker logs --tail 100 ARR-MCP
```
Die Offline-Tests des Forks:
```bash
python3 dev/test_radarr_patch.py
python3 -m unittest dev/test_sonarr_release_grab.py
```
## Deinstallation
```bash
./services/arr-mcp/install-on-unraid.sh uninstall
```
Der Container verschwindet, während die Appdata-Konfiguration als Rückfall
erhalten bleibt. Erst wenn sie wirklich nicht mehr benötigt wird, kann
`/mnt/nvme-storage/appdata/ARR-MCP` separat gelöscht werden.
## Regeln für Hermes/Qwen
1. Diese README vollständig lesen.
2. Keine eigene ARR-Implementierung und keinen zweiten Sonarr-/Radarr-Dienst
erstellen.
3. Ausschließlich `install-on-unraid.sh` verwenden; keine Befehle improvisieren.
4. Vorhandene `arr-mcp.env` weder anzeigen noch überschreiben.
5. Nach Installation Healthcheck und MCP-Handshake prüfen.
6. Den alten MCPHub-/Athena-Endpunkt erst entfernen, wenn der neue Endpunkt
nachweislich funktioniert.
+28
View File
@@ -0,0 +1,28 @@
# Nur diese Datei kopieren und die beiden Schlüssel eintragen. Keine echten
# Zugangsdaten in Git committen.
SONARRTOOL=True
SONARR_BASE_URL=http://192.168.1.2:8989
SONARR_TOKEN=CHANGE_ME
RADARRTOOL=True
RADARR_BASE_URL=http://192.168.1.2:7878
RADARR_TOKEN=CHANGE_ME
# Andere *arr-Dienste sind in diesem Container absichtlich deaktiviert.
LIDARRTOOL=False
PROWLARRTOOL=False
BAZARRTOOL=False
SEERRTOOL=False
CHAPTARRTOOL=False
# Zwei kompakte Werkzeuge statt hunderter Einzeltools.
MCP_TOOL_MODE=condensed
# Standardmäßig rein lesend. Erst nach bewusster Entscheidung auf 1 setzen.
# Auch im Schreibmodus bleiben nur die eng begrenzten, ticketgebundenen
# Sonarr-Aktionen unseres Patches erlaubt.
ARR_MCP_WRITE=0
ARR_MCP_HOST=0.0.0.0
ARR_MCP_PORT=8000
+29
View File
@@ -0,0 +1,29 @@
services:
arr-mcp:
build:
context: ../..
dockerfile: services/arr-mcp/Dockerfile
args:
ARR_MCP_VERSION: "1.0.1"
image: mike-ai/arr-mcp:1.0.0
container_name: ARR-MCP
restart: unless-stopped
network_mode: bridge
env_file:
- ${ARR_MCP_ENV_FILE:-/mnt/nvme-storage/appdata/ARR-MCP/arr-mcp.env}
ports:
- "${ARR_MCP_PUBLIC_PORT:-8207}:8000"
read_only: true
tmpfs:
- /tmp:rw,noexec,nosuid,nodev,size=64m
cap_drop:
- ALL
security_opt:
- no-new-privileges:true
pids_limit: 256
healthcheck:
test: ["CMD", "python", "-c", "import socket; s=socket.create_connection(('127.0.0.1',8000),3); s.close()"]
interval: 30s
timeout: 5s
start_period: 20s
retries: 3
+47
View File
@@ -0,0 +1,47 @@
#!/usr/bin/env bash
# Installation innerhalb eines bereits laufenden Debian-Slim-Containers.
# Der empfohlene, reproduzierbare Weg bleibt der Dockerfile-Build.
set -Eeuo pipefail
[[ $EUID -eq 0 ]] || { echo "Bitte im Container als root ausführen." >&2; exit 1; }
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
SONARR_PATCH="$REPO_ROOT/platform/mcp/patches/mcp_sonarr.py"
RADARR_PATCH="$REPO_ROOT/platform/mcp/patches/mcp_radarr.py"
[[ -f $SONARR_PATCH && -f $RADARR_PATCH ]] || {
echo "Die Installation muss aus einem vollständigen Checkout dieses Repositories laufen." >&2
exit 2
}
need_python_packages=0
venv_check_dir="$(mktemp -d)"
if ! command -v python3 >/dev/null 2>&1 \
|| ! python3 -m venv "$venv_check_dir/check" >/dev/null 2>&1; then
need_python_packages=1
fi
rm -rf "$venv_check_dir"
if (( need_python_packages )); then
command -v apt-get >/dev/null 2>&1 || {
echo "Dieser Installer erwartet Debian/Ubuntu mit apt-get." >&2
exit 3
}
apt-get update
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
ca-certificates python3 python3-pip python3-venv
rm -rf /var/lib/apt/lists/*
fi
python3 -m venv /opt/arr-mcp/venv
/opt/arr-mcp/venv/bin/pip install --no-cache-dir --upgrade pip
/opt/arr-mcp/venv/bin/pip install --no-cache-dir "arr-mcp[mcp]==1.0.1"
site_packages="$(/opt/arr-mcp/venv/bin/python -c 'import site; print(site.getsitepackages()[0])')"
install -m 0644 "$SONARR_PATCH" "$site_packages/arr_mcp/mcp/mcp_sonarr.py"
install -m 0644 "$RADARR_PATCH" "$site_packages/arr_mcp/mcp/mcp_radarr.py"
install -m 0755 "$SCRIPT_DIR/run.sh" /usr/local/bin/run-arr-mcp
/opt/arr-mcp/venv/bin/python -m compileall -q "$site_packages/arr_mcp/mcp"
echo "ARR-MCP ist installiert. Start: /usr/local/bin/run-arr-mcp"
+86
View File
@@ -0,0 +1,86 @@
#!/usr/bin/env bash
# Baut und verwaltet ausschließlich den eigenständigen ARR-MCP-Container.
set -Eeuo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
APPDATA_DIR=${ARR_MCP_APPDATA_DIR:-/mnt/nvme-storage/appdata/ARR-MCP}
ENV_FILE=${ARR_MCP_ENV_FILE:-$APPDATA_DIR/arr-mcp.env}
PUBLIC_PORT=${ARR_MCP_PUBLIC_PORT:-8207}
TEMPLATE_SOURCE="$REPO_ROOT/config/unraid-templates/my-ARR-MCP.xml"
TEMPLATE_DEST=${ARR_MCP_TEMPLATE_DEST:-/boot/config/plugins/dockerMan/templates-user/my-ARR-MCP.xml}
usage() {
cat <<'EOF'
Verwendung:
./services/arr-mcp/install-on-unraid.sh
./services/arr-mcp/install-on-unraid.sh uninstall
Der erste Lauf legt nur dann eine Beispielkonfiguration an, wenn noch keine
existiert. Echte Schlüssel werden niemals überschrieben oder ausgegeben.
EOF
}
command=${1:-install}
case "$command" in
-h|--help) usage; exit 0 ;;
install|uninstall) ;;
*) usage >&2; exit 2 ;;
esac
command -v docker >/dev/null 2>&1 || { echo "Docker wurde nicht gefunden." >&2; exit 1; }
docker compose version >/dev/null 2>&1 || { echo "Docker Compose v2 wurde nicht gefunden." >&2; exit 1; }
compose=(docker compose -f "$SCRIPT_DIR/compose.yaml")
if [[ $command == uninstall ]]; then
ARR_MCP_ENV_FILE="$ENV_FILE" ARR_MCP_PUBLIC_PORT="$PUBLIC_PORT" \
"${compose[@]}" down --remove-orphans
echo "ARR-MCP wurde entfernt. Appdata und Konfiguration bleiben erhalten: $APPDATA_DIR"
exit 0
fi
install -d -m 0700 "$APPDATA_DIR"
if [[ ! -e $ENV_FILE ]]; then
install -m 0600 "$SCRIPT_DIR/arr-mcp.env.example" "$ENV_FILE"
echo "Beispielkonfiguration angelegt: $ENV_FILE"
echo "Bitte SONARR_TOKEN und RADARR_TOKEN eintragen und denselben Befehl erneut starten."
exit 3
fi
# Nur Vollständigkeit prüfen; Werte niemals ausgeben.
for key in SONARR_TOKEN RADARR_TOKEN; do
if ! grep -Eq "^${key}=[^[:space:]]+" "$ENV_FILE" \
|| grep -Eq "^${key}=CHANGE_ME$" "$ENV_FILE"; then
echo "In $ENV_FILE fehlt noch $key." >&2
exit 3
fi
done
docker build \
--build-arg ARR_MCP_VERSION=1.0.1 \
-f "$SCRIPT_DIR/Dockerfile" \
-t mike-ai/arr-mcp:1.0.0 \
"$REPO_ROOT"
ARR_MCP_ENV_FILE="$ENV_FILE" ARR_MCP_PUBLIC_PORT="$PUBLIC_PORT" \
"${compose[@]}" up -d --no-build
if [[ -d $(dirname "$TEMPLATE_DEST") ]]; then
install -m 0644 "$TEMPLATE_SOURCE" "$TEMPLATE_DEST"
fi
for _ in {1..30}; do
health=$(docker inspect --format '{{if .State.Health}}{{.State.Health.Status}}{{else}}{{.State.Status}}{{end}}' ARR-MCP 2>/dev/null || true)
[[ $health == healthy ]] && break
sleep 1
done
health=$(docker inspect --format '{{if .State.Health}}{{.State.Health.Status}}{{else}}{{.State.Status}}{{end}}' ARR-MCP 2>/dev/null || true)
[[ $health == healthy ]] || {
echo "ARR-MCP wurde gestartet, ist aber noch nicht healthy (Status: ${health:-unbekannt})." >&2
echo "Prüfen mit: docker logs --tail 100 ARR-MCP" >&2
exit 1
}
echo "ARR-MCP läuft: http://$(hostname -I | awk '{print $1}'):$PUBLIC_PORT/mcp"
+8
View File
@@ -0,0 +1,8 @@
#!/usr/bin/env sh
set -eu
exec /opt/arr-mcp/venv/bin/arr-mcp \
--transport streamable-http \
--host "${ARR_MCP_HOST:-0.0.0.0}" \
--port "${ARR_MCP_PORT:-8000}" \
--auth-type none