e331544cee
Implements OAuth 2.0 Token Exchange (RFC 8693) enabling the MCP server to exchange service account tokens for user-scoped tokens. This provides an alternative to refresh tokens for background operations. **Core Implementation:** - Added `get_service_account_token()` method to KeycloakOAuthClient for client_credentials grant - Added `exchange_token_for_user()` method implementing RFC 8693 token exchange - Fixed Fernet encryption key handling in RefreshTokenStorage (was incorrectly base64 decoding already-encoded keys) - Updated OAuth configuration to support offline_access scope and refresh token storage infrastructure **Keycloak Configuration:** - Enabled `serviceAccountsEnabled` in realm-export.json - Added `token.exchange.grant.enabled` attribute - Added `client.token.exchange.standard.enabled` attribute (required for Keycloak 26.2+ Standard Token Exchange V2) - Fresh Keycloak imports now correctly enable token exchange **Docker Compose:** - Added TOKEN_ENCRYPTION_KEY and ENABLE_OFFLINE_ACCESS environment variables - Created oauth-tokens volume for refresh token storage - Configured both mcp-oauth and mcp-keycloak services **Testing & Documentation:** - Added tests/manual/test_token_exchange.py - Validates complete RFC 8693 flow - Added tests/manual/test_nextcloud_impersonate.py - Documents session-based impersonation limitations - Added docs/oauth-impersonation-findings.md - Comprehensive investigation findings and resolution documentation **Verified Working:** ✅ Service account token acquisition (client_credentials grant) ✅ RFC 8693 token exchange for internal-to-internal tokens ✅ Exchanged tokens validate with Nextcloud APIs ✅ Keycloak 26.4.2 Standard Token Exchange V2 support **Known Limitations:** - User impersonation (requested_subject) requires Keycloak Legacy V1 with preview features - Cross-client token exchange limited to same realm - Refresh token storage infrastructure ready but unused (MCP protocol limitation) Dependencies: aiosqlite>=0.20.0 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>