From 5ceafe425c6b9301c6672e41efb8537ff6c0469f Mon Sep 17 00:00:00 2001 From: Jeremiah Lowin <153965+jlowin@users.noreply.github.com> Date: Sun, 26 Oct 2025 21:07:54 -0400 Subject: [PATCH] Fix OAuth token storage documentation (#2272) Correct imports (DiskStore not FileStore) and simplify structure. --- docs/clients/auth/oauth.mdx | 105 ++++-------------------------------- 1 file changed, 11 insertions(+), 94 deletions(-) diff --git a/docs/clients/auth/oauth.mdx b/docs/clients/auth/oauth.mdx index 47293aae5..090afbd30 100644 --- a/docs/clients/auth/oauth.mdx +++ b/docs/clients/auth/oauth.mdx @@ -92,96 +92,27 @@ If the access token expires, the client will automatically use the refresh token -## Token Management - -### Token Storage +## Token Storage +By default, tokens are stored in memory and lost when your application restarts. For persistent storage, pass an `AsyncKeyValue`-compatible storage backend to the `token_storage` parameter. + -**Security Consideration**: MCP clients can accumulate OAuth credentials for many different servers over time. Unlike single-service CLI tools (like `gh` or `gcloud`), a compromised token store could expose access to multiple services. Use encrypted storage for production use. +**Security Consideration**: Use encrypted storage for production. MCP clients can accumulate OAuth credentials for many servers over time, and a compromised token store could expose access to multiple services. -By default, tokens are stored in memory and lost when your application restarts. For persistent storage, provide an `AsyncKeyValue`-compatible storage backend to the `token_storage` parameter. - -#### In-Memory Storage (Default) - ```python from fastmcp import Client from fastmcp.client.auth import OAuth - -# Default: tokens stored in memory, lost on restart -oauth = OAuth(mcp_url="https://fastmcp.cloud/mcp") - -async with Client("https://fastmcp.cloud/mcp", auth=oauth) as client: - await client.ping() -``` - -#### Encrypted Disk Storage (Recommended) - -For production use where your client connects to multiple MCP servers, use encrypted storage to protect accumulated credentials: - -```python -from fastmcp import Client -from fastmcp.client.auth import OAuth -from key_value.aio.stores.file import FileStore +from key_value.aio.stores.disk import DiskStore from key_value.aio.wrappers.encryption import FernetEncryptionWrapper from cryptography.fernet import Fernet import os -# Generate encryption key (store securely, e.g., in environment variable) -# On first run: Fernet.generate_key() -> save to secure location -encryption_key = os.environ["OAUTH_STORAGE_ENCRYPTION_KEY"] - -# Create encrypted file storage +# Create encrypted disk storage encrypted_storage = FernetEncryptionWrapper( - key_value=FileStore(base_path="~/.fastmcp/oauth-tokens"), - fernet=Fernet(encryption_key) -) - -oauth = OAuth( - mcp_url="https://fastmcp.cloud/mcp", - token_storage=encrypted_storage -) - -async with Client("https://fastmcp.cloud/mcp", auth=oauth) as client: - await client.ping() -``` - - -The `FernetEncryptionWrapper` provides AES-128-CBC encryption with HMAC-SHA256 authentication. Your encryption key should be at least 32 bytes and stored securely (environment variables, system keychain, or secrets manager). - - -#### Plaintext Disk Storage (Development Only) - - -**Not recommended**: Plaintext storage accumulates credentials for multiple MCP servers without encryption. Only use for local development on secure machines. - - -```python -from key_value.aio.stores.file import FileStore - -# Development only - plaintext storage -oauth = OAuth( - mcp_url="https://fastmcp.cloud/mcp", - token_storage=FileStore(base_path="~/.fastmcp/oauth-tokens") -) -``` - -### Custom Storage Backends - -You can use any `AsyncKeyValue`-compatible storage backend. See the [key-value library](https://github.com/jlowin/key-value) for available options including Redis, DynamoDB, and more. - -For multi-server deployments, wrap your storage in `FernetEncryptionWrapper`: - -```python -from key_value.aio.stores.redis import RedisStore -from key_value.aio.wrappers.encryption import FernetEncryptionWrapper -from cryptography.fernet import Fernet -import os - -encrypted_storage = FernetEncryptionWrapper( - key_value=RedisStore(host="redis.example.com", port=6379), + key_value=DiskStore(directory="~/.fastmcp/oauth-tokens"), fernet=Fernet(os.environ["OAUTH_STORAGE_ENCRYPTION_KEY"]) ) @@ -189,23 +120,9 @@ oauth = OAuth( mcp_url="https://fastmcp.cloud/mcp", token_storage=encrypted_storage ) + +async with Client("https://fastmcp.cloud/mcp", auth=oauth) as client: + await client.ping() ``` -### Managing Stored Tokens - -To clear tokens for a specific server: - -```python -# Clear tokens for one server -await oauth.token_storage_adapter.clear() -``` - -To clear all tokens across all storage: - -```python -# Clear all tokens in the storage backend -from key_value.aio.stores.file import FileStore - -storage = FileStore(base_path="~/.fastmcp/oauth-tokens") -await storage.clear() -``` +You can use any `AsyncKeyValue`-compatible backend from the [key-value library](https://github.com/jlowin/key-value) including Redis, DynamoDB, and more. Wrap your storage in `FernetEncryptionWrapper` for encryption.