MCP-Clients repariert: GUI-Env via LaunchAgent (F6) + pve-mcp-URL (F7)

Beide Fehler kamen aus der Session-Startmeldung, keiner von der
F1-Migration.

F7 - pve-mcp ENDPOINT_NOT_FOUND: beide globalen Configs zeigten auf
:3000/mcp, der Server antwortet nur auf dem Root-Pfad (live: / -> 200,
/mcp -> 404). Damit widersprachen sie der eigenen CLAUDE.md, die den
Root-Pfad seit jeher korrekt nennt. Beide korrigiert.

F6 - n8n-mcp 401, und das ist der weitreichende Fund: ~/.zprofile
sourct exports.zsh korrekt, aber .zprofile liest NUR eine Login-Shell.
Claude Desktop und opencode starten gar keine Shell - sie erben die
launchd-Umgebung. Folge: ALLE {env:VAR}-Referenzen loesen zu leer auf.
Live nachgewiesen: in keinem Claude-Helper-, claude- oder
opencode-MCP-Prozess war N8N_MCP_BEARER gesetzt, waehrend dieselbe
Variable in einer Login-Shell len=64 hat.

Das erklaert rueckwirkend die Klartext-Secrets in ~/.claude.json: dort
steht Klartext, WEIL {env:} in einer GUI-App nicht aufloest. Meine
frueher vorgeschlagene Umstellung auf {env:} haette mikromcp und
hostinger stillgelegt - gut, dass sie vertagt wurde. Jetzt ist sie
gefahrlos (Voraussetzung fuer TP30).

Loesung: ~/Library/LaunchAgents/net.emsenhuber.gui-env.plist ruft
~/dev/.bin/gui-env-sync.sh, das exports.zsh zur Laufzeit liest und die
Variablen per launchctl setenv setzt. Kein Secret-Duplikat, die plist
enthaelt keine Werte, SSoT bleibt exports.zsh. 22 von 22 gesetzt.

Die Konvention "niemals launchctl setenv" ist damit zu praezisieren,
nicht gebrochen: verboten war der Handaufruf, weil er in keiner Datei
steht. plist + Skript sind die Datei-Form davon.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-30 21:00:00 +02:00
parent 65a61f7fb6
commit ed005fff2e

View File

@@ -593,6 +593,36 @@ und Letzteres verlangt die **physische Bestätigung am Gerät** — ap1 Technik
`~/.secrets/credentials.env` wurden beide gegen die n8n-API getestet (**HTTP 200**), der `.env`-Wert
ist ungetestet. Der Bearer dagegen ist überall identisch. Volume `n8n-mcp-deploy_n8n-mcp-data`
überlebt einen Recreate, `gitea-mcp` hat gar kein Compose (reiner `docker run`).
- ✅ **F7 — `pve-mcp`-URL korrigiert (2026-08-30).** Beide globalen Configs zeigten auf
`http://10.40.20.173:3000/**mcp**`, der Server antwortet aber **nur auf dem Root-Pfad**
(live: `/` → **200**, `/mcp` → **404**) — die Session meldete entsprechend `ENDPOINT_NOT_FOUND`.
Damit widersprachen `~/.claude.json` und `~/.config/opencode/opencode.jsonc` der **eigenen
CLAUDE.md**, die den Root-Pfad seit jeher korrekt nennt („Root-Pfad, NICHT /sse"). Beide auf
`http://10.40.20.173:3000/` gezogen; `bf-infra/opencode.jsonc` war bereits richtig.
Merkregel: **wenn ein MCP „endpoint not found" meldet, zuerst die Doku gegen die Config lesen** —
hier stand die Antwort seit Monaten im Repo.
- ✅ **F6 — GUI-Apps sehen `exports.zsh` nicht: gelöst 2026-08-30 per LaunchAgent.** Aufgefallen, weil
`n8n-mcp` beim Session-Start mit **401** abgewiesen wurde, obwohl URL und Bearer stimmten.
🔑 **Ursache — und sie reicht weit über n8n-mcp hinaus:** `~/.zprofile` sourct `exports.zsh` korrekt,
aber **`.zprofile` liest nur eine Login-Shell**. Claude Desktop und opencode starten **gar keine
Shell**, sie erben die launchd-Umgebung. Damit lösen **alle `{env:VAR}`-Referenzen zu LEER auf** —
live nachgewiesen: in **keinem** der `Claude Helper`-, `claude`- und opencode-MCP-Prozesse war
`N8N_MCP_BEARER` gesetzt, während dieselbe Variable in einer Login-Shell mit `len=64` vorhanden ist.
🔴 **Das erklärt rückwirkend die Klartext-Secrets in `~/.claude.json`** (4 Router-PW + Hostinger-Token):
dort steht Klartext, *weil* `{env:}` in einer GUI-App nicht funktioniert — vermutlich kein
Schlamperei-, sondern ein Notbehelf. **Die naheliegende „Hygiene"-Umstellung auf `{env:}` hätte
`mikromcp` und `hostinger` stillgelegt.** Erst jetzt ist sie gefahrlos → Voraussetzung für **TP30**.
- **Lösung:** `~/Library/LaunchAgents/net.emsenhuber.gui-env.plist` (RunAtLoad) ruft
`~/dev/.bin/gui-env-sync.sh`; das Skript liest `exports.zsh` **zur Laufzeit** und setzt die
Variablen per `launchctl setenv`. **Kein Secret-Duplikat** — die plist enthält keine Werte, die
SSoT bleibt `exports.zsh`, neue Variablen dort werden beim nächsten Login automatisch mitgenommen.
Abnahme: **22 von 22** gesetzt, `N8N_MCP_BEARER` mit dem Hash des Store-Werts.
- ⚠️ **Damit ist die Konvention „niemals `launchctl setenv`" zu präzisieren, nicht zu brechen:**
verboten war der *Handaufruf*, weil er in keiner Datei steht und keinen Reboot überlebt. plist +
Skript sind genau die Datei-Form davon. In Kauf genommen: `launchctl setenv` übergibt den Wert
als Argument (kurz in `ps` sichtbar) — es gibt keine stdin-Variante.
- 📌 **Nach jeder Änderung an `exports.zsh` gilt:** Skript erneut laufen lassen **und die App neu
starten** — ein laufender Prozess erbt die Umgebung nicht nachträglich.
- 🔜 **F5 — Secret-Verteilung auf Container-Hosts klären** (offen aus F1). `~/.secrets/credentials.env`
ist SSoT, liegt aber **nur auf dem MacBook**; auf dem macmini existiert **kein `~/.secrets`**, und
dort laufen die Container. Ein Symlink ist deshalb physisch unmöglich. Zu entscheiden: schlanke