Blame
|
1 | --- |
||||||
| 2 | category: reference |
|||||||
| 3 | tags: |
|||||||
| 4 | - vs-2 |
|||||||
| 5 | - oauth |
|||||||
| 6 | - mcp |
|||||||
| 7 | - infrastructure |
|||||||
| 8 | last_updated: 2026-03-15 |
|||||||
| 9 | --- |
|||||||
| 10 | ||||||||
| 11 | # VS-2: Persistent MCP OAuth for Claude.ai |
|||||||
| 12 | ||||||||
| 13 | ## Problem |
|||||||
| 14 | ||||||||
| 15 | The otterwiki-mcp server used FastMCP's `InMemoryOAuthProvider` for OAuth 2.1. All OAuth state (registered clients, authorization codes, access/refresh tokens) was lost on every server restart, forcing Claude.ai to re-authorize each time. |
|||||||
| 16 | ||||||||
| 17 | ## Solution |
|||||||
| 18 | ||||||||
| 19 | Replaced `InMemoryOAuthProvider` with `SQLiteOAuthProvider` — a drop-in persistent provider backed by a local SQLite database. |
|||||||
| 20 | ||||||||
| 21 | ## Changes |
|||||||
| 22 | ||||||||
| 23 | | File | Description | |
|||||||
| 24 | |------|-------------| |
|||||||
| 25 | | `otterwiki_mcp/oauth_store.py` | New SQLiteOAuthProvider implementing FastMCP's OAuthProvider interface | |
|||||||
| 26 | | `otterwiki_mcp/server.py` | Swapped InMemoryOAuthProvider for SQLiteOAuthProvider | |
|||||||
| 27 | | `otterwiki_mcp/config.py` | Added `MCP_OAUTH_DB` env var (default: `mcp_oauth.db`) | |
|||||||
| 28 | | `tests/test_oauth_store.py` | 20 tests covering registration, auth flow, tokens, refresh, revocation, persistence | |
|||||||
| 29 | | `tests/test_server_auth.py` | Updated to expect SQLiteOAuthProvider; uses `:memory:` DB for test isolation | |
|||||||
| 30 | | `.gitignore` | Added `*.db` pattern | |
|||||||
| 31 | ||||||||
| 32 | ## Configuration |
|||||||
| 33 | ||||||||
| 34 | - **`MCP_OAUTH_DB`** — Path to the SQLite database file. Default: `mcp_oauth.db` in the working directory. Set to `:memory:` for ephemeral (test) usage. |
|||||||
| 35 | ||||||||
| 36 | ## Token Lifetimes |
|||||||
| 37 | ||||||||
| 38 | | Token Type | Expiry | |
|||||||
| 39 | |------------|--------| |
|||||||
| 40 | | Authorization code | 10 minutes | |
|||||||
| 41 | | Access token | 1 hour | |
|||||||
| 42 | | Refresh token | 30 days | |
|||||||
| 43 | ||||||||
| 44 | ## Schema |
|||||||
| 45 | ||||||||
| 46 | Three tables, auto-created on first use: |
|||||||
| 47 | ||||||||
| 48 | - **oauth_clients** — Registered OAuth clients (client_id, full client JSON, created_at) |
|||||||
| 49 | - **oauth_codes** — Authorization codes (code, client_id, redirect_uri, PKCE challenge, scopes, expiry, resource) |
|||||||
| 50 | - **oauth_tokens** — Access and refresh tokens stored as paired rows (token, client_id, scopes, expiry, type, paired counterpart, resource) |
|||||||
| 51 | ||||||||
| 52 | Token pairs are linked: revoking either token in a pair removes both. |
|||||||
| 53 | ||||||||
| 54 | ## Branch |
|||||||
| 55 | ||||||||
| 56 | `feat/persistent-oauth` on otterwiki-mcp repo. Not pushed, not merged. |
|||||||
| 57 | ||||||||
| 58 | ## Test Results |
|||||||
| 59 | ||||||||
| 60 | All 133 tests pass (20 new + 113 existing). |
|||||||