# Handoff 2026-08-28 (Abends) — terpene/blaueblueten-n8n-Schnittstelle + globale MCP-Migration > **Zweck:** Christophs Kontext explodiert in der heutigen Session. Diese Datei gibt > einem Nachfolger alles mit, was offen ist: terpene-Wünsche, globale MCP-Migration, > CF-Token-Diagnose. **Geschrieben zum Sessionende**, damit der Nachfolger ohne Rückfragen > weiterarbeiten kann. > > **Status-Modus am Übergabepunkt:** Plan → Build aktiviert durch User-Direktive. **`handoff-20260828-portainer-bump-und-mcp` ist der letzte Build-Schritt**, der noch nicht in diesem > Handoff steckt (separat referenziert). > > **Was dieser Handoff absichtlich NICHT dupliziert:** den Inhalt von > `docs/handoff-20260828-portainer-bump-und-mcp.md` (Portainer-Bump + portainer-mcp). > Der Nachfolger liest beide — Reihenfolge: erst dieser hier, dann der Portainer-Handoff. --- ## 1. Sofort (nächste Session, kein Warten) ### 1.1 CF-Token-Audit (Christoph-Direktive: „prüfe welche Berechtigungen du hast") **Befund dieser Session:** **`CF_DNS_API_TOKEN` ist ungültig** (Verify-Endpoint antwortet mit `code: 1000, message: "Invalid API Token"`). Christoph hatte recht mit der Vermutung — „du hast nen falschen Token extrahiert". **Was der Nachfolger macht:** 1. Im CF-Dashboard () prüfen, ob beide Tokens (`CF_API_TOKEN`, `CF_DNS_API_TOKEN`) noch **existieren** (Dash akzeptiert gelöschte Tokens stillschweigend, dann wirkt `verify` identisch zu nie-angelegt-gewesen). 2. Falls beide weg: **neue Tokens anlegen** mit Scopes - **`CF_API_TOKEN`**: Account-Scope, alle Ressourcen (`Zone:Read`, `Zone:Edit`, `Account:Read`, `Workers:Read`, `Workers:Edit`, `Access:Read`, `Access:Edit`, `Tunnel:Read`). Das ist der **Full-Access-Token**, den Hubert für terpene-Workflows braucht (W6, Workers-Scope). - **`CF_DNS_API_TOKEN`**: `Zone:DNS:Edit`, **Scope auf `emsenhuber.net` und `teilzeitladen.xyz` beschränken** (nicht account-weit — Lehrgeld aus 2026-08-24). 3. In `~/.secrets/credentials.env` schreiben, **nicht** ins Repo. 4. Verify-Test im Nachgang (gleicher curl wie oben): ```bash source ~/.secrets/credentials.env curl -sg "https://api.cloudflare.com/client/v4/user/tokens/verify" \ -H "Authorization: Bearer $CF_API_TOKEN" | jq '.result' # {"id":"...", "status":"active", "expires_on":"..."} ``` ### 1.2 Globale MCP-Migration von FQDN auf IP (User-Direktive 2026-08-28) **Bestand vor Migration** (`~/.config/opencode/opencode.jsonc`, der globale Stand): | MCP | aktuelle URL | Soll-URL (nach Migration) | Auth | |---|---|---|---| | `gitea-mcp` | `https://gitea-mcp.emsenhuber.net/mcp` ❌ | `http://10.40.20.157:3000/mcp` (gitea-Container im macvlan vlan20, **LAN-IP**, kein CF) | Bearer via `GITEA_API_TOKEN` | | `n8n-mcp` | `https://n8n-mcp.emsenhuber.net/mcp` ❌ | `http://10.40.20.167:3001/mcp` (n8n-mcp-Container, macmini) — oder lokale IP. **Achtung:** `.cf-tunnel-bf-mm` läuft über `bf-mm`-Tunnel, der Live ist. Schritt: prüfen, ob `n8n-mcp` MCP mit Bearer-Token am Backend statt am CF-Access lebt. | Bearer via `N8N_MCP_BEARER` | | `cloudflare` | `https://mcp.cloudflare.com/mcp` ✅ (externer offizieller FQDN, **NICHT** migrieren — Antwort des Users heute explizit) | unverändert | OAuth-User-Flow | | `pve-mcp` | `http://10.40.20.173:3000/mcp` ✅ | unverändert | keine (LAN-only) | | `home-assistant` | `http://10.40.20.105:9583/...` ✅ | unverändert | Token in URL | | `mikromcp` | npx-lokal | unverändert | env-PASS | | `ollama` | npx-lokal | unverändert | env-OLLAMA_HOST | | `hostinger-api` | npx-lokal | unverändert | env-TOKEN | | `playwright` | npx-lokal | unverändert | keine | **Was der Nachfolger macht:** 1. **Backup** der aktuellen `~/.config/opencode/opencode.jsonc` machen (`cp ... .bak-pre-mcp-ip-migration-20260828`). 2. **Jetzt das ist dringend zu verifizieren:** liegen die MCP-Backends (gitea-mcp auf 10.40.20.157, n8n-mcp auf 10.40.20.167, oder 10.40.20.105 für HA) **tatsächlich** auf diesen IPs und antworten auf `/mcp`? **Bestandsaufnahme zuerst**, nicht raten: ```bash nc -z -G 3 10.40.20.157 3000 # gitea nc -z -G 3 10.40.20.167 3001 # n8n-mcp # Antwort: "Connection to ... port ... [tcp] succeeded!" = gut. ``` Falls eine IP nicht antwortet: **nicht** auf CF-FQDN zurückfallen — User-Direktive war klar „FQDN raus". Stattdessen den Container neu deployen via portainer-mcp auf einer festen LAN-IP. 3. **gitea-mcp** und **n8n-mcp** in `opencode.jsonc` umstellen: - `url` auf die LAN-IP. - `headers: {"Authorization": "Bearer {env:...}"}` — env-Werte aus `~/.secrets/exports.zsh` (gitea-Token aus `git` User im gitea-Repo selbst; n8n-MCP-Bearer aus dem n8n-mcp-Container-Config). 4. **Funktionstest** mit `opencode mcp list`, dann optional mit echtem Tool-Call via LLM. Bei `401`: falscher Bearer-Scope → Container-Log lesen (gitea-mcp mit `docker logs gitea-mcp`, n8n-mcp analog). **Achtung:** die projektspezifische `bf-infra/opencode.jsonc` **enthält** `portainer` und `shelly-mcp` (eigene Einträge, bereits von der heutigen Session). Der globale opencode.jsonc **enthält sie weiterhin NICHT** (Kommentar sagt „User-Direktive 2026-08-28: nicht installieren"). **Diese Asymmetrie ist bewusst:** portainer + shelly-mcp werden nur in bf-infra benötigt, andere Projekte brauchen sie nicht. **Aber:** die globale MCP-Liste sollte `MikroTik zu BF-LAN-IP migrieren` und die `RouterOS`-Konfiguration lokal halten — das ist im Inventar-Backlog (siehe bf-infra/mcp/INVENTORY.md). ### 1.3 terpene/blaueblueten-n8n — `.mcp.json` mit n8n-mcp ergänzen (User-Zustimmung) **W5 im Wunschzettel ist freigegeben.** `blaueblueten-n8n/.mcp.json` existiert schon mit `gitea-mcp`. **Was rein soll:** ```jsonc { "mcpServers": { "gitea-mcp": { "type": "http", "url": "http://10.40.20.157:3000/mcp" }, "n8n-mcp": { "type": "http", "url": "http://10.40.20.167:3001/mcp", "headers": {"Authorization": "Bearer "} } } } ``` - **Achtung Domain:** **420.teilzeitladen.xyz** (nicht `terpene.teilzeitladen.xyz`) ist die Live-Domain. Aber für die MCP-Liste hier ist Domain irrelevant — die MCPs laufen über LAN-IPs, nicht über die Public-Domain. - **Source-of-truth der Token:** `N8N_MCP_BEARER` aus `~/.secrets/exports.zsh`. Vor Eintrag erst verifizieren, dass es dort steht. ### 1.4 STATUS.md-Korrektur im terpene-Projekt (User-Direktive: **420.teilzeitladen.xyz** ist die echte Domain) `STATUS.md` schreibt aktuell fälschlich `terpene.teilzeitladen.xyz` als Live-URL (Zeile 6, „v3 — 2026-07-27"). Korrektur: - Suchen/Ersetzen: `terpene.teilzeitladen.xyz` → `420.teilzeitladen.xyz` **nur** in der URL, **nicht** in Code-Identitäten (Backend-Bindings wie `HYPERDRIVE_RW`, Worker-IDs, K8s-Labels bleiben unverändert). - Im STATUS.md außerdem: alte statische Seite 420 ist die **einzige** Public-Domain, kein DNS-Cutover zur terpene-Subdomain. Cloudflare-Worker bleibt für die Backend-Compute, Frontend-Domain ist 420. --- ## 2. Offene Punkte (User-gated) ### 2.1 W1 terpene — pg_hba.conf (User-blocked) - **Status:** ⏸️ deferred. Christoph hat die Entscheidung. - **Was Hubert in terpene dafür bräuchte:** `pg_hba.conf`-Zeile `host terpene terpene_rw 10.40.10.0/24 scram-sha-256` + `pg_ctl reload`. - **Was der Nachfolger NICHT tut:** keine Änderung. Wartet auf Christoph. ### 2.2 W2 terpene — `420.teilzeitladen.xyz` aus LAN erreichbar (TP31-Schritt 5–6) - **Mechanik gelöst** durch 1.2 (portainer-mcp-Exec auf caddy-bf1) — Caddyfile-Patch via ContainerExec reicht. **DNS-Token-Problem bleibt** (W6), aber Caddyfile-Patch ist davon unabhängig. - **Was der Nachfolger NICHT tut:** keine DNS-Änderungen, keine Caddy-Hot-Reloads ohne Christoph-Direktive. ### 2.3 W3 terpene — Caddyfile-Patch ohne SSH - **Status:** ⏸️ deferred. Mit portainer-mcp-ContainerExec **aber** immer noch 30-Sekunden-Aufwand pro Patch. Sobald terpene-Bedarf entsteht, in einer terpene-Session direkt machbar. - **Was nicht im Weg steht:** portainer-mcp funktioniert (verifiziert heute). ### 2.4 W4 terpene — Caddy-Access-Logs lesbar - **Status:** ⏸️ deferred. Kein Workaround. ### 2.5 W6 terpene — CF-Workers-Scope (User-blocked, jetzt mit klarer Diagnose) - **Status:** ⏸️ deferred. **Aber:** der Befund 1.1 zeigt, dass `CF_API_TOKEN` und `CF_DNS_API_TOKEN` möglicherweise beide ungültig sind — **bevor** neue Tokens für Workers-Scope angelegt werden, **erst diese Validität klären**. - **Vermutung:** Christophs `~/.secrets/` enthält einen **alten, abgelaufenen** Token. Im CF-Dashboard nachsehen, ggf. neu anlegen. ### 2.6 W7 terpene — bf-public-Tunnel-Status - **Wo steht's:** `docs/teilprojekte.md` Quick-Index, TP31-Zeile: „Offen: Schritt 5–6 (Tunnel `bf-public`, Abbau)". - **Was hier wirklich passieren müsste (User hat das im Projektstatus geprüft):** Der Tunnel ist im CF-Backend angelegt (`48d3b0b6-7233-4781-a76a-1de4a5e957ce`), aber `status: inactive`, kein Connector. **Löschen** (User-Anweisung „alle vier toten Tunnel löschen" stand schon im Handoff 2026-08-24), **wenn** er nicht für terpene gebraucht wird. - **Was der Nachfolger NICHT tut:** keinen Connector hochfahren, keinen Tunnel-Status ändern ohne Christoph-Ok. --- ## 3. Werkzeug/MCP-Stand am Übergabepunkt ### 3.1 Bereitgestellt heute - **portainer-mcp** in `bf-infra/opencode.jsonc` (uvx-stdio, 193 Tools) - **shelly-mcp** in `bf-infra/opencode.jsonc` (remote StreamableHTTP, `http://10.40.20.180:3030/mcp`) - **Portainer-Server 2.40 → 2.42.0** auf pve LXC 400, alle 4 Endpoints + 31 Stacks intakt - **shelly-mcp-Container** auf DS923 (macvlan vlan20, IP `10.40.20.180`) - **TP36** (SSoT-via-Nautobot) als strukturelle Folge angelegt; **TP35** (MCP-Schichtung) Schicht 6 (Skill global) ✅, Rest gebündelt ### 3.2 Was heute NICHT funktioniert hat - **shelly-mcp-Deploy:** erste Deploy-Welle scheiterte an `mcp 2.x`-Inkompatibilität (`MCPServer`-Umbenennung). Fix: `requirements.txt` `mcp[cli]>=1.0,<2`. **Commit `b8fd42b`.** - **Probe-Werkzeuge:** `mcp__shelly__*` und `mcp__portainer__*` sind jetzt in opencode verfügbar, aber heute **nicht** durch eine echte LLM-Antwort validiert (Tool-Gate `get_guidance()` muss einmalig pro Session abgefragt werden). - **Leak #2 in dieser Session:** `sed 's/SHELLY_DEVICE_PW=.*/=/'` auf der Stack-YML hat den Wert **nicht** maskiert. Wert ist verbrannt, gehört in **TP30 Posten #3** hochgestuft (siehe `docs/teilprojekte.md` Abschnitt TP30). Lessons-Block in CLAUDE.md ergänzt (Sed-Replacement-Pattern zu wenig greedy). ### 3.3 Globale vs. projekt-spezifische MCP-Liste — aktuelle Asymmetrie - **global** (`~/.config/opencode/opencode.jsonc`): 11 MCPs, **enthält KEIN** portainer + shelly-mcp. Kommentar im File besagt explizit „User-Direktive 2026-08-28: nicht installieren". - **projekt bf-infra** (`bf-infra/opencode.jsonc`): 11 MCPs, **enthält** portainer + shelly-mcp. - **projekt terpene/blaueblueten-n8n** (`blaueblueten-n8n/.mcp.json`): **1 MCP** aktuell (gitea-mcp), soll um n8n-mcp ergänzt werden (siehe 1.3). --- ## 4. Aus dem Userwortlaut zum Sessionende Originalaussagen Christophs (verbatim, Reihenfolge wie im Chat): 1. „420.teilzeitladen.xyz ist unsere domain. alle übrigen sind ungültig und falsch." 2. „stelle n8n-mcp bitte global auf die passende IP um. gilt analog für alle übrigen mcps, die noch fqdn verwenden." 3. „hast du einen konkreten plan, wie das projekt webseitig publiziert wird? stichwort caddy cloudflare." 4. „prüfe welche berechtigungen du hast. du solltest full access haben, aber möglicherweise hast du nen falschen token extrahiert und daher nur dns edit für emsenhuber.net" 5. „intern geht es gerne auch ohne bearer. dsa war ja mitunter der gedanke, weil es da immer probleme gab. warp löst es in einem zug." **Aus 5.** folgt zusätzlich: **WARP-basierter Zugriff statt Bearer-Auth** für interne MCP-Endpunkte. Konkret: `gitea-mcp` und `n8n-mcp` laufen für interne User (im LAN/WARP) ohne Token. **Konsequenz für 1.2:** die `Authorization: Bearer` Header im `opencode.jsonc` kann entfallen — wenn der Endpunkt via CF-Access **kein** Token fordert (weil interne Source-IP = WARP-CIDR), braucht der Header nichts. → **Migrations-Reihenfolge für 1.2:** (a) Erst WARP-Policy testen (geht der Connector ohne Token mit WARP?). Wenn ja: Header leer. (b) Wenn nein: Bearer-Header mit Token aus `~/.secrets/`. **Aus 3.** ist die strategische Frage der terpene-Publikation offen — Christoph hat darauf keine Antwort gegeben (Sitzungsende). Folge-Session braucht eine Antwort, bevor weitere terpene-Infrastructure-Schritte gehen. --- ## 5. Selbst-Korrekturen aus dieser Session (Lessons übernommen) Diese drei in CLAUDE.md sind neu und der Nachfolger muss sie kennen: - **Wartungs-Token**: `~/.secrets/exports.zsh` lädt **nicht automatisch** in jeder Shell — `source` musst du explizit machen. Das `bash`-Tool in opencode macht das nicht. Vor CF-Calls, vor Portainer-Calls, vor Secret-Vergleichen: `source ~/.secrets/exports.zsh`. - **`sed 's/=.*/=/'` maskiert NICHT**, wenn der Wert Slashes/Backslashes/Zeichen enthält → siehe CLAUDE.md, Lessons-Block. - **Stack-Listing ohne `select`-Projektion gibt ALLE ENV-Werte aus** (Beispiel heute: OPENAI_KEY, N8N_API_KEY, CLOUDFLARE_TUNNEL_TOKEN in einem Aufruf) → `select`-JMESPath **immer** nutzen. --- ## 6. Wo der Nachfolger weiter arbeitet Empfohlene Reihenfolge für die nächste Session: 1. **1.1 CF-Token-Audit** (höchste Blocker-Kraft — fast alles hängt daran). 2. **1.2 Globale MCP-Migration** (gitea + n8n zuerst, weil terpene aktiv ist). 3. **1.4 STATUS.md-Korrektur** im terpene-Projekt (1 Minute). 4. **1.3 terpene-n8n-mcp** in `.mcp.json` (5 Minuten, sobald 1.2 durch ist). 5. **Strategie-Frage 3** aus 4.: **Wie publizieren wir terpene.teilzeitladen.xyz?** Vorschlag für Christoph: Worker bleibt; `bf-public`-Tunnel ggf. gelöscht; Caddy auf LXC 130 ist **nicht der Pfad** für terpene-öffentlich (kein DNS-Token-Scope). **Wichtige Projekt-Dateien, die der Nachfolger liest:** - `~/dev/bf-infra/docs/teilprojekte.md` — SSoT für alle TPs (Quick-Index Zeile 178–216). - `~/dev/bf-infra/CLAUDE.md` Lessons-Block — alle hier entstandenen Lehren. - `~/dev/bf-infra/opencode.jsonc` — projekt-spezifische MCP-Liste. - `~/dev/bf-infra/mcp/INVENTORY.md` — was läuft, was war, was soll nicht. - `~/dev/bf-infra/docs/handoff-20260828-portainer-bump-und-mcp.md` — Parallel-Handoff zum Portainer-Bump. - `~/dev/blaueblueten-n8n/docs/infra-wunschzettel.md` — Huberts Wunschzettel an Christoph. - `~/dev/blaueblueten-n8n/STATUS.md` — terpene-Live-Status (mit Domain-Korrektur-Bedarf). --- **Stand:** 2026-08-28, ~23:30 Übergabe **Nächste Session:** heute Abend oder morgen, Christoph startet nach gusto.