# JWT OAuth Reference - Nextcloud MCP Server **Last Updated:** 2025-10-23 **Status:** Production Ready ## Table of Contents - [Overview](#overview) - [JWT vs Opaque Tokens](#jwt-vs-opaque-tokens) - [Scope-Based Authorization](#scope-based-authorization) - [Configuration](#configuration) - [Architecture](#architecture) - [Testing](#testing) - [Troubleshooting](#troubleshooting) - [Production Deployment](#production-deployment) --- ## Overview The Nextcloud MCP Server supports OAuth authentication with both **JWT** (RFC 9068) and **opaque** tokens. JWT tokens are recommended for production use as they enable: - **Faster validation** - No HTTP call needed for token verification - **Direct scope extraction** - Scopes embedded in token claims - **Dynamic tool filtering** - Users only see tools they have permission to use - **Signature verification** - Cryptographic validation using JWKS ### Key Features ✅ **JWT Token Support** - RFC 9068 compliant access tokens with RS256 signatures ✅ **Custom Scopes** - `nc:read` and `nc:write` for read/write access control ✅ **Dynamic Tool Filtering** - Tools filtered based on user's token scopes ✅ **Automatic Client Creation** - JWT OAuth clients auto-generated on container startup ✅ **Scope Challenges** - RFC-compliant `WWW-Authenticate` headers for insufficient scopes ✅ **Protected Resource Metadata** - RFC 8959 endpoint for scope discovery ✅ **Backward Compatible** - BasicAuth mode bypasses all scope checks ### Supported Scopes | Scope | Description | Tool Count | |-------|-------------|------------| | `nc:read` | Read-only access to Nextcloud data | 36 tools | | `nc:write` | Write access to create/modify/delete data | 54 tools | All MCP tools (90 total) require at least one of these scopes. Standard OIDC scopes (`openid`, `profile`, `email`) are also supported. --- ## JWT vs Opaque Tokens The Nextcloud OIDC app supports two token formats, configured per-client: ### JWT Tokens (Recommended) **Advantages:** - ✅ Fast validation - JWT signature verified locally using JWKS - ✅ Direct scope extraction from `scope` claim in payload - ✅ Standard approach (RFC 9068) - ✅ No additional HTTP calls for validation **Disadvantages:** - ⚠️ Larger size (~800-1200 chars vs 72 chars for opaque) - ⚠️ Token payload visible to client (not an issue for access tokens) **Token Structure:** ```json { "header": { "typ": "at+JWT", "alg": "RS256", "kid": "..." }, "payload": { "iss": "http://localhost:8080", "sub": "admin", "aud": "client_id", "exp": 1234567890, "iat": 1234567890, "scope": "openid profile email nc:read nc:write", "client_id": "...", "jti": "..." } } ``` ### Opaque Tokens **Advantages:** - ✅ Smaller size (72 characters) - ✅ No payload visible to client **Disadvantages:** - ❌ Requires userinfo endpoint call for each validation - ❌ Scopes must be inferred from userinfo response (not directly available) - ❌ Higher latency (HTTP call required) **When to Use:** - Use **JWT tokens** for production (better performance, direct scope access) - Use **opaque tokens** for compatibility with clients that don't support JWT --- ## Scope-Based Authorization ### Scope Definitions The MCP server uses **coarse-grained scopes** for simplicity: | Scope | Operations | Examples | |-------|------------|----------| | `nc:read` | Read-only access | Get notes, search files, list calendars, read contacts | | `nc:write` | Write operations | Create notes, update events, delete files, modify contacts | ### Standard OIDC Scopes | Scope | Description | Required | |-------|-------------|----------| | `openid` | OIDC authentication | Yes | | `profile` | User profile information | Recommended | | `email` | Email address | Recommended | ### Recommended Configurations **Full Access:** ``` openid profile email nc:read nc:write ``` **Read-Only:** ``` openid profile email nc:read ``` **No Custom Scopes (OIDC only):** ``` openid profile email ``` ### Implementation All 90 MCP tools are decorated with scope requirements: ```python @mcp.tool() @require_scopes("nc:read") async def nc_notes_get_note(note_id: int, ctx: Context): """Get a note by ID (requires nc:read scope)""" ... @mcp.tool() @require_scopes("nc:write") async def nc_notes_create_note(title: str, content: str, ctx: Context): """Create a note (requires nc:write scope)""" ... ``` **Coverage:** - ✅ 36 read tools decorated with `@require_scopes("nc:read")` - ✅ 54 write tools decorated with `@require_scopes("nc:write")` - ✅ 90/90 tools covered (100%) ### Dynamic Tool Filtering The MCP server implements **dynamic tool filtering** - users only see tools they have permission to use: **JWT with `nc:read` only:** - `list_tools()` returns 36 read-only tools - Write tools are hidden from the tool list **JWT with `nc:write` only:** - `list_tools()` returns 54 write-only tools - Read tools are hidden from the tool list **JWT with both scopes:** - `list_tools()` returns all 90 tools **JWT with no custom scopes:** - `list_tools()` returns 0 tools (all require `nc:read` or `nc:write`) **BasicAuth mode:** - `list_tools()` returns all 90 tools (no filtering) ### Scope Challenges When a tool is called without required scopes, the server returns a `403 Forbidden` response with a `WWW-Authenticate` header: ```http HTTP/1.1 403 Forbidden WWW-Authenticate: Bearer error="insufficient_scope", scope="nc:write", resource_metadata="http://server/.well-known/oauth-protected-resource" ``` This enables **step-up authorization** - clients can detect missing scopes and trigger re-authentication to obtain additional permissions. ### Protected Resource Metadata (PRM) The server implements RFC 8959's Protected Resource Metadata endpoint: **Endpoint:** `GET /.well-known/oauth-protected-resource` **Response:** ```json { "resource": "http://localhost:8002", "scopes_supported": ["nc:read", "nc:write"], "authorization_servers": ["http://localhost:8080"], "bearer_methods_supported": ["header"], "resource_signing_alg_values_supported": ["RS256"] } ``` This allows OAuth clients to discover supported scopes before requesting authorization. --- ## Configuration ### Docker Services The development environment includes three MCP server variants: | Service | Port | Auth Type | Token Type | Use Case | |---------|------|-----------|------------|----------| | `mcp` | 8000 | BasicAuth | N/A | Development, testing | | `mcp-oauth` | 8001 | OAuth | Opaque | Standard OAuth flows | | `mcp-oauth-jwt` | 8002 | OAuth | JWT | Production, JWT testing | ### JWT Service Configuration The `mcp-oauth-jwt` service uses automatic client creation: ```yaml mcp-oauth-jwt: build: . command: ["--transport", "streamable-http", "--oauth", "--port", "8002"] ports: - 127.0.0.1:8002:8002 environment: - NEXTCLOUD_HOST=http://app:80 - NEXTCLOUD_MCP_SERVER_URL=http://localhost:8002 - NEXTCLOUD_PUBLIC_ISSUER_URL=http://localhost:8080 - NEXTCLOUD_OIDC_CLIENT_STORAGE=/var/www/html/.oauth-jwt/nextcloud_oauth_client.json - NEXTCLOUD_OIDC_SCOPES=openid profile email nc:read nc:write - NEXTCLOUD_OIDC_TOKEN_TYPE=jwt volumes: - nextcloud:/var/www/html:ro ``` **Key Points:** - No `NEXTCLOUD_OIDC_CLIENT_ID` or `CLIENT_SECRET` needed (loaded from storage) - JWT client auto-created during Nextcloud initialization - Credentials stored in `/var/www/html/.oauth-jwt/nextcloud_oauth_client.json` - Volume mounted read-only for security ### Environment Variables | Variable | Description | Default | |----------|-------------|---------| | `NEXTCLOUD_HOST` | Nextcloud base URL | `http://localhost:8080` | | `NEXTCLOUD_MCP_SERVER_URL` | MCP server external URL | (required) | | `NEXTCLOUD_PUBLIC_ISSUER_URL` | Public issuer URL for JWT validation | (uses `NEXTCLOUD_HOST`) | | `NEXTCLOUD_OIDC_CLIENT_STORAGE` | Path to client credentials JSON | (optional - uses DCR if unset) | | `NEXTCLOUD_OIDC_SCOPES` | Space-separated scopes to request | `"openid profile email nc:read nc:write"` | | `NEXTCLOUD_OIDC_TOKEN_TYPE` | Token format: `"jwt"` or `"Bearer"` | `"Bearer"` | ### Manual Client Creation If not using automatic creation, create a JWT client manually: ```bash docker compose exec app php occ oidc:create \ --token_type=jwt \ --allowed_scopes="openid profile email nc:read nc:write" \ "Nextcloud MCP Server" \ "http://localhost:8002/oauth/callback" ``` **Output:** ```json { "client_id": "...", "client_secret": "...", "token_type": "jwt", "allowed_scopes": "openid profile email nc:read nc:write" } ``` Then configure the MCP server: ```bash export NEXTCLOUD_OIDC_CLIENT_ID="" export NEXTCLOUD_OIDC_CLIENT_SECRET="" export NEXTCLOUD_OIDC_TOKEN_TYPE="jwt" ``` --- ## Architecture ### Component Overview ``` ┌──────────────────┐ OAuth Flow ┌──────────────────┐ │ OAuth Client │<─────────────────────>│ Nextcloud OIDC │ │ (Claude, etc) │ │ Server │ └────────┬─────────┘ └────────┬─────────┘ │ │ │ JWT Access Token │ │ { │ │ "scope": "openid nc:read nc:write" │ │ ... │ │ } │ │ │ v │ ┌────────────────────────────────────────────────────────────┐ │ Nextcloud MCP Server │ │ ┌───────────────────────────────────────────────────┐ │ │ │ NextcloudTokenVerifier │ │ │ │ - JWT signature verification (JWKS) │ │ │ │ - Scope extraction from token payload │ │ │ │ - Fallback to userinfo endpoint (opaque tokens) │ │ │ └───────────────────┬───────────────────────────────┘ │ │ │ │ │ v │ │ ┌───────────────────────────────────────────────────┐ │ │ │ Dynamic Tool Filtering (list_tools) │ │ │ │ - Get user scopes from verified token │ │ │ │ - Filter tools based on @require_scopes metadata │ │ │ │ - Return only accessible tools │ │ │ └───────────────────┬───────────────────────────────┘ │ │ │ │ │ v │ │ ┌───────────────────────────────────────────────────┐ │ │ │ Tool Execution (@require_scopes decorator) │ │ │ │ - Check token scopes before execution │ │ │ │ - Raise InsufficientScopeError if missing │ │ │ │ - Return 403 with WWW-Authenticate header │ │ │ └───────────────────────────────────────────────────┘ │ └────────────────────────────────────────────────────────────┘ ``` ### Key Components **1. Token Verification** (`nextcloud_mcp_server/auth/token_verifier.py`) - JWT signature verification using JWKS (RS256) - Scope extraction from `scope` claim - Token caching with TTL - Fallback to userinfo endpoint for opaque tokens **2. Scope Authorization** (`nextcloud_mcp_server/auth/scope_authorization.py`) - `@require_scopes()` decorator for tools - `get_required_scopes()` - Extract scope requirements from functions - `has_required_scopes()` - Check if user has necessary scopes - `InsufficientScopeError` exception for WWW-Authenticate challenges **3. Dynamic Filtering** (`nextcloud_mcp_server/app.py:433-488`) - Overrides FastMCP's `list_tools()` method - Filters based on user's JWT token scopes - Only active in OAuth mode - Bypassed in BasicAuth mode **4. PRM Endpoint** (`nextcloud_mcp_server/app.py:503-532`) - `GET /.well-known/oauth-protected-resource` - Advertises `["nc:read", "nc:write"]` - RFC 8959 compliant **5. Exception Handler** (`nextcloud_mcp_server/app.py:540-563`) - Catches `InsufficientScopeError` - Returns 403 with `WWW-Authenticate` header - Includes missing scopes and PRM endpoint URL ### Automatic Client Creation **Post-Installation Hook:** `app-hooks/post-installation/90-create-jwt-oauth-client.sh` On container startup, this script: 1. Installs/enables OIDC app 2. Creates JWT client via `occ oidc:create --token_type=jwt` 3. Parses JSON output for credentials 4. Saves to `/var/www/html/.oauth-jwt/nextcloud_oauth_client.json` **Credential Format:** ```json { "client_id": "XBd2xqIisu3Kswg39Ub4BUhC36PEYjwwivx3G5nZdDgigvwKXrTHozs7m9DeoLSY", "client_secret": "xNKcy0qpUSau36T60pGGdb03pMEVLXtqykxjK8YkDpoNxNcZ4ClyAT3IAEse2AKT", "client_id_issued_at": 1761097039, "client_secret_expires_at": 2076457039, "redirect_uris": ["http://localhost:8002/oauth/callback"] } ``` --- ## Testing ### Test Infrastructure The test suite includes comprehensive coverage for JWT OAuth and scope authorization: **Test Files:** - `tests/server/test_scope_authorization.py` - Scope-based authorization tests (4 tests) - `tests/server/test_mcp_oauth_jwt.py` - JWT OAuth integration tests - `tests/conftest.py` - Shared fixtures for JWT testing ### Consent Scenario Tests Four test scenarios verify scope-based tool filtering with different consent levels: #### 1. No Custom Scopes (0 tools) ```bash uv run pytest tests/server/test_scope_authorization.py::test_jwt_with_no_custom_scopes_returns_zero_tools -v ``` **Scenario:** JWT token with only OIDC defaults (`openid profile email`) **Expected:** 0 tools returned (all require `nc:read` or `nc:write`) **Verifies:** Security - users who decline custom scopes cannot access any MCP tools #### 2. Read-Only Access (36 tools) ```bash uv run pytest tests/server/test_scope_authorization.py::test_jwt_consent_scenarios_read_only -v ``` **Scenario:** JWT token with `nc:read` only **Expected:** 36 read-only tools visible, write tools hidden **Verifies:** Read tools accessible, write tools filtered out #### 3. Write-Only Access (54 tools) ```bash uv run pytest tests/server/test_scope_authorization.py::test_jwt_consent_scenarios_write_only -v ``` **Scenario:** JWT token with `nc:write` only **Expected:** 54 write tools visible, read tools hidden **Verifies:** Write tools accessible, read tools filtered out #### 4. Full Access (90 tools) ```bash uv run pytest tests/server/test_scope_authorization.py::test_jwt_consent_scenarios_full_access -v ``` **Scenario:** JWT token with both `nc:read` and `nc:write` **Expected:** All 90 tools visible **Verifies:** Full access when user grants all custom scopes ### Test Fixtures **OAuth Client Fixtures:** - `read_only_oauth_client_credentials` - Client with `nc:read` only - `write_only_oauth_client_credentials` - Client with `nc:write` only - `full_access_oauth_client_credentials` - Client with both scopes - `no_custom_scopes_oauth_client_credentials` - Client with OIDC defaults only **Token Fixtures:** - `playwright_oauth_token_read_only` - Obtains token with `nc:read` - `playwright_oauth_token_write_only` - Obtains token with `nc:write` - `playwright_oauth_token_full_access` - Obtains token with both scopes - `playwright_oauth_token_no_custom_scopes` - Obtains token with no custom scopes **MCP Client Fixtures:** - `nc_mcp_oauth_client_read_only` - MCP session with read-only token - `nc_mcp_oauth_client_write_only` - MCP session with write-only token - `nc_mcp_oauth_client_full_access` - MCP session with full access token - `nc_mcp_oauth_client_no_custom_scopes` - MCP session with no custom scopes ### Running Tests **All consent scenario tests:** ```bash uv run pytest tests/server/test_scope_authorization.py -v ``` **JWT OAuth integration tests:** ```bash uv run pytest tests/server/test_mcp_oauth_jwt.py -v --browser firefox ``` **With visible browser (debugging):** ```bash uv run pytest tests/server/test_mcp_oauth_jwt.py -v --browser firefox --headed ``` ### Test Configuration **Playwright Browser:** - Default: Chromium - Recommended for CI: Firefox (`--browser firefox`) - Debugging: Add `--headed` flag **OAuth Flow:** - Uses automated Playwright browser automation - Completes OAuth consent flow programmatically - Creates separate OAuth client for each scenario - Each user gets unique access token --- ## Troubleshooting ### Issue: JWT Issuer Validation Failed **Symptom:** ``` WARNING JWT issuer validation failed: Invalid issuer WARNING JWT verification failed, will try other methods ✅ Extracted scopes from access token: {'openid', 'profile'} ``` **Cause:** Token's `iss` claim doesn't match expected issuer URL. This often happens when: - Using `localhost` vs `127.0.0.1` inconsistently - MCP server uses internal URL but clients use public URL **Solution:** ```bash # Option 1: Use consistent URLs export NEXTCLOUD_PUBLIC_ISSUER_URL=http://localhost:8080 # Ensure all test fixtures also use localhost:8080 # Option 2: Check discovery document curl http://localhost:8080/.well-known/openid-configuration | jq .issuer # Use this exact issuer in NEXTCLOUD_PUBLIC_ISSUER_URL ``` **Impact if not fixed:** - JWT validation falls back to userinfo endpoint - Scopes inferred from userinfo (only standard OIDC scopes, no custom scopes) - Result: 0 tools visible or incorrect tool filtering ### Issue: Scopes Not Present in JWT **Symptom:** JWT token doesn't contain `scope` claim or contains empty string **Cause:** Client's `allowed_scopes` is empty or not configured **Solution:** ```bash # Check client configuration docker compose exec app php occ oidc:list # Look for allowed_scopes in output # If empty, recreate client with --allowed_scopes docker compose exec app php occ oidc:create \ --token_type=jwt \ --allowed_scopes="openid profile email nc:read nc:write" \ "Client Name" \ "http://callback/url" ``` ### Issue: All Tools Visible Despite Read-Only Token **Symptom:** User with `nc:read` token can see all 90 tools including write tools **Cause:** Server running in BasicAuth mode, not OAuth mode **Solution:** ```bash # Verify OAuth mode is active docker compose logs mcp-oauth-jwt | grep "OAuth mode" # Should see: "Running in OAuth mode" # If not, check environment variables: docker compose exec mcp-oauth-jwt env | grep NEXTCLOUD_OIDC # Ensure no NEXTCLOUD_USERNAME or NEXTCLOUD_PASSWORD set ``` ### Issue: Dynamic Client Registration Doesn't Set Scopes **Symptom:** Client registered via DCR has empty `allowed_scopes` **Cause:** OIDC app's registration endpoint doesn't populate `allowed_scopes` from request **Workaround:** Use pre-configured clients instead of DCR for JWT tokens: ```bash # Create client manually via occ docker compose exec app php occ oidc:create \ --token_type=jwt \ --allowed_scopes="openid profile email nc:read nc:write" \ "Client Name" \ "http://callback" # Configure MCP server to use these credentials export NEXTCLOUD_OIDC_CLIENT_ID="..." export NEXTCLOUD_OIDC_CLIENT_SECRET="..." ``` ### Issue: Token Type Case Sensitivity **Symptom:** JWT tokens not generated even though `token_type=JWT` set **Cause:** OIDC app checks `token_type === 'jwt'` (lowercase) **Solution:** Always use lowercase: ```bash # Correct export NEXTCLOUD_OIDC_TOKEN_TYPE=jwt # Incorrect (will generate opaque tokens) export NEXTCLOUD_OIDC_TOKEN_TYPE=JWT ``` ### Issue: Missing WWW-Authenticate Header **Symptom:** 403 error doesn't include `WWW-Authenticate` header **Cause:** Server not in OAuth mode, or exception not being caught **Solution:** ```bash # Check server logs for OAuth mode docker compose logs mcp-oauth-jwt | grep "WWW-Authenticate scope challenges enabled" # Should see this during startup # Check exception handling docker compose logs mcp-oauth-jwt | grep "InsufficientScopeError" ``` ### Debugging Tools **Check JWT contents:** ```bash # Decode JWT (base64 decode the payload) echo "JWT_PAYLOAD_PART" | base64 -d | jq . ``` **Check database scopes:** ```bash # View access tokens with scopes docker compose exec db mariadb -u nextcloud -ppassword nextcloud \ -e "SELECT id, client_id, user_id, scope FROM oc_oidc_access_tokens ORDER BY id DESC LIMIT 5;" # View user consents docker compose exec db mariadb -u nextcloud -ppassword nextcloud \ -e "SELECT user_id, client_id, scopes_granted FROM oc_oidc_user_consents;" ``` **Check server logs:** ```bash # Follow JWT verification logs docker compose logs -f mcp-oauth-jwt | grep -E "JWT|scope|tool" # Check for issuer mismatches docker compose logs mcp-oauth-jwt | grep -i issuer ``` --- ## Production Deployment ### Deployment Checklist ✅ **Use JWT Tokens** - Enable `token_type=jwt` for better performance ✅ **Configure Allowed Scopes** - Always set `allowed_scopes` on OAuth clients ✅ **Use Pre-Configured Clients** - Avoid DCR limitation with manual client creation ✅ **Consistent URLs** - Use same URL for `NEXTCLOUD_HOST` and `PUBLIC_ISSUER_URL` ✅ **Secure Credentials** - Store client credentials securely (environment variables or secrets management) ✅ **Monitor Token Size** - JWT tokens are 10-15x larger than opaque (not usually an issue) ✅ **Enable Logging** - Configure appropriate log levels for JWT verification ### Production Configuration Example ```yaml # docker-compose.yml (production) mcp-oauth-jwt: image: ghcr.io/yourusername/nextcloud-mcp-server:latest environment: - NEXTCLOUD_HOST=https://nextcloud.example.com - NEXTCLOUD_MCP_SERVER_URL=https://mcp.example.com - NEXTCLOUD_PUBLIC_ISSUER_URL=https://nextcloud.example.com - NEXTCLOUD_OIDC_CLIENT_ID=${JWT_CLIENT_ID} - NEXTCLOUD_OIDC_CLIENT_SECRET=${JWT_CLIENT_SECRET} - NEXTCLOUD_OIDC_SCOPES=openid profile email nc:read nc:write - NEXTCLOUD_OIDC_TOKEN_TYPE=jwt ports: - "8002:8002" ``` ### Security Considerations **Token Storage:** - Never commit credentials to version control - Use environment variables or secrets management - Rotate client secrets periodically **Scope Configuration:** - Grant minimum necessary scopes to clients - Use read-only tokens for AI assistants that don't need write access - Review OAuth client list regularly **Network Security:** - Use HTTPS in production - Ensure issuer URL matches public URL - Configure proper CORS headers ### Monitoring **Key Metrics:** - JWT verification success/failure rate - Scope challenge frequency (indicates clients with insufficient scopes) - Token validation latency - Tool execution by scope (identify unused scopes) **Log Patterns:** ```bash # Success INFO JWT verified successfully for user: admin INFO ✅ Extracted scopes from access token: {'openid', 'profile', 'email', 'nc:read', 'nc:write'} # Failures WARNING JWT issuer validation failed: Invalid issuer WARNING Missing required scopes: nc:write ``` ### Known Limitations 1. **No Fine-Grained Scopes** - Only coarse `nc:read` and `nc:write` (not per-app scopes) 2. **DCR Doesn't Set Allowed Scopes** - Must use pre-configured clients for JWT 3. **No Introspection Endpoint** - OIDC app lacks RFC 7662 introspection 4. **No Refresh Token Support** - Tokens must be reacquired when expired ### Future Enhancements **Potential Improvements:** - Per-app scopes (`nc:notes:read`, `nc:calendar:write`) - Resource-level filtering (apply to MCP resources, not just tools) - Automatic scope discovery from decorated tools - Admin UI for scope management - Token introspection endpoint (RFC 7662) --- ## References ### Standards - [RFC 9068: JWT Profile for OAuth 2.0 Access Tokens](https://www.rfc-editor.org/rfc/rfc9068.html) - [RFC 7519: JSON Web Token (JWT)](https://www.rfc-editor.org/rfc/rfc7519.html) - [RFC 7517: JSON Web Key (JWK)](https://www.rfc-editor.org/rfc/rfc7517.html) - [RFC 8959: Protected Resource Metadata](https://www.rfc-editor.org/rfc/rfc8959.html) - [RFC 7662: OAuth 2.0 Token Introspection](https://www.rfc-editor.org/rfc/rfc7662.html) ### Related Documentation - [OAuth Setup Guide](oauth-setup.md) - Complete OAuth configuration guide - [OAuth Architecture](oauth-architecture.md) - Detailed architecture documentation - [OAuth Troubleshooting](oauth-troubleshooting.md) - Common OAuth issues and solutions - [Authentication Guide](authentication.md) - BasicAuth vs OAuth comparison ### External Resources - [Nextcloud OIDC App](https://github.com/H2CK/oidc) - OIDC identity provider for Nextcloud - [PyJWT Documentation](https://pyjwt.readthedocs.io/) - JWT library used for verification - [FastMCP Documentation](https://github.com/jlowin/fastmcp) - MCP server framework --- **Implementation Date:** 2025-10-21 to 2025-10-23 **Version:** 1.0.0 **Status:** ✅ Production Ready