Skip to content

feat: Persistent OAuth Client Registry — fix disconnect-on-restart #45

@danieljustus

Description

@danieljustus

Ziel

opencode (und andere DCR-fähige MCP-Clients) bleiben nach Server-Neustart verbunden, ohne dass der User erneut mcp auth ausführen muss.

Warum jetzt

Jeder Server-Neustart führt zu "MCP Authentication Required" bei opencode, weil die DCR-Client-Registrierung flüchtig im RAM gehalten wird. Sobald der Authorize-Endpoint die gespeicherte client_id nicht mehr kennt, lehnt er den Request mit unauthorized_client ab.

Aktueller Stand / Evidenz

  • internal/mcp/serverbootstrap/oauth.go:20-47oauthClientStore ist ein reiner In-Memory-Map, kein Load()/Save(), kein Persistenz-Pfad
  • internal/mcp/serverbootstrap/http.go:184newOAuthClientStore() ohne vaultDir-Parameter
  • internal/mcp/token.go:144-213TokenRegistry zeigt das existierende AtomicWrite-Pattern (Referenzimplementierung)
  • internal/mcp/serverbootstrap/oauth.go:164-171 — Bei unbekannter client_id: unauthorized_client statt RFC-konformem invalid_client, kein WWW-Authenticate-Header mit Registration-Hint

In Scope

  • oauthClientStore persistent machen: JSON-Datei <vault-dir>/mcp-oauth-clients.json mit Schema {"version": 1, "clients": {"<client_id>": {...}}}
  • Load()/Save()-Methoden analog zu TokenRegistry (fileutil.AtomicWriteFile, 0o600)
  • put() ruft am Ende Save() (best-effort; Fehler logged, lässt Register-Request nicht scheitern)
  • Factory loadOAuthClientStore(vaultDir) in http.go statt newOAuthClientStore()
  • Fehlersignale verbessern: invalid_client statt unauthorized_client, WWW-Authenticate-Header mit Registration-Hint
  • Bestehenden 5-min-Cleanup in http.go:78-80 optional für client-TTL-Cleanup nutzen
  • Optionale TTL für Client-Einträge (default: unbegrenzt)

Out of Scope

  • oauthCodeStore-Persistenz (Auth-Codes haben 5 min TTL, one-shot — Restart während aktivem Flow ist Edge-Case)
  • Migration auf SQLite (Filesystem-JSON reicht)
  • Refresh-Token-Grant (eigenes Issue)
  • Konfigurierbare TTLs (eigenes Issue)

Akzeptanzkriterien

  • oauth_persistence_test.go: registriere Client → Save() → frisch geladener Store → get(clientID) liefert Client
  • Server-Restart-Szenario in serverbootstrap_test.go: Client registrieren, Server stoppen+neustarten, Authorize-Request alter client_id → success
  • mcp-oauth-clients.json existiert nach Registrierung mit 0o600-Permissions
  • Fehlerhafter Save() logged ins Audit-Log, blockiert nicht den Register-Request
  • Unbekannte client_idinvalid_client + WWW-Authenticate-Header mit Registration-Hint
  • Lint und go test sind grün

Risiken / Abhängigkeiten

  • Baut auf fileutil.AtomicWriteFile auf (bereits in token.go verwendet)
  • Ändert das Verhalten des Authorize-Fehlersignals — opencode muss den neuen invalid_client-Error und Header parsen können

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions