diff --git a/docs/auth-flows.md b/docs/auth-flows.md index ff74c53e..5ac142b9 100644 --- a/docs/auth-flows.md +++ b/docs/auth-flows.md @@ -125,8 +125,7 @@ Astrolabe MCP Server Nextcloud OIDC - Astrolabe has its own OAuth client registered in Nextcloud - Tokens are validated by the MCP server using Nextcloud OIDC JWKS - Authorization check: `token.sub == requested_resource_owner` - -> **Note:** The diagram and JWKS source above apply to **Multi-User BasicAuth**, where Nextcloud is the IdP. Under [Login Flow v2](#3-login-flow-v2) the MCP server is its own OAuth issuer and validates Bearer tokens against its **own** JWKS (or via local introspection of opaque tokens) — Nextcloud's JWKS is not involved on the MCP-client → MCP-server leg. See the Login Flow v2 section below. +- The same JWKS-based validation path applies under [Login Flow v2](#3-login-flow-v2) — the MCP server is an OIDC relying party of Nextcloud OIDC in both modes; Login Flow v2 only changes the MCP→Nextcloud credential leg (per-user app passwords). --- @@ -159,7 +158,7 @@ MCP Client MCP Server Nextcloud **Key characteristics:** - MCP client authenticates to MCP server via OAuth 2.1 + PKCE -- MCP server is the OAuth authorization server (DCR via RFC 7591) +- MCP server is an **OIDC relying party of Nextcloud OIDC** + an OAuth facade for MCP clients (RFC 7591 DCR for MCP-client registration; the server's own RP credentials come from `NEXTCLOUD_OIDC_CLIENT_ID/SECRET`, with DCR fallback). Tokens are signed by Nextcloud OIDC and validated against Nextcloud's JWKS. - `mcp:*` scopes (e.g. `mcp:notes.read`, `mcp:notes.write`) gate tool access - Per-user app password obtained via Login Flow v2 and stored encrypted in SQLite - App passwords appear in Nextcloud's **Settings → Security → Devices & Sessions** and are user-revocable diff --git a/docs/configuration.md b/docs/configuration.md index 55df25de..68f9e2fd 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -636,7 +636,7 @@ uv run nextcloud-mcp-server --no-oauth \ Pick the mode that matches your deployment topology — there is no single "always" answer: -- **Multi-user / hosted** — use [Login Flow v2](login-flow-v2.md). MCP clients authenticate via OAuth 2.1 + DCR (no pre-configured client to manage); per-user Nextcloud access is stored as encrypted app passwords. +- **Multi-user / hosted** — use [Login Flow v2](login-flow-v2.md). The MCP server registers with Nextcloud OIDC via static `NEXTCLOUD_OIDC_CLIENT_ID` / `NEXTCLOUD_OIDC_CLIENT_SECRET` (preferred) or RFC 7591 DCR (fallback); MCP clients authenticate via OAuth 2.1 + PKCE; per-user Nextcloud access is stored as encrypted app passwords. - **Internal multi-user** — Multi-User BasicAuth pass-through (clients send `Authorization: Basic` headers) is fully supported when users manage their own Nextcloud credentials. - **Personal / self-hosted** — Single-User BasicAuth with a Nextcloud app password is the simplest production setup. @@ -648,13 +648,20 @@ In all modes: ### For Docker -- Under Login Flow v2, mount the encrypted app-password store as a volume so per-user provisioning survives container restarts: - ```bash - docker run -v $(pwd)/data:/app/data --env-file .env \ - ghcr.io/cbcoutinho/nextcloud-mcp-server:latest --oauth - ``` - (`TOKEN_STORAGE_DB=/app/data/tokens.db` in `.env`.) -- Use Docker secrets for sensitive values in production (`TOKEN_ENCRYPTION_KEY`, `NEXTCLOUD_PASSWORD`, etc.) +Mount **two** volumes for OAuth-mode deployments: + +- `/app/.oauth` — DCR-registered MCP-client state (only used when DCR is the chosen registration path; harmless to mount otherwise). +- `/app/data` — encrypted app-password store under Login Flow v2 (`TOKEN_STORAGE_DB=/app/data/tokens.db`). + +```bash +docker run \ + -v $(pwd)/.oauth:/app/.oauth \ + -v $(pwd)/data:/app/data \ + --env-file .env \ + ghcr.io/cbcoutinho/nextcloud-mcp-server:latest --oauth +``` + +Use Docker secrets for sensitive values in production (`TOKEN_ENCRYPTION_KEY`, `NEXTCLOUD_OIDC_CLIENT_SECRET`, `NEXTCLOUD_PASSWORD`, etc.) --- diff --git a/docs/login-flow-v2.md b/docs/login-flow-v2.md index 47124667..1d73f30d 100644 --- a/docs/login-flow-v2.md +++ b/docs/login-flow-v2.md @@ -11,13 +11,15 @@ Two authentication legs, each with a different mechanism: ``` ┌─────────────────┐ OAuth/OIDC ┌──────────────────┐ App password ┌─────────────────┐ │ MCP Client │ ───────────────> │ MCP Server │ ────────────────> │ Nextcloud │ -│ (Claude, etc.) │ (mcp:* scopes) │ (OAuth issuer + │ (Basic Auth) │ (NC 16+) │ +│ (Claude, etc.) │ (mcp:* scopes) │ (OIDC RP of NC, │ (Basic Auth) │ (NC 16+, │ +│ │ │ OAuth facade, │ │ OIDC issuer) │ │ │ │ app-pwd holder) │ │ │ └─────────────────┘ └──────────────────┘ └─────────────────┘ ``` -- **MCP client → MCP server**: OAuth 2.1 with PKCE. The MCP server is the authorization server (or proxies an external IdP). Tokens carry `mcp:*` scopes that gate which tools the user can call. -- **MCP server → Nextcloud**: Per-user **app password** obtained via Nextcloud's native [Login Flow v2](https://docs.nextcloud.com/server/latest/developer_manual/client_apis/LoginFlow/index.html#login-flow-v2). Sent as HTTP Basic Auth. +- **MCP client → MCP server**: OAuth 2.1 with PKCE. The MCP server is **not** a standalone OAuth issuer — it acts as an OIDC relying party of Nextcloud OIDC and exposes an OAuth facade in front of it. Tokens are signed by Nextcloud's OIDC provider, validated by the MCP server against Nextcloud's JWKS, and carry `mcp:*` scopes that gate which tools the user can call. +- **MCP server → Nextcloud (auth leg)**: The MCP server registers itself with Nextcloud's OIDC provider via static `NEXTCLOUD_OIDC_CLIENT_ID`/`SECRET` (preferred) or RFC 7591 DCR (fallback). This relationship is used for OIDC discovery, JWKS retrieval, and token validation — *not* for proxying client tokens to Nextcloud APIs. +- **MCP server → Nextcloud (data leg)**: Per-user **app password** obtained via Nextcloud's native [Login Flow v2](https://docs.nextcloud.com/server/latest/developer_manual/client_apis/LoginFlow/index.html#login-flow-v2). Sent as HTTP Basic Auth. App passwords appear in **Settings → Security → Devices & Sessions** in Nextcloud and can be revoked by the user at any time. @@ -35,14 +37,20 @@ Scope enforcement happens at the MCP server layer (defense-in-depth). See [Scope # Nextcloud connection NEXTCLOUD_HOST=https://your.nextcloud.example.com -# Enable Login Flow v2 +# OIDC client credentials for the MCP server's relying-party relationship with Nextcloud OIDC. +# Preferred path: register a client in Nextcloud admin → OIDC and set these. If both are unset +# and Nextcloud advertises a `registration_endpoint`, the server falls back to RFC 7591 DCR. +NEXTCLOUD_OIDC_CLIENT_ID= +NEXTCLOUD_OIDC_CLIENT_SECRET= + +# Enable Login Flow v2 (per-user app-password provisioning for the MCP→Nextcloud data leg) ENABLE_LOGIN_FLOW=true # App-password storage (required for persistence across restarts) TOKEN_STORAGE_DB=/app/data/tokens.db TOKEN_ENCRYPTION_KEY= # see "Generating an encryption key" below -# Public OAuth issuer URL (the URL clients will be redirected to for browser flows) +# Public URLs (for browser redirects) NEXTCLOUD_MCP_SERVER_URL=https://mcp.example.com NEXTCLOUD_PUBLIC_ISSUER_URL=https://your.nextcloud.example.com # Public URL of Nextcloud ``` @@ -186,15 +194,15 @@ Clients can use this header to trigger **step-up authorization** — re-running Implementation: [`nextcloud_mcp_server/auth/scope_authorization.py`](../nextcloud_mcp_server/auth/scope_authorization.py). -## OAuth Issuer Endpoints +## OAuth Endpoints -When `--oauth` is enabled, the MCP server exposes OAuth 2.1 endpoints: +When `--oauth` is enabled, the MCP server exposes OAuth 2.1 endpoints. **These endpoints front Nextcloud OIDC** — discovery metadata, token issuance, and JWKS still come from Nextcloud; the MCP server is not a standalone OAuth issuer. | Endpoint | RFC | Purpose | |----------|-----|---------| -| `GET /.well-known/oauth-authorization-server` | RFC 8414 | Server metadata | +| `GET /.well-known/oauth-authorization-server` | RFC 8414 | Server metadata (advertises Nextcloud OIDC as the upstream issuer) | | `GET /.well-known/oauth-protected-resource/mcp` | RFC 9728 | PRM — advertises supported scopes (dynamically discovered from `@require_scopes`) | -| `POST /register` | RFC 7591 | Dynamic Client Registration | +| `POST /register` | RFC 7591 | Dynamic Client Registration (for MCP clients; see also `NEXTCLOUD_OIDC_CLIENT_ID/SECRET` for the MCP server's own RP credentials) | | `PUT/DELETE /register/{client_id}` | RFC 7592 | Client management with registration token | | `GET /authorize` | RFC 6749 | Authorization endpoint (PKCE required, S256) | | `POST /token` | RFC 6749 | Token endpoint | diff --git a/docs/running.md b/docs/running.md index be5016db..019eb0ef 100644 --- a/docs/running.md +++ b/docs/running.md @@ -17,7 +17,7 @@ Before running the server: Start the server using Docker: ```bash -# Login Flow v2 / OAuth issuer mode (--oauth, recommended for multi-user) +# OAuth mode (--oauth, recommended for multi-user; required by Login Flow v2) docker run -p 127.0.0.1:8000:8000 --env-file .env --rm \ ghcr.io/cbcoutinho/nextcloud-mcp-server:latest --oauth @@ -26,7 +26,7 @@ docker run -p 127.0.0.1:8000:8000 --env-file .env --rm \ ghcr.io/cbcoutinho/nextcloud-mcp-server:latest ``` -> **Note:** The `--oauth` flag turns on the OAuth-issuer layer used by [Login Flow v2](login-flow-v2.md), the recommended multi-user mode. It does **not** forward client OAuth tokens to Nextcloud — Nextcloud is always reached via per-user app passwords (Login Flow v2) or Basic Auth credentials. +> **Note:** Under `--oauth` the MCP server is an **OIDC relying party of Nextcloud OIDC** (validates client Bearer tokens against Nextcloud's JWKS) and exposes an OAuth facade for MCP clients. It does **not** forward client OAuth tokens to Nextcloud — Nextcloud is always reached via per-user app passwords ([Login Flow v2](login-flow-v2.md)) or Basic Auth credentials. The server will start on `http://127.0.0.1:8000` by default. @@ -36,20 +36,31 @@ The server will start on `http://127.0.0.1:8000` by default. ### Basic Docker Run -#### Login Flow v2 / OAuth issuer mode (`--oauth`) +#### OAuth Mode (`--oauth`, recommended for multi-user) -Recommended for multi-user deployments. The MCP server acts as the OAuth issuer for MCP clients; per-user Nextcloud access is obtained via [Login Flow v2](login-flow-v2.md) and stored as encrypted app passwords. +The `--oauth` flag turns on the OAuth/OIDC layer. In this mode the MCP server is an **OIDC relying party of Nextcloud OIDC** (it validates Bearer tokens against Nextcloud's JWKS) and exposes an OAuth facade for MCP clients. [Login Flow v2](login-flow-v2.md) is layered on top to acquire and store per-user Nextcloud app passwords. + +The MCP server registers itself with Nextcloud's OIDC provider in one of two ways: + +- **Static client (preferred)** — set `NEXTCLOUD_OIDC_CLIENT_ID` and `NEXTCLOUD_OIDC_CLIENT_SECRET` in `.env` (matching a client you registered in Nextcloud admin → OIDC). +- **Dynamic Client Registration (fallback)** — if the static creds aren't set and Nextcloud advertises a `registration_endpoint`, the server self-registers via RFC 7591. ```bash -# OAuth issuer with DCR (Dynamic Client Registration) +# OAuth with static (pre-registered) client — preferred +docker run -p 127.0.0.1:8000:8000 --env-file .env --rm \ + -e NEXTCLOUD_OIDC_CLIENT_ID=abc123 \ + -e NEXTCLOUD_OIDC_CLIENT_SECRET=xyz789 \ + ghcr.io/cbcoutinho/nextcloud-mcp-server:latest --oauth + +# OAuth with auto-registration (DCR) — used when static creds are absent docker run -p 127.0.0.1:8000:8000 --env-file .env --rm \ ghcr.io/cbcoutinho/nextcloud-mcp-server:latest --oauth -# OAuth issuer on a custom port +# OAuth on a custom port docker run -p 127.0.0.1:8080:8000 --env-file .env --rm \ ghcr.io/cbcoutinho/nextcloud-mcp-server:latest --oauth -# OAuth issuer with specific apps only +# OAuth with specific apps only docker run -p 127.0.0.1:8000:8000 --env-file .env --rm \ ghcr.io/cbcoutinho/nextcloud-mcp-server:latest --oauth \ --enable-app notes --enable-app calendar