Chris Coutinho
403f8be429
feat: Add Keycloak external IdP integration with custom scopes
...
Add comprehensive support for using Keycloak as an external identity
provider with Nextcloud custom scopes. This enables testing of ADR-002
external IdP integration patterns.
**Keycloak Realm Configuration:**
- Add frontendUrl attribute to issue tokens with public issuer URL
- Define 18 Nextcloud custom client scopes (notes:read/write,
calendar:read/write, contacts:read/write, cookbook:read/write,
deck:read/write, tables:read/write, files:read/write,
sharing:read/write, todo:read/write)
- Add all custom scopes to nextcloud-mcp-server client optional scopes
- Scopes include consent screen text for user-friendly OAuth flow
**MCP Server Configuration:**
- Add OIDC_JWKS_URI environment variable support
- Implement JWKS URI override logic for Docker networking
- Update NEXTCLOUD_PUBLIC_ISSUER_URL to include full realm path
- Enable MCP server to fetch JWKS from internal Docker network
**Test Infrastructure:**
- Add keycloak_oauth_client_credentials fixture (session-scoped)
- Add keycloak_oauth_token fixture with Playwright automation
- Implement PKCE (S256) support for Keycloak OAuth flow
- Add nc_mcp_keycloak_client fixture for MCP testing
- Create comprehensive test suite in test_keycloak_external_idp.py
**Tests Created:**
- test_keycloak_oauth_token_acquisition: Token acquisition via Playwright
- test_keycloak_oauth_client_credentials_discovery: OIDC discovery
- test_mcp_client_connects_to_keycloak_server: MCP connectivity
- test_external_idp_server_initialization: Server auto-detection
- test_external_idp_token_validation: Token validation flow
- test_tools_work_with_keycloak_token: End-to-end tool execution
- test_keycloak_token_persistence: Multi-operation token reuse
- test_user_auto_provisioning: Nextcloud user provisioning
- test_scope_filtering_with_keycloak: Scope-based tool filtering
- test_keycloak_error_handling: Error handling
- test_external_idp_architecture: Architecture documentation
**Current Status:**
- ✅ Keycloak realm configuration complete
- ✅ Custom scopes defined and available
- ✅ OAuth token acquisition working (1 test passing)
- ⚠️ Token validation needs additional work (external IdP userinfo)
**Files Modified:**
- keycloak/realm-export.json: Realm configuration with scopes
- tests/conftest.py: Keycloak OAuth fixtures (+285 lines)
- tests/server/oauth/test_keycloak_external_idp.py: New test suite
- docker-compose.yml: OIDC_JWKS_URI and issuer configuration
- nextcloud_mcp_server/app.py: JWKS URI override logic
🤖 Generated with [Claude Code](https://claude.com/claude-code )
Co-Authored-By: Claude <noreply@anthropic.com >
2025-11-02 22:03:20 +01:00
Chris Coutinho
2a1274d8a8
refactor: Unify OAuth configuration to be provider-agnostic
...
Replace provider-specific environment variables (OAUTH_PROVIDER, KEYCLOAK_*)
with generic OIDC_* variables that work with any OIDC-compliant provider.
**Key Changes:**
- Auto-detect provider mode from OIDC_DISCOVERY_URL issuer
- External IdP mode: issuer ≠ NEXTCLOUD_HOST (Keycloak, Auth0, Okta, etc.)
- Integrated mode: issuer = NEXTCLOUD_HOST (Nextcloud OIDC app)
- Unified OIDC discovery flow (single code path)
- Generic client credential loading (static or DCR)
- Simplified docker-compose.yml environment variables
**Environment Variables:**
BEFORE:
OAUTH_PROVIDER=keycloak
KEYCLOAK_URL=http://keycloak:8080
KEYCLOAK_REALM=nextcloud-mcp
KEYCLOAK_CLIENT_ID=...
KEYCLOAK_DISCOVERY_URL=...
AFTER:
OIDC_DISCOVERY_URL=http://keycloak:8080/realms/nextcloud-mcp/.well-known/ ...
OIDC_CLIENT_ID=nextcloud-mcp-server
OIDC_CLIENT_SECRET=...
**Benefits:**
- Works with any OIDC provider without code changes
- No manual provider selection needed
- Cleaner environment variable naming
- Reduced code duplication (~150 lines removed)
**Testing:**
✅ mcp-keycloak auto-detects external IdP mode
✅ Token exchange test passes with generic config
✅ Backward compatible - integrated mode still works
🤖 Generated with [Claude Code](https://claude.com/claude-code )
Co-Authored-By: Claude <noreply@anthropic.com >
2025-11-02 22:03:20 +01:00
Chris Coutinho
e331544cee
feat: Implement RFC 8693 token exchange for Keycloak (ADR-002 Tier 2)
...
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 >
2025-11-02 22:03:19 +01:00
Chris Coutinho
37b0b4a281
fix: Update DCR token_type tests for OIDC app changes
...
The Nextcloud OIDC app has updated token_type parameter values:
- Changed from "Bearer" → "opaque" for opaque tokens
- Changed from "JWT" → "jwt" for JWT tokens
Updated test_dcr_token_type.py to use lowercase token_type values:
- token_type="jwt" for JWT-formatted tokens
- token_type="opaque" for opaque/bearer tokens
This fixes test failures where tests were using the old "Bearer" and
"JWT" (uppercase) values which are no longer recognized by the OIDC app.
Fixes test: test_dcr_respects_bearer_token_type
🤖 Generated with [Claude Code](https://claude.com/claude-code )
Co-Authored-By: Claude <noreply@anthropic.com >
2025-11-02 22:03:19 +01:00
Chris Coutinho
f34366a260
feat: Add Keycloak OAuth provider support with refresh token storage
...
Implements Keycloak as an external OIDC provider following ADR-002
architecture for background job authentication using offline_access.
## Features
- Keycloak OAuth provider with PKCE and offline_access support
- Refresh token storage with Fernet encryption
- Token verifier for both JWT and opaque tokens
- Multi-client validation (realm-level trust)
- Sample configuration for Keycloak integration
## Implementation
### OAuth Provider (keycloak_oauth.py)
- Authorization Code Flow with PKCE
- Refresh token exchange
- OIDC discovery endpoint support
- Token validation with JWKS
### Token Storage (refresh_token_storage.py)
- Encrypted storage using Fernet symmetric encryption
- SQLite backend for persistence
- Token rotation support
- Per-user token management
### Token Verifier Updates
- Support both JWT (self-encoded) and opaque tokens
- JWKS-based JWT signature verification
- Introspection endpoint fallback for opaque tokens
- Scope extraction from both token types
### Configuration
- .env.keycloak.sample: Example configuration with Keycloak URLs
- docs/keycloak-multi-client-validation.md: Realm-level validation documentation
- app-hooks/post-installation/10-install-user_oidc-app.sh: Updated dependencies
## Architecture Notes
- MCP Server is a protected resource (requires OAuth)
- MCP Client initiates OAuth flow and shares refresh tokens
- Refresh tokens enable background operations without admin credentials
- Supports future token exchange delegation when Keycloak implements it
## References
- ADR-002: Vector Database Background Sync Authentication
- RFC 6749: OAuth 2.0 (offline_access, refresh tokens)
- RFC 7517: JSON Web Key (JWK)
🤖 Generated with [Claude Code](https://claude.com/claude-code )
Co-Authored-By: Claude <noreply@anthropic.com >
2025-11-02 22:03:19 +01:00
Chris Coutinho
529dc4616b
docs: Implement separate clients architecture for Keycloak integration
...
Implements proper OAuth 2.0 separation following RFC 8707 best practices
with distinct resource server and OAuth client configurations.
## Architecture Changes
- Create separate "nextcloud" bearer-only client (resource server)
- Configure "nextcloud-mcp-server" OAuth client with audience mapper
- Audience mapper targets "nextcloud" resource server
- Token flow: aud="nextcloud", azp="nextcloud-mcp-server"
## Benefits
- Proper OAuth client vs resource server separation
- Support for future multi-resource tokens: aud=["nextcloud", "other-service"]
- RFC 8707 Resource Indicators compliance
- Clear requester identification via azp claim
## Documentation Updates
- Correct OAuth flow: MCP Client initiates, handles redirect, shares tokens
- Explain MCP Server as protected resource architecture
- Document offline_access with refresh tokens (Tier 1, current)
- Document token exchange with delegation (Tier 2, future when Keycloak adds support)
- Reference Keycloak issue #38279 for delegation status
## Files
- keycloak/realm-export.json: Add separate clients configuration
- app-hooks/post-installation/15-setup-keycloak-provider.sh: Setup user_oidc with "nextcloud" client
- docs/audience-validation-setup.md: Comprehensive documentation with corrected OAuth flow and delegation comparison
- docker-compose.yml: Fix Keycloak healthcheck (bash TCP instead of curl)
- scripts/test_separate_clients.sh: Verification script for architecture
🤖 Generated with [Claude Code](https://claude.com/claude-code )
Co-Authored-By: Claude <noreply@anthropic.com >
2025-11-02 22:03:19 +01:00
Chris Coutinho
f739330341
ci: fix typo
2025-11-02 22:03:19 +01:00
Chris Coutinho
136df2422b
build: Add keykloak to docker-compose.yml
2025-11-02 22:03:19 +01:00
Chris Coutinho
0f03541486
Merge branch 'master' of github.com:cbcoutinho/nextcloud-mcp-server
2025-10-31 02:59:53 +01:00
Chris Coutinho
ef07b1a6c9
docs: Add ADRs
2025-10-31 02:59:44 +01:00
Chris Coutinho
4f82357f24
ci: update submodule
2025-10-31 02:59:35 +01:00
Chris Coutinho
c4293b6750
Merge pull request #251 from cbcoutinho/renovate/docker.io-library-nginx-alpine
...
chore(deps): update docker.io/library/nginx:alpine docker digest to b3c656d
2025-10-30 20:23:52 +01:00
renovate-bot-cbcoutinho[bot]
72e4eb3d19
chore(deps): update docker.io/library/nginx:alpine docker digest to b3c656d
2025-10-30 17:06:28 +00:00
Chris Coutinho
47dd2df7aa
Merge pull request #250 from cbcoutinho/renovate/ghcr.io-astral-sh-uv-0.x
...
chore(deps): update ghcr.io/astral-sh/uv docker tag to v0.9.6
2025-10-30 12:55:02 +01:00
renovate-bot-cbcoutinho[bot]
9fd2022151
chore(deps): update ghcr.io/astral-sh/uv docker tag to v0.9.6
2025-10-29 23:07:53 +00:00
Chris Coutinho
b99dc52c95
docs: Update README with instructions on helm install
2025-10-29 12:47:20 +01:00
Chris Coutinho
78b27fb5e9
Merge pull request #249 from cbcoutinho/renovate/actions-checkout-5.x
...
chore(deps): update actions/checkout action to v5
2025-10-29 12:42:59 +01:00
renovate-bot-cbcoutinho[bot]
03e39a3f94
chore(deps): update actions/checkout action to v5
2025-10-29 11:28:09 +00:00
github-actions[bot]
5259658458
bump: version 0.22.6 → 0.22.7
nextcloud-mcp-server-0.22.7
v0.22.7
2025-10-29 11:18:41 +00:00
Chris Coutinho
e03a3c2e83
fix(helm): Remove image tag overide
2025-10-29 12:18:12 +01:00
Chris Coutinho
94cbd3015d
Merge pull request #248 from cbcoutinho/renovate/pin-dependencies
...
chore(deps): pin dependencies
2025-10-29 12:14:10 +01:00
renovate-bot-cbcoutinho[bot]
49a961cbcc
chore(deps): pin dependencies
2025-10-29 11:06:51 +00:00
github-actions[bot]
e1aca04aff
bump: version 0.22.5 → 0.22.6
nextcloud-mcp-server-0.22.6
v0.22.6
2025-10-29 10:57:44 +00:00
Chris Coutinho
3b12e585ca
fix(helm): Update helm chart with extraArgs
2025-10-29 11:57:13 +01:00
github-actions[bot]
e647c87dd8
bump: version 0.22.4 → 0.22.5
nextcloud-mcp-server-0.22.5
v0.22.5
2025-10-29 10:54:54 +00:00
Chris Coutinho
cb74157d51
fix: Update helm chart variables
2025-10-29 11:54:26 +01:00
github-actions[bot]
202058bdc8
bump: version 0.22.3 → 0.22.4
nextcloud-mcp-server-0.22.4
v0.22.4
2025-10-29 10:44:11 +00:00
Chris Coutinho
c312911538
fix(helm): Update helm version with release
2025-10-29 11:43:30 +01:00
Chris Coutinho
e602684743
fix(helm): Update helm version with release
2025-10-29 11:43:02 +01:00
github-actions[bot]
8221046d8a
bump: version 0.22.2 → 0.22.3
v0.22.3
2025-10-29 10:35:58 +00:00
Chris Coutinho
3e45b6ca25
fix(helm): Update helm version with release
2025-10-29 11:34:58 +01:00
github-actions[bot]
9ec7637579
bump: version 0.22.1 → 0.22.2
nextcloud-mcp-server-0.1.1
v0.22.2
2025-10-29 10:30:39 +00:00
Chris Coutinho
670188f9e4
fix(helm): Update helm version with release
2025-10-29 11:29:59 +01:00
github-actions[bot]
3878beaf65
bump: version 0.22.0 → 0.22.1
v0.22.1
2025-10-29 10:17:08 +00:00
Chris Coutinho
a5a0571bde
fix: Trigger release
2025-10-29 11:16:30 +01:00
github-actions[bot]
0e7e74867f
bump: version 0.21.0 → 0.22.0
nextcloud-mcp-server-0.1.0
v0.22.0
2025-10-29 09:32:27 +00:00
Chris Coutinho
a29045cca4
Merge pull request #246 from cbcoutinho/feature/helm-chart
...
Feature/helm chart
2025-10-29 10:32:02 +01:00
Chris Coutinho
b11c3ddfb6
build: Rename /helm -> /charts
2025-10-29 10:30:48 +01:00
Chris Coutinho
562c102711
feat(server): Add /live & /health endpoints
2025-10-29 10:29:30 +01:00
Chris Coutinho
3c3646bec2
Merge pull request #247 from cbcoutinho/renovate/docker.io-library-nginx-alpine
...
chore(deps): update docker.io/library/nginx:alpine docker digest to 9dacca6
2025-10-29 09:37:07 +01:00
renovate-bot-cbcoutinho[bot]
dd636e6a08
chore(deps): update docker.io/library/nginx:alpine docker digest to 9dacca6
2025-10-29 05:07:08 +00:00
Chris Coutinho
d7a8719d0e
build: Remove duplicate --host
2025-10-29 01:40:36 +01:00
Chris Coutinho
97fa9ef8a7
build: Update helm chart README and instructions
2025-10-29 01:37:08 +01:00
Chris Coutinho
77dd17b3e1
build: fix templating/linting errors
2025-10-29 01:37:07 +01:00
Chris Coutinho
d56ec33b77
build: update helm chart
2025-10-29 01:37:07 +01:00
Chris Coutinho
a1c5acc1c2
feat: Initialize helm chart
2025-10-29 01:37:03 +01:00
Chris Coutinho
e0de2e17e9
Merge pull request #245 from cbcoutinho/renovate/docker.io-library-nextcloud-32.0.1
...
chore(deps): update docker.io/library/nextcloud:32.0.1 docker digest to 1e4eae5
2025-10-28 09:19:39 +01:00
renovate-bot-cbcoutinho[bot]
4fc0cb5a41
chore(deps): update docker.io/library/nextcloud:32.0.1 docker digest to 1e4eae5
2025-10-27 23:10:34 +00:00
Chris Coutinho
ff9cca716b
Merge pull request #243 from cbcoutinho/renovate/astral-sh-setup-uv-digest
...
chore(deps): update astral-sh/setup-uv digest to 8585678
2025-10-26 22:00:45 +01:00
Chris Coutinho
ef4a82e589
Update .github/workflows/release.yml
2025-10-26 22:00:36 +01:00