Ziel
MCP-Clients können via refresh_token-Grant ihre Tokens automatisch erneuern, ohne dass der User erneut den Authorization-Code-Flow durchlaufen muss. Die Token-TTLs sind via Konfiguration einstellbar.
Warum jetzt
Der Token-Endpoint unterstützt aktuell nur grant_type=authorization_code. opencode muss nach 24h den kompletten Flow wiederholen, weil kein Refresh-Token-Grant (RFC 6749 §6) implementiert ist. Refresh-Tokens machen die persistente Client-Registry (separates Issue) erst richtig wertvoll: Clients rotieren selbstständig und bleiben über Wochen verbunden.
Aktueller Stand / Evidenz
internal/mcp/serverbootstrap/oauth.go:234 — grant_type != "authorization_code" → einziger unterstützter Grant
internal/mcp/serverbootstrap/oauth.go:258 — 24*time.Hour hardcodiert
internal/mcp/serverbootstrap/wellknown.go:49 — grant_types_supported: ["authorization_code"] → refresh_token fehlt
internal/mcp/token.go — ScopedToken hat kein RefreshTokenHash-Feld
internal/config/schema.go:52-71 — MCPConfig hat keine OAuthConfig-Felder für TTLs
In Scope
ScopedToken um RefreshTokenHash string + RefreshExpiresAt *time.Time erweitern
TokenRegistry.CreateWithRefresh(label, allowed, agent, accessTTL, refreshTTL) — erzeugt access+refresh-Token-Paar mit separaten Hashes
TokenRegistry.RotateViaRefreshToken(rawRefreshToken) — revoziert altes access-Token, erzeugt neues Paar, constant-time-compare der Hashes
TokenRegistryFile.Version von 1 auf 2 erhöhen (alte Dateien load-kompatibel)
handleOAuthToken um grant_type=refresh_token-Switch erweitern
OAuthConfig in MCPConfig: AccessTokenTTL (default 24h), RefreshTokenTTL (default 720h/30d)
fileMCPConfig-Pointer-Felder + MergeFileMCPConfig-Roundtrip + defaultMCPConfig()
wellknown.go:49 — grant_types_supported um "refresh_token" erweitern
handleOAuthRegister-Response: grant_types auf ["authorization_code", "refresh_token"] aktualisieren
- Abgelaufenes refresh_token → RFC-konformes
invalid_grant
Out of Scope
- Persistente Client-Registry (separates Issue)
- OAuth-Code-Store-Persistenz (5-min-TTL, Edge-Case)
- Migration auf SQLite
- Andere MCP-Clients automatisiert testen
Akzeptanzkriterien
Risiken / Abhängigkeiten
- Baut auf dem AtomicWrite-Pattern in
token.go auf
- Baut auf Issue "Persistent OAuth Client Registry" auf (getrennte Issues, aber logisch verwandt)
- Schema-Version 1→2 erfordert vorsichtige Migration (leere Refresh-Felder ignorieren)
- Refresh-Token-Rotation folgt dem "Single-Use"-Pattern: Client bekommt bei Rotation immer ein neues refresh_token, das alte wird invalid
Ziel
MCP-Clients können via
refresh_token-Grant ihre Tokens automatisch erneuern, ohne dass der User erneut den Authorization-Code-Flow durchlaufen muss. Die Token-TTLs sind via Konfiguration einstellbar.Warum jetzt
Der Token-Endpoint unterstützt aktuell nur
grant_type=authorization_code. opencode muss nach 24h den kompletten Flow wiederholen, weil kein Refresh-Token-Grant (RFC 6749 §6) implementiert ist. Refresh-Tokens machen die persistente Client-Registry (separates Issue) erst richtig wertvoll: Clients rotieren selbstständig und bleiben über Wochen verbunden.Aktueller Stand / Evidenz
internal/mcp/serverbootstrap/oauth.go:234—grant_type != "authorization_code"→ einziger unterstützter Grantinternal/mcp/serverbootstrap/oauth.go:258—24*time.Hourhardcodiertinternal/mcp/serverbootstrap/wellknown.go:49—grant_types_supported: ["authorization_code"]→refresh_tokenfehltinternal/mcp/token.go—ScopedTokenhat keinRefreshTokenHash-Feldinternal/config/schema.go:52-71—MCPConfighat keineOAuthConfig-Felder für TTLsIn Scope
ScopedTokenumRefreshTokenHash string+RefreshExpiresAt *time.TimeerweiternTokenRegistry.CreateWithRefresh(label, allowed, agent, accessTTL, refreshTTL)— erzeugt access+refresh-Token-Paar mit separaten HashesTokenRegistry.RotateViaRefreshToken(rawRefreshToken)— revoziert altes access-Token, erzeugt neues Paar, constant-time-compare der HashesTokenRegistryFile.Versionvon 1 auf 2 erhöhen (alte Dateien load-kompatibel)handleOAuthTokenumgrant_type=refresh_token-Switch erweiternOAuthConfiginMCPConfig:AccessTokenTTL(default 24h),RefreshTokenTTL(default 720h/30d)fileMCPConfig-Pointer-Felder +MergeFileMCPConfig-Roundtrip +defaultMCPConfig()wellknown.go:49—grant_types_supportedum"refresh_token"erweiternhandleOAuthRegister-Response:grant_typesauf["authorization_code", "refresh_token"]aktualisiereninvalid_grantOut of Scope
Akzeptanzkriterien
oauth_refresh_test.go: Full-Flow → refresh_token-Grant → altes access-Token revoked → neues Paar funktioniertoauth_refresh_test.go: abgelaufenes refresh_token →invalid_grantoauth_config_test.go: custom TTLs in Config →handleOAuthTokenrespektiert sie (z. B. Access 10s, Refresh 30s → nach 15s kein Refresh mehr möglich)/.well-known/oauth-authorization-serverlistetgrant_types_supported: ["authorization_code", "refresh_token"]POST /oauth/registergibtgrant_types: ["authorization_code", "refresh_token"]zurückmcp-tokens.jsonzeigt nachCreateWithRefreshsowohl access- als auch refresh-token-Einträgeoauth.access_token_ttl: 10s+oauth.refresh_token_ttl: 30swird korrekt eingelesenRisiken / Abhängigkeiten
token.goauf