Properties
category: reference
tags:
  - vs-2
  - oauth
  - mcp
  - infrastructure
last_updated: 2026-03-15

VS-2: Persistent MCP OAuth for Claude.ai

Problem

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.

Solution

Replaced InMemoryOAuthProvider with SQLiteOAuthProvider — a drop-in persistent provider backed by a local SQLite database.

Changes

File Description
otterwiki_mcp/oauth_store.py New SQLiteOAuthProvider implementing FastMCP's OAuthProvider interface
otterwiki_mcp/server.py Swapped InMemoryOAuthProvider for SQLiteOAuthProvider
otterwiki_mcp/config.py Added MCP_OAUTH_DB env var (default: mcp_oauth.db)
tests/test_oauth_store.py 20 tests covering registration, auth flow, tokens, refresh, revocation, persistence
tests/test_server_auth.py Updated to expect SQLiteOAuthProvider; uses :memory: DB for test isolation
.gitignore Added *.db pattern

Configuration

  • MCP_OAUTH_DB — Path to the SQLite database file. Default: mcp_oauth.db in the working directory. Set to :memory: for ephemeral (test) usage.

Token Lifetimes

Token Type Expiry
Authorization code 10 minutes
Access token 1 hour
Refresh token 30 days

Schema

Three tables, auto-created on first use:

  • oauth_clients — Registered OAuth clients (client_id, full client JSON, created_at)
  • oauth_codes — Authorization codes (code, client_id, redirect_uri, PKCE challenge, scopes, expiry, resource)
  • oauth_tokens — Access and refresh tokens stored as paired rows (token, client_id, scopes, expiry, type, paired counterpart, resource)

Token pairs are linked: revoking either token in a pair removes both.

Branch

feat/persistent-oauth on otterwiki-mcp repo. Not pushed, not merged.

Test Results

All 133 tests pass (20 new + 113 existing).

0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9