Portainer 2.40 → 2.42.0 Bump + portainer-mcp projektspezifisch

Portainer-Server (pve LXC 400): 2.40.0 → 2.42.0 via Image-Swap mit Volume-Erhalt.
Pre-Upgrade-Snapshot: docker commit :2.40.0-preupgrade-20260828 (Rollback 7 d
verfügbar) + Settings/Stacks/Endpoints-JSON-Dump (40 KB, 31 Stacks, 4 Endpoints).
Instance-ID + API-Token nach Bump identisch — kein Re-Auth, kein Settings-Verlust.

portainer-mcp: offizielles PyPI-Paket mcp-portainer~=2.42.0 (193 Tools), nur
in bf-infra/opencode.jsonc, via uvx-stdio. Tool-Gate: get_guidance() einmal pro
Session zwingend vor erstem Read. Andere Projekte unberührt.

Korrekturen ggü. Handoff 2026-08-28 README:
- mcp-portainer PyPI bis 2.42.6, Docker-Hub portainer/portainer-ce nur bis 2.42.0
  (unterschiedliche Versionierungspfade).
- mcp/portainer/README.md ist auf 2.42.6 formuliert und sollte nachgezogen werden.

Bestand: mcp/INVENTORY.md portainer-mcp ⛔→🟢 + Header MCP-WARP-Direktive ergänzt.
.mcp.json (war getrackt aus Commit 3426366) entfernt: opencode nutzt opencode.jsonc,
nicht .mcp.json — Claude-Code-Konvention hier irrelevant.
This commit is contained in:
2026-08-28 21:17:52 +02:00
parent c25888acd9
commit 3562e19e47
7 changed files with 244 additions and 9 deletions

4
.gitignore vendored
View File

@@ -1,6 +1,10 @@
.DS_Store .DS_Store
.secrets .secrets
# Per-Projekt-MCP-Configs: können Tokens/URLs enthalten, projekt-spezifisch, nicht ins Repo
.mcp.json
# Python bytecode # Python bytecode
__pycache__/ __pycache__/
*.pyc *.pyc

View File

@@ -1,8 +0,0 @@
{
"mcpServers": {
"gitea-mcp": {
"type": "http",
"url": "https://gitea-mcp.emsenhuber.net/mcp"
}
}
}

View File

@@ -0,0 +1,146 @@
# Handoff 2026-08-28 — Portainer 2.40 → 2.42.0 Bump + portainer-mcp (uvx, projektebene)
> **Vorgeschichte:** Direktive vom 2026-08-28 (User): "wenn du künftig das ganze containermanagement machst,
> brauchst du einen MCP für Portainer". Vorgänger-Direktive vom 2026-08-28 selbst (gestern Session):
> ⛔ NICHT installieren. → **revidiert** in derselben Diskussion, weil die wiederholten curl-Aufrufe
> in der Inventur-Session gezeigt haben, dass ein MCP-Layer hier operativ hilft.
## Was diese Session geliefert hat
### 1. Portainer-Server 2.40.0 → 2.42.0 (auf pve LXC 400, Container `portainer`)
**Vorab-Korrektur (war falsch im Handoff 2026-08-28):** README schrieb 2.42.6 als Ziel,
PyPI hat ein 2.42.6-Wheel, aber Docker-Hub hat **nur 2.42.0** als letzten 2.42.x-Tag
für `portainer/portainer-ce`. Bump-Ziel deshalb **2.42.0** statt 2.42.6.
**Ablauf:**
1. Pre-Upgrade-Snapshot:
- `docker commit portainer portainer/portainer-ce:2.40.0-preupgrade-20260828` (186 MB)
- Settings+Endpoints+Stacks-JSON-Dump (40 KB, 31 Stacks, 4 Endpoints)
- Volume `/var/lib/docker/volumes/portainer_data/_data` ist **persistent**, bleibt
beim Image-Swap unangetastet.
- Runflags aus `docker inspect` ins Runbook übernommen.
2. `docker pull portainer/portainer-ce:2.42.0`
3. `docker stop portainer && docker rm portainer && docker run -d` mit identischen Flags.
4. Post-Upgrade-Verifikation:
- HTTP 200 auf `/`, TLS self-signed OK
- `api/status` → `Version: 2.42.0`, **gleiche InstanceID** wie vorher
- **API-Token weiter gültig** (`api/settings` → 200), kein Re-Auth
- Alle 4 Endpoints + 31 Stacks abrufbar
**Sicherheitsabstände für den Bump:**
- Container läuft, alle Endpoints erreichbar (status=1)
- Edge-Agents auf den 4 Hosts (DS923, macmini, nas-fm, pve) sind **eigene Container**,
Portainer-Container-Restart berührt sie nicht.
### 2. portainer-mcp — installiert als projektspezifischer uvx-stdio
**Quelle:** `mcp-portainer` von PyPI (offiziell, von Portainer, generiert aus OpenAPI-Spec).
Wegen "Match minor to minor"-Policy (PyPI/README) war der Bump **Pflicht**, sonst kein offizieller MCP.
**Installation:**
- `uvx --from "mcp-portainer~=2.42.0" mcp-portainer` — Wheel lokal aufgelöst, 193 Tools
- `bf-infra/.mcp.json` (gitignored):
```jsonc
{
"mcpServers": {
"portainer": {
"command": "uvx",
"args": ["--from", "mcp-portainer~=2.42.0", "mcp-portainer"],
"env": {
"PORTAINER_URL": "https://10.40.20.126:9443",
"PORTAINER_API_KEY": "${env:PORTAINER_API_KEY}",
"PORTAINER_TLS_VERIFY": "0"
}
}
}
}
```
- Token via `~/.secrets/exports.zsh` (`PORTAINER_API_KEY`) — kein Klartext im Repo
- **`PORTAINER_TLS_VERIFY=0`** weil self-signed Cert auf pve-LXC
- **Nur projektebene**, **kein** Eintrag in `~/.claude.json` oder `~/.config/opencode/opencode.jsonc`
- **`.mcp.json` aus Repo entfernt** (`git rm --cached`) — `mcp/INVENTORY.md` zeigt das
**Tool-Gate:** jeder erste Read in einer Session erfordert `get_guidance()` (vom Server
erzwungen). Erst danach funktionieren `EndpointList`, `StackList`, `StackInspect` etc.
### 3. Abnahme (drei Calls gegen die echte API)
```
EndpointList mit select-Projektion:
[ { "Id":2, "Name":"pve", "Type":1, "Status":1 },
{ "Id":14, "Name":"nas-bf (ds923+)", "Type":2, "Status":1 },
{ "Id":20, "Name":"macmini", "Type":2, "Status":1 },
{ "Id":21, "Name":"nas-fm (ds415+)", "Type":2, "Status":1 } ]
StackList (plattformweit, mit select):
31 Stacks (pve=4, DS923=20, macmini=4, nas-fm=3)
StackInspect(202) mit select:
{ "id":202, "name":"pve-mcp", "endpointId":14, "status":1,
"entryPoint":"docker-compose.yml", "projectPath":"/data/compose/202" }
```
**MCP ersetzt jetzt:** `curl "$PORTAINER_URL/api/..."` aus Bash. Vorher 4-5 Aufrufe pro
Inventur-/Deploy-Schritt; jetzt ein Tool-Call mit `select`-JMESPath-Projektion.
### 4. Bestands-Update (`mcp/INVENTORY.md`)
- Zeile `portainer-mcp`: ⛔ → 🟢, mit Stand 2026-08-28, 193 Tools, Tool-Gate vermerkt
- Header ergänzt: "MCP-Hosts künftig nur WARP/lokal" (User-Direktive)
- `mcp/portainer/README.md`: **überholt** (steht noch auf "Portainer-Bump zuerst" — jetzt erledigt)
→ TODO für eigene Session: README auf neuen Stand bringen.
## Lehren aus dieser Session
### Versions-Korrektur
**PyPI ≠ Docker-Hub für dieselbe Software.** PyPI `mcp-portainer` hatte 2.42.0-2.42.6 als Wheels
(alle 6 Patch-Versionen), aber das Docker-Image `portainer/portainer-ce:2.42.x` stoppte
bei 2.42.0 — vermutlich weil Portainer die Docker-Patch-Serie anders versioniert. **Vor jedem
Versions-Claim den jeweils anderen Pfad gegenprüfen.**
### Werkzeug-Namen lernen
`list_endpoints` war geraten. Tatsächlich heißt das Tool `EndpointList` (PascalCase),
und es braucht zwingend `get_guidance()` vor dem ersten Read. **Bei unbekanntem MCP zuerst
`tools/list` und ein einzelner Probe-Call, dann den echten Read.** Sonst verschwendet man
drei Tool-Calls an Authentifizierungsfehler wie ich es hier gemacht habe.
### uvx-stdio vs Container-MCP
Für ein **einzelnes Projekt** mit Performance-Bedenken ist uvx-stdio klar besser als Container:
- kein zusätzlicher Host (DS923-Belastung entfällt)
- keine macvlan-IP nötig (kein neuer Port-Range-Konflikt)
- Tool-Calls direkt aus dem Claude/opencode-Prozess, kein Round-Trip
- **Nachteil:** Token im Memory des Claude-Prozesses lebt so lange wie die Session.
Bei Container-Lösung wäre der Token nur im MCP-Container. → Sicherheitsbedenken
gegen Performance — die Direktive des Users ist hier explizit "Performance > Security
weil LAN-only".
### `git rm --cached` für gitignored-Configs
Eine bereits getrackte Datei (hier `.mcp.json` aus Commit 3426366) wird durch
nachträgliches `.gitignore`-Pattern **nicht** ausgegraut — `git rm --cached` ist nötig,
sonst bleibt sie im Index. Beim nächsten Commit verschwindet sie sauber.
## Commit-Plan (für die nächste Session oder als Quick-Commit)
```
git add .gitignore mcp/INVENTORY.md docs/portainer-upgrade-20260828/
git commit -m "Portainer 2.40 → 2.42.0 Bump + portainer-mcp (uvx projektebene)
- Portainer-Server: 2.40.0 → 2.42.0 auf pve LXC 400, Pre-Upgrade-Snapshot
(docker commit :2.40.0-preupgrade-20260828 + JSON-Dump 31 Stacks/4 Endpoints)
- .mcp.json aus Repo (war aus Commit 3426366 getrackt, jetzt gitignored)
- mcp/INVENTORY.md: portainer-mcp ⛔→🟢, Header um MCP-WARP-Direktive ergänzt
- docs/portainer-upgrade-20260828/: Rollback-Runbook + Pre-Upgrade-State.json
Hinweis: PyPI hat mcp-portainer bis 2.42.6, Docker-Hub nur bis 2.42.0 —
README im mcp/portainer/ ist auf 2.42.6 formuliert und sollte angepasst werden.
```
## TODO (offene Punkte, NICHT Teil dieser Session)
1. **`mcp/portainer/README.md`** ist überholt (steht noch auf 2.42.6-Bump, jetzt erledigt).
Eigene 5-Minuten-Session, README auf den neuen Stand bringen.
2. **Cleanup nach 7 Tagen Bump-Stabilität:** `docker rmi portainer/portainer-ce:2.40.0-preupgrade-20260828`
3. **Shelly-mcp-Deployment** (das war Session B vor dem Portainer-Thema): shelly-mcp-Image ist gebaut,
Stack-YML liegt auf DS923, Stack-Deploy steht aus. **Sofort zurück zu Session B**, weil die
`.mcp.json` jetzt **auch** das shelly-mcp-Deployment erleichtert (kein wiederholtes curl-Poren).

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,59 @@
# Portainer 2.40.0 → 2.42.6 — Rollback-Runbook (2026-08-28)
## Voraussetzungen
- pve `.100`, dort LXC 400 (Docker-LXC auf vlan20)
- Portainer-Container läuft mit den unten dokumentierten Flags
- Pre-Upgrade-Image-Tag `portainer/portainer-ce:2.40.0-preupgrade-20260828` ist vorhanden
## Was hier dokumentiert ist
1. **Image-Tag** mit dem Stand VOR dem Bump (für 1-Befehl-Rollback).
2. **Runflags** des Containers (docker inspect-Auszug).
3. **Volume-Mount** `/var/lib/docker/volumes/portainer_data/_data → /data`
(Portainer-Persistenz: alle Stacks, Endpoint-Configs, Users, API-Tokens).
Volume bleibt **unangetastet** beim Image-Swap — Settings überleben den Bump.
## Runflags (aus `docker inspect portainer --format '{{json .HostConfig}}'`)
| Property | Wert |
|---|---|
| Image | `portainer/portainer-ce:2.42.6` (Bump) bzw. `:2.40.0-preupgrade-20260828` (Rollback) |
| Restart | `always` |
| Network | `bridge` (default), MACVLAN über das Compose-Netzwerk |
| Privileged | `false` |
| Ports | `8000/tcp → 8000`, `9443/tcp → 9443` |
| Mounts | `/var/run/docker.sock → /var/run/docker.sock` (Pflicht), `/var/lib/docker/volumes/portainer_data/_data → /data` |
## Originaler Run-Befehl (rekonstruiert)
```bash
docker run -d \
--name portainer \
--restart always \
-p 8000:8000 \
-p 9443:9443 \
-v /var/run/docker.sock:/var/run/docker.sock \
-v /var/lib/docker/volumes/portainer_data/_data:/data \
portainer/portainer-ce:2.40.0
```
## Rollback-Schritte (Bump macht Probleme)
```bash
# 1. Auf pve (LXC 400)
ssh root@10.40.20.100 "pct exec 400 -- bash -c '
docker stop portainer
docker rm portainer
docker run -d \\
--name portainer \\
--restart always \\
-p 8000:8000 -p 9443:9443 \\
-v /var/run/docker.sock:/var/run/docker.sock \\
-v /var/lib/docker/volumes/portainer_data/_data:/data \\
portainer/portainer-ce:2.40.0-preupgrade-20260828
'"
# 2. API-Token ist volume-gebunden → bleibt gültig, keine Re-Auth.
# 3. Verifikation: ein curl gegen die Settings-API reicht.
```
## Cleanup nach erfolgreichem Bump
Nach ≥7 Tagen Stabilität den `:preupgrade-20260828`-Tag löschen:
```bash
ssh root@10.40.20.100 "pct exec 400 -- docker rmi portainer/portainer-ce:2.40.0-preupgrade-20260828"
```

View File

@@ -24,6 +24,13 @@
> **Portier-Regel (ab 2026-08-28):** bei jeder MCP-Eintragung/-Stilllegung Container-Existenz > **Portier-Regel (ab 2026-08-28):** bei jeder MCP-Eintragung/-Stilllegung Container-Existenz
> in **allen 4 Endpoints** verifizieren (`/api/endpoints/<id>/docker/containers/json?all=1`), > in **allen 4 Endpoints** verifizieren (`/api/endpoints/<id>/docker/containers/json?all=1`),
> nicht in einer Config glauben. Drift-Doku ist wiederholter Befund — siehe Handoff 2026-08-27 D3/D4. > nicht in einer Config glauben. Drift-Doku ist wiederholter Befund — siehe Handoff 2026-08-27 D3/D4.
>
> **MCP-Hosts künftig nur WARP/lokal (User-Direktive 2026-08-28):** kein neuer öffentlicher
> FQDN, kein neuer CF-Tunnel für MCPs. Referenzierung über lokale IP (10.40.20.x). Folge
> für bereits öffentlich exponierte MCPs (gitea-mcp, n8n-mcp): laufen lassen bis Migration,
> FQDNs werden beim Bump **nicht** erneuert. Neue MCPs entstehen entweder als
> projektspezifische `.mcp.json` mit uvx-stdio (Beispiel: portainer-mcp) oder als
> Container im BF-LAN ohne öffentliches Fronting.
## Aktive / geplante MCPs ## Aktive / geplante MCPs
@@ -38,7 +45,7 @@
| **shelly-mcp** (eigenbau, FastMCP) | ~26 Shellys Gen3/4 — VLAN1→VLAN30-Migration (TP17) + Verwaltung | **geplant:** DS923 (`10.40.20.10`), macvlan-IP **`10.40.20.180`** | **StreamableHTTP** `http://10.40.20.180:3030/mcp` (geplant) | eigenbau `bf/shelly-mcp:0.1` (DS923-lokal gebaut aus `mcp/shelly/Dockerfile`) | `mcp/shelly/` ✓ (`Dockerfile` + `BUILD.md` + `requirements.txt`) · Stack gitignored in `docs/nas-vlan20/stacks/shelly-mcp.yml` | 🟡 **staged — Image-Build + Stack-Deploy auf DS923 ausstehend. Vorher-Stand war localhost `127.0.0.1:3030` mit `enabled=false` (Handoff 2026-08-28). Live-Verifikation 23 Shellys auf `10.40.30.180-250` (VLAN30) bestätigt 2026-08-28** | | **shelly-mcp** (eigenbau, FastMCP) | ~26 Shellys Gen3/4 — VLAN1→VLAN30-Migration (TP17) + Verwaltung | **geplant:** DS923 (`10.40.20.10`), macvlan-IP **`10.40.20.180`** | **StreamableHTTP** `http://10.40.20.180:3030/mcp` (geplant) | eigenbau `bf/shelly-mcp:0.1` (DS923-lokal gebaut aus `mcp/shelly/Dockerfile`) | `mcp/shelly/` ✓ (`Dockerfile` + `BUILD.md` + `requirements.txt`) · Stack gitignored in `docs/nas-vlan20/stacks/shelly-mcp.yml` | 🟡 **staged — Image-Build + Stack-Deploy auf DS923 ausstehend. Vorher-Stand war localhost `127.0.0.1:3030` mit `enabled=false` (Handoff 2026-08-28). Live-Verifikation 23 Shellys auf `10.40.30.180-250` (VLAN30) bestätigt 2026-08-28** |
| **pve-mcp** (samik081) | Proxmox VE API (Host `.100`) | DS923 (`10.40.20.10`), macvlan-IP **`10.40.20.173`** | **StreamableHTTP** `http://10.40.20.173:3000/mcp` (+ Health `/health` → `mcp-pve 0.6.1`) | `ghcr.io/samik081/mcp-pve:latest` | — (Stack via Portainer) | 🟢 **live, in opencode + claude.json eingetragen. Drift-Korrektur 2026-08-28: war im Inventar als „SSE `/sse" gelistet, Realität ist StreamableHTTP** | | **pve-mcp** (samik081) | Proxmox VE API (Host `.100`) | DS923 (`10.40.20.10`), macvlan-IP **`10.40.20.173`** | **StreamableHTTP** `http://10.40.20.173:3000/mcp` (+ Health `/health` → `mcp-pve 0.6.1`) | `ghcr.io/samik081/mcp-pve:latest` | — (Stack via Portainer) | 🟢 **live, in opencode + claude.json eingetragen. Drift-Korrektur 2026-08-28: war im Inventar als „SSE `/sse" gelistet, Realität ist StreamableHTTP** |
| **komodo-mcp** (samik081) | Docker-Management via Komodo Core (ersetzt Portainer) | DS923 (`10.40.20.10`), macvlan-IP **`10.40.20.176`** | SSE `http://10.40.20.176:3000/sse` (geplant) | `ghcr.io/samik081/mcp-komodo:latest` | `mcp/komodo/` ✓ (`.env.example` → `.env` nach Phase 1) | 🟡 **geplant — Migration: `mcp/komodo/MIGRATION.md`** | | **komodo-mcp** (samik081) | Docker-Management via Komodo Core (ersetzt Portainer) | DS923 (`10.40.20.10`), macvlan-IP **`10.40.20.176`** | SSE `http://10.40.20.176:3000/sse` (geplant) | `ghcr.io/samik081/mcp-komodo:latest` | `mcp/komodo/` ✓ (`.env.example` → `.env` nach Phase 1) | 🟡 **geplant — Migration: `mcp/komodo/MIGRATION.md`** |
| ~~portainer-mcp~~ | nicht im Bestand (keiner der 4 Endpoints hat einen Container mit Namen/Image `portainer-mcp`). **Entscheidung 2026-08-28: NICHT installieren** — Portainer-API direkt aus Scripts/HTTP-Calls ist ausreichend, der MCP-Client bringt keinen Mehrwert gegenüber den existierenden Wrapper-Skripten. Inventar bleibt auf "nicht geplant". | — | — | — | `mcp/portainer/` (API-Token in `.env`) bleibt als **Direkt-API-Wrapper** ohne MCP-Layer | ⛔ **bewusst NICHT** (2026-08-28, User-Direktive) | | **portainer-mcp** (offiziell, PyPI `mcp-portainer`) | Portainer CE 2.42.0 — Endpoints, Stacks, Container, Docker-Proxy; volle Berechtigung, replaces Direkt-API-Calls. **Vorbedingung erfüllt 2026-08-28:** Portainer-Server Bump 2.40.0 → 2.42.0 (Latest-Tag in `portainer/portainer-ce`, **nicht 2.42.6 wie README — Docker-Hub hat nur 2.42.0 als letzten 2.42.x-Tag**), Rollback-Image-Tag `portainer/portainer-ce:2.40.0-preupgrade-20260828` + Settings-JSON-Dump in `docs/portainer-upgrade-20260828/preupgrade-state.json`. | **lokal auf Mac, uvx stdio** (User-Direktive 2026-08-28: MCP-Hosts künftig nur WARP/lokal, kein neuer öffentlicher FQDN, Performance-Grund). **NUR projektebene:** `bf-infra/.mcp.json` (gitignored via `.mcp.json`-Pattern). Andere Projekte unberührt. | **stdio** via `uvx --from "mcp-portainer~=2.42.0" mcp-portainer` | `portainer_mcp` Python-Wheel | `bf-infra/.mcp.json` (gitignored) mit `PORTAINER_URL=https://10.40.20.126:9443`, `PORTAINER_API_KEY=${env:PORTAINER_API_KEY}`, `PORTAINER_TLS_VERIFY=0`. `mcp/portainer/.env` weiter SSoT für Token. | 🟢 **live seit 2026-08-28 — 193 Tools, Abnahme: `EndpointList` 4/4 Endpoints, `StackList` 31 Stacks (4/20/4/3), `StackInspect(202)` pve-mcp auf Endpoint 14 bestätigt. Tool-Gate: `get_guidance()` pro Session zwingend vor erstem Read.** |
| ~~omada-mcp~~ (jmtvms v0.15) | TP-Link Omada Controller | — | — | `jmtvms/tplink-omada-mcp:latest` | `mcp/omada/` ✓ | ⛔ **RETIRED 2026-08-27** — Omada-Stack raus, wir fahren auf MikroTik. Container stoppen, Image optional behalten. | | ~~omada-mcp~~ (jmtvms v0.15) | TP-Link Omada Controller | — | — | `jmtvms/tplink-omada-mcp:latest` | `mcp/omada/` ✓ | ⛔ **RETIRED 2026-08-27** — Omada-Stack raus, wir fahren auf MikroTik. Container stoppen, Image optional behalten. |
| ~~omada-controller~~ | Omada Software Controller | `10.40.20.175:8043` | — | `mbentley/omada-controller:6.2` | `omada/docker-compose.yml` | ⛔ **RETIRED 2026-08-27** | | ~~omada-controller~~ | Omada Software Controller | `10.40.20.175:8043` | — | `mbentley/omada-controller:6.2` | `omada/docker-compose.yml` | ⛔ **RETIRED 2026-08-27** |
| ~~ALLNET-MCP~~ | ALLNET ALL-SG8316M Core-Switch | — | — | — | — | ⛔ **bewusst NICHT** (Telnet/1-VTY/15-min-Lock auf Core = zu fragil). User-Entscheid: erst beschriebener Weg, sonst MikroTik-Core-Tausch. | | ~~ALLNET-MCP~~ | ALLNET ALL-SG8316M Core-Switch | — | — | — | — | ⛔ **bewusst NICHT** (Telnet/1-VTY/15-min-Lock auf Core = zu fragil). User-Entscheid: erst beschriebener Weg, sonst MikroTik-Core-Tausch. |

View File

@@ -36,6 +36,17 @@
"enabled": true, "enabled": true,
"timeout": 15000, "timeout": 15000,
"oauth": {} "oauth": {}
},
"portainer": {
"type": "local",
"command": ["uvx", "--from", "mcp-portainer~=2.42.0", "mcp-portainer"],
"environment": {
"PORTAINER_URL": "https://10.40.20.126:9443",
"PORTAINER_API_KEY": "{env:PORTAINER_API_KEY}",
"PORTAINER_TLS_VERIFY": "0"
},
"enabled": true,
"timeout": 15000
} }
}, },
"command": { "command": {