a8da306ce5
container_stats(container_name) calls SYNO.Docker.Container/stats, locates the entry by name (stripping the DSM-added leading slash), and reports: - CPU % (standard Docker formula: cpu_delta / system_delta * cpus * 100) - Memory used / limit (human-readable) - Network I/O rx / tx (summed across all interfaces) - Block I/O read / write (from io_service_bytes_recursive) Gracefully handles first-poll (precpu system_cpu_usage absent → 0%). 7 unit tests covering: found, CPU formula, memory format, slash-strip, not-found, API error, no-precpu fallback. rename_container removed: DSM Container Manager offers no rename API. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
242 lines
7.9 KiB
Markdown
242 lines
7.9 KiB
Markdown
# mcp-synology-container
|
||
|
||
## Kontext
|
||
|
||
Dieses Projekt entwickelt und betreibt `mcp-synology-container` – einen MCP-Server
|
||
für die vollständige Verwaltung von Docker-Projekten auf einer Synology DiskStation
|
||
via Container Manager. Der MCP-Server ist in Claude Desktop aktiv verbunden.
|
||
|
||
---
|
||
|
||
## Infrastruktur
|
||
|
||
| | |
|
||
|---|---|
|
||
| **NAS** | `https://dsm.gecheckt.de` (Split-DNS, intern direkt aufgelöst) |
|
||
| **Compose-Pfade** | `/volume1/docker/<projektname>/` |
|
||
| **Gitea** | `https://gitea.gecheckt.de/marcus/mcp-synology-container` |
|
||
| **Lokaler Code** | `D:\Dev\Projects\mcp-synology-container` |
|
||
| **Sprache** | Python 3.12+, `uv`, MCP SDK, `httpx`, `keyring`, `click`, `rich` |
|
||
|
||
---
|
||
|
||
## Deploy-Workflow (nach jeder Code-Änderung)
|
||
|
||
```
|
||
1. Claude Code committet und pusht
|
||
2. uv tool install --reinstall git+https://gitea.gecheckt.de/marcus/mcp-synology-container.git
|
||
3. Claude Desktop neu starten (Tray-Icon → Quit → neu starten)
|
||
```
|
||
|
||
---
|
||
|
||
## Aktueller Stand
|
||
|
||
### Implementierte Tools (17)
|
||
|
||
| Kategorie | Tools |
|
||
|---|---|
|
||
| Projekte | `list_projects`, `get_project_status`, `start_project`, `stop_project`, `redeploy_project` |
|
||
| Container | `list_containers`, `get_container_status`, `get_container_logs`, `exec_in_container`, `container_stats` |
|
||
| Compose | `read_compose`, `update_compose`, `update_image_tag`, `update_env_var` |
|
||
| Images | `check_image_updates`, `list_images`, `delete_image` |
|
||
|
||
### Bekannte Bugs
|
||
|
||
- Container-Name wird manchmal mit Hash-Präfix zurückgegeben (z.B. `f93cb8b504f7_jenkins`)
|
||
→ Tritt auf wenn Service-Name in compose.yaml von `container_name` abweicht
|
||
- `redeploy_project` schlägt mit DSM 2101/1202 fehl bei falschem Projektstatus
|
||
→ Workaround: `stop_project` + `start_project` separat oder `docker compose` per SSH
|
||
|
||
---
|
||
|
||
## Roadmap (geplante Erweiterungen)
|
||
|
||
### Images
|
||
- `list_images` – alle lokalen Images mit Größe, Tag, Erstellungsdatum
|
||
- `delete_image` – nicht mehr benötigte Images löschen
|
||
- `pull_image` – Image manuell aus Registry ziehen
|
||
|
||
### Container
|
||
- ~~`container_stats`~~ – implementiert
|
||
- ~~`rename_container`~~ – entfällt (DSM bietet kein Container-Umbenennen)
|
||
|
||
### Netzwerke
|
||
- `list_networks` – alle Docker-Netzwerke auflisten
|
||
- `create_network` – neues Netzwerk anlegen
|
||
- `delete_network` – Netzwerk löschen
|
||
|
||
### Volumes
|
||
- `list_volumes` – alle Docker-Volumes auflisten
|
||
- `delete_volume` – verwaiste Volumes löschen
|
||
- `inspect_volume` – Volume-Details anzeigen
|
||
|
||
### System
|
||
- `system_df` – Docker Disk Usage (Images, Container, Volumes)
|
||
- `system_prune` – Aufräumen (dangling Images, gestoppte Container)
|
||
|
||
### Registries
|
||
- `list_registries` – konfigurierte Registries anzeigen
|
||
|
||
---
|
||
|
||
## NAS – Laufende Container (Stand April 2026)
|
||
|
||
| Container | Image | Status |
|
||
|---|---|---|
|
||
| `jenkins` | `jenkins/jenkins:2.558-jdk21` | running |
|
||
| `openwebui` | `ghcr.io/open-webui/open-webui:v0.8.10` | running |
|
||
| `frostiq-gitea` | `gitea/gitea:1.25.4` | running |
|
||
| `frostiq-wildfly` | `frostiq/wildfly:39.0.0.Final-jdk21-pg` | running |
|
||
| `bookstack` | `linuxserver/bookstack:latest` | running |
|
||
| `bookstack-mariadb` | `mariadb:11.8` | running |
|
||
| `homeassistant` | `homeassistant/home-assistant:stable` | running |
|
||
| `pgadmin` | `dpage/pgadmin4:latest` | running |
|
||
| `postgres17` | `postgres:17.7` | running |
|
||
| `synology_docviewer_1` | `synology/docviewer:1.3.0.0126` | stopped |
|
||
| `synology_docviewer_2` | `synology/docviewer:1.3.0.0126` | stopped |
|
||
|
||
---
|
||
|
||
## Claude Code – Implementierungsregeln
|
||
|
||
- Confirmation vor destruktiven Operationen: `stop_project`, `redeploy_project`,
|
||
`exec_in_container`, `update_image_tag`, `update_env_var`, `update_compose`
|
||
- Nach Compose-Änderungen: `redeploy_project` vorschlagen
|
||
- Fehlerbehandlung: DSM-Fehler als verständliche Meldung zurückgeben, keine Stacktraces
|
||
- Keine Secrets in stderr-Ausgaben
|
||
- Type Hints und Docstrings konsequent verwenden
|
||
- Formatter: `ruff format`, Linter: `ruff check`, Tests: `pytest`
|
||
- Alle Texte (Docstrings, Kommentare, README): Englisch
|
||
|
||
---
|
||
|
||
## Hintergrund
|
||
|
||
Marcus ist Senior Software Engineer (Java, Jakarta EE).
|
||
FrostIQ ist eines seiner größeren Projekte (Jenkins, WildFly, Gitea).
|
||
Präferenz: State-of-the-Art, Best Practices, saubere Architektur.
|
||
Automatisierung spart Zeit für die Familie. 🌱
|
||
|
||
---
|
||
|
||
## Aktueller Auftrag (Stand April 2026)
|
||
|
||
### Rollenteilung
|
||
- **Claude Code** – Implementierung + Unit Tests (pytest, gemockter DSM-Client)
|
||
- **Marcus** – Deploy-Workflow (commit → push → install → Desktop-Neustart)
|
||
- **Claude Chat** – Produkttest live gegen die NAS nach jedem Deploy
|
||
|
||
### Implementierungsreihenfolge
|
||
|
||
Implementiere **eine Gruppe nach der anderen**. Commit + Push nach jeder Gruppe, dann Marcus Bescheid geben.
|
||
|
||
---
|
||
|
||
#### Gruppe 1 – Images `modules/images.py` ✦ Prio: hoch
|
||
|
||
**`list_images`**
|
||
- DSM API: `SYNO.Docker.Image`, method `list`
|
||
- Ausgabe: Name:Tag, Größe (human-readable), Erstellungsdatum, ob aktuell von einem Container verwendet
|
||
- Sortierung: Größe absteigend
|
||
- Confirmation: nein
|
||
|
||
**`delete_image`**
|
||
- Signatur: `delete_image(image_id: str, confirmed: bool = False) -> str`
|
||
- `image_id` akzeptiert `name:tag` und Image-Hash
|
||
- DSM API: `SYNO.Docker.Image`, method `delete`
|
||
- Ohne `confirmed=True`: nur Preview, nicht löschen
|
||
- Fehler wenn Image von laufendem Container verwendet → klare Meldung, kein Stacktrace
|
||
- Erfolg: `Deleted nouchka/sqlite3:latest – 76 MiB freed`
|
||
- Confirmation: **ja**
|
||
|
||
---
|
||
|
||
#### Gruppe 2 – Container `modules/containers.py` ✦ Prio: mittel ✅ erledigt
|
||
|
||
**`container_stats`** ✅
|
||
- Signatur: `container_stats(container_name: str) -> str`
|
||
- Ausgabe: CPU %, RAM verwendet/limit, Netzwerk I/O, Block I/O
|
||
- Confirmation: nein
|
||
|
||
**`rename_container`** – entfällt (DSM bietet kein Container-Umbenennen)
|
||
|
||
---
|
||
|
||
#### Gruppe 3 – System `modules/system.py` (neu) ✦ Prio: hoch
|
||
|
||
**`system_df`**
|
||
- DSM API: `SYNO.Docker.System` oder Docker Engine `/system/df`
|
||
- Ausgabe: Images (Anzahl, Gesamtgröße, reclaimable), Container, Volumes – tabellarisch
|
||
- Confirmation: nein
|
||
|
||
**`system_prune`**
|
||
- Signatur: `system_prune(confirmed: bool = False) -> str`
|
||
- Löscht: dangling Images, gestoppte Container, ungenutzte Netzwerke
|
||
- Ohne `confirmed=True`: zeigt was gelöscht würde
|
||
- Ausgabe: freigegebener Speicher
|
||
- Confirmation: **ja**
|
||
|
||
---
|
||
|
||
#### Gruppe 4 – Netzwerke `modules/networks.py` (neu) ✦ Prio: mittel
|
||
|
||
**`list_networks`**
|
||
- DSM API: `SYNO.Docker.Network`, method `list`
|
||
- Ausgabe: Name, Driver, Subnet, angebundene Container
|
||
- Confirmation: nein
|
||
|
||
**`create_network`**
|
||
- Signatur: `create_network(name: str, driver: str = "bridge", confirmed: bool = False) -> str`
|
||
- Confirmation: **ja**
|
||
|
||
**`delete_network`**
|
||
- Signatur: `delete_network(name: str, confirmed: bool = False) -> str`
|
||
- Fehler wenn Container angebunden → klare Meldung
|
||
- Confirmation: **ja**
|
||
|
||
---
|
||
|
||
#### Gruppe 5 – Volumes `modules/volumes.py` (neu) ✦ Prio: niedrig
|
||
|
||
**`list_volumes`**
|
||
- DSM API: `SYNO.Docker.Volume`, method `list`
|
||
- Ausgabe: Name, Mountpoint, Größe, ob in Verwendung
|
||
- Confirmation: nein
|
||
|
||
**`inspect_volume`**
|
||
- Signatur: `inspect_volume(volume_name: str) -> str`
|
||
- Confirmation: nein
|
||
|
||
**`delete_volume`**
|
||
- Signatur: `delete_volume(volume_name: str, confirmed: bool = False) -> str`
|
||
- Fehler wenn Volume gemountet → klare Meldung
|
||
- Confirmation: **ja**
|
||
|
||
---
|
||
|
||
#### Gruppe 6 – Images Ergänzung `modules/images.py` ✦ Prio: niedrig
|
||
|
||
**`pull_image`**
|
||
- Signatur: `pull_image(image: str) -> str` (z.B. `"postgres:17.8"`)
|
||
- DSM API: `SYNO.Docker.Image`, method `create` oder pull-Endpunkt
|
||
- Fortschritt streamen wenn möglich, sonst polling
|
||
- Confirmation: nein
|
||
|
||
---
|
||
|
||
#### Gruppe 7 – Registries `modules/registries.py` (neu) ✦ Prio: niedrig
|
||
|
||
**`list_registries`**
|
||
- DSM API: `SYNO.Docker.Registry`, method `list`
|
||
- Ausgabe: Name, URL, ob authentifiziert
|
||
- Confirmation: nein
|
||
|
||
---
|
||
|
||
### Referenz für DSM API-Calls
|
||
- `cmeans/mcp-synology` (GitHub) – Auth, Keyring, CLI-Struktur
|
||
- `N4S4/synology-api` `docker_api.py` (GitHub) – SYNO.Docker.* API-Calls
|
||
|
||
### Start
|
||
Beginne mit **Gruppe 1** (`list_images` + `delete_image`). |