From b362444ddf9b631237a0c1107cddf93a4fa4465c Mon Sep 17 00:00:00 2001 From: Jeremiah Lowin <153965+jlowin@users.noreply.github.com> Date: Sun, 19 Oct 2025 19:24:13 -0400 Subject: [PATCH] Add storage backend documentation (#2137) * Add storage backend documentation * Add storage patterns documentation for wrapper caching strategies - Add PassthroughCacheWrapper section for multi-tier caching - Document TTL clamping strategy for optimized memory usage - Add example for wrapping custom storage implementations - Explain how to combine fast in-memory caches with persistent remote stores * Update docs --- docs/clients/elicitation.mdx | 1 - docs/development/upgrade-guide.mdx | 1 + docs/docs.json | 9 +- docs/servers/auth/authentication.mdx | 1 - docs/servers/auth/full-oauth-server.mdx | 2 +- docs/servers/auth/oauth-proxy.mdx | 26 ++- docs/servers/auth/token-verification.mdx | 1 - docs/servers/composition.mdx | 2 +- docs/servers/elicitation.mdx | 1 - docs/servers/icons.mdx | 1 + docs/servers/middleware.mdx | 36 +++- docs/servers/storage-backends.mdx | 259 +++++++++++++++++++++++ 12 files changed, 322 insertions(+), 18 deletions(-) create mode 100644 docs/servers/storage-backends.mdx diff --git a/docs/clients/elicitation.mdx b/docs/clients/elicitation.mdx index c11009822..15b0e5030 100644 --- a/docs/clients/elicitation.mdx +++ b/docs/clients/elicitation.mdx @@ -3,7 +3,6 @@ title: User Elicitation sidebarTitle: Elicitation description: Handle server-initiated user input requests with structured schemas. icon: message-question -tag: NEW --- import { VersionBadge } from "/snippets/version-badge.mdx"; diff --git a/docs/development/upgrade-guide.mdx b/docs/development/upgrade-guide.mdx index 4aba64dbc..47108912c 100644 --- a/docs/development/upgrade-guide.mdx +++ b/docs/development/upgrade-guide.mdx @@ -3,6 +3,7 @@ title: Upgrade Guide sidebarTitle: Upgrade Guide description: Migration instructions for upgrading between FastMCP versions icon: up +tag: NEW --- This guide provides migration instructions for breaking changes and major updates when upgrading between FastMCP versions. diff --git a/docs/docs.json b/docs/docs.json index 1d39e4e4c..8f11677d5 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -107,15 +107,16 @@ "group": "Advanced Features", "icon": "stars", "pages": [ - "servers/icons", - "servers/context", - "servers/proxy", "servers/composition", + "servers/context", "servers/elicitation", + "servers/icons", "servers/logging", + "servers/middleware", "servers/progress", + "servers/proxy", "servers/sampling", - "servers/middleware" + "servers/storage-backends" ] }, { diff --git a/docs/servers/auth/authentication.mdx b/docs/servers/auth/authentication.mdx index 9b5583002..b366066e5 100644 --- a/docs/servers/auth/authentication.mdx +++ b/docs/servers/auth/authentication.mdx @@ -3,7 +3,6 @@ title: Authentication sidebarTitle: Overview description: Secure your FastMCP server with flexible authentication patterns, from simple API keys to full OAuth 2.1 integration with external identity providers. icon: user-shield -tag: NEW --- import { VersionBadge } from "/snippets/version-badge.mdx" diff --git a/docs/servers/auth/full-oauth-server.mdx b/docs/servers/auth/full-oauth-server.mdx index da6b81519..529a01784 100644 --- a/docs/servers/auth/full-oauth-server.mdx +++ b/docs/servers/auth/full-oauth-server.mdx @@ -3,7 +3,7 @@ title: Full OAuth Server sidebarTitle: Full OAuth Server description: Build a self-contained authentication system where your FastMCP server manages users, issues tokens, and validates them. icon: users-between-lines -tag: NEW + --- import { VersionBadge } from "/snippets/version-badge.mdx" diff --git a/docs/servers/auth/oauth-proxy.mdx b/docs/servers/auth/oauth-proxy.mdx index 17bcbd412..21ad2b917 100644 --- a/docs/servers/auth/oauth-proxy.mdx +++ b/docs/servers/auth/oauth-proxy.mdx @@ -210,16 +210,34 @@ These parameters are included in all token requests to the upstream provider. - + Storage backend for persisting OAuth client registrations and encrypted upstream tokens. By default, clients are automatically persisted to disk in `~/.config/fastmcp/oauth-proxy-clients/`, allowing them to survive server restarts as long as the filesystem remains accessible. This means MCP clients only need to register once and can reconnect seamlessly after your server restarts. -For production deployments with token persistence, use this with `jwt_signing_key` and `token_encryption_key` - all three work together to ensure tokens survive restarts. See [OAuth Token Security](/deployment/http#oauth-token-security). +For production deployments with multiple servers or cloud deployments, see [Storage Backends](/servers/storage-backends) for options including Redis, DynamoDB, and custom implementations. + +For production token persistence, use this with `jwt_signing_key` and `token_encryption_key` - all three work together to ensure tokens survive restarts. See [OAuth Token Security](/deployment/http#oauth-token-security). + +Testing with in-memory storage: ```python -from fastmcp.utilities.storage import InMemoryStorage +from key_value.aio.stores.memory import MemoryStore # Use in-memory storage for testing (clients lost on restart) -auth = OAuthProxy(..., client_storage=InMemoryStorage()) +auth = OAuthProxy(..., client_storage=MemoryStore()) +``` + +Production with Redis for distributed deployments: + +```python +from key_value.aio.stores.redis import RedisStore +import os + +auth = OAuthProxy( + ..., + jwt_signing_key=os.environ["JWT_SIGNING_KEY"], + token_encryption_key=os.environ["TOKEN_ENCRYPTION_KEY"], + client_storage=RedisStore(host="redis.example.com", port=6379) +) ``` diff --git a/docs/servers/auth/token-verification.mdx b/docs/servers/auth/token-verification.mdx index 637ff77a2..d3380d4ff 100644 --- a/docs/servers/auth/token-verification.mdx +++ b/docs/servers/auth/token-verification.mdx @@ -3,7 +3,6 @@ title: Token Verification sidebarTitle: Token Verification description: Protect your server by validating bearer tokens issued by external systems. icon: key -tag: NEW --- import { VersionBadge } from "/snippets/version-badge.mdx" diff --git a/docs/servers/composition.mdx b/docs/servers/composition.mdx index 4ca1895ce..d90bb47c7 100644 --- a/docs/servers/composition.mdx +++ b/docs/servers/composition.mdx @@ -1,6 +1,6 @@ --- title: Server Composition -sidebarTitle: Server Composition +sidebarTitle: Composition description: Combine multiple FastMCP servers into a single, larger application using mounting and importing. icon: puzzle-piece --- diff --git a/docs/servers/elicitation.mdx b/docs/servers/elicitation.mdx index 88e426f40..7b07c0fdb 100644 --- a/docs/servers/elicitation.mdx +++ b/docs/servers/elicitation.mdx @@ -3,7 +3,6 @@ title: User Elicitation sidebarTitle: Elicitation description: Request structured input from users during tool execution through the MCP context. icon: message-question -tag: NEW --- import { VersionBadge } from '/snippets/version-badge.mdx' diff --git a/docs/servers/icons.mdx b/docs/servers/icons.mdx index 5dfd828fa..f14b4d6f5 100644 --- a/docs/servers/icons.mdx +++ b/docs/servers/icons.mdx @@ -2,6 +2,7 @@ title: Icons description: Add visual icons to your servers, tools, resources, and prompts icon: image +tag: NEW --- import { VersionBadge } from '/snippets/version-badge.mdx' diff --git a/docs/servers/middleware.mdx b/docs/servers/middleware.mdx index b38a0a649..7b5246bdd 100644 --- a/docs/servers/middleware.mdx +++ b/docs/servers/middleware.mdx @@ -461,9 +461,9 @@ from fastmcp.server.middleware.caching import ResponseCachingMiddleware mcp.add_middleware(ResponseCachingMiddleware()) ``` -Out of the box, it caches call/list tool, resources, and prompts to an in-memory cache. Sending a notification of a tool/resource/prompt change will invalidate the cache for the affected method. List calls are stored under global keys, if you share a key_value backend across servers, keep this in mind and consider using the PrefixCollectionsWrapper in py-key-value-aio to namespace collections by server. +Out of the box, it caches call/list tool, resources, and prompts to an in-memory cache with TTL-based expiration. Cache entries expire based on their TTL; there is no event-based cache invalidation. List calls are stored under global keys—when sharing a storage backend across multiple servers, consider namespacing collections to prevent conflicts. See [Storage Backends](/servers/storage-backends) for advanced configuration options. -Each method can be configured individually, for example, caching list tools for 30 seconds, skipping caching for tools other than `tool1` and not caching and requests to read resources: +Each method can be configured individually, for example, caching list tools for 30 seconds, limiting caching to specific tools, and disabling caching for resource reads: ```python from fastmcp.server.middleware.caching import ResponseCachingMiddleware, CallToolSettings, ListToolsSettings, ReadResourceSettings @@ -481,7 +481,11 @@ mcp.add_middleware(ResponseCachingMiddleware( )) ``` -It can also be configured to cache to disk: +#### Storage Backends + +By default, caching uses in-memory storage, which is fast but doesn't persist across restarts. For production or persistent caching across server restarts, configure a different storage backend. See [Storage Backends](/servers/storage-backends) for complete options including disk, Redis, DynamoDB, and custom implementations. + +Disk-based caching example: ```python from fastmcp.server.middleware.caching import ResponseCachingMiddleware @@ -492,7 +496,31 @@ mcp.add_middleware(ResponseCachingMiddleware( )) ``` -See the Contrib modules for caching middleware implementations that support additional features like distributed caching. +Redis for distributed deployments: + +```python +from fastmcp.server.middleware.caching import ResponseCachingMiddleware +from key_value.aio.stores.redis import RedisStore + +mcp.add_middleware(ResponseCachingMiddleware( + cache_storage=RedisStore(host="redis.example.com", port=6379), +)) +``` + +#### Cache Statistics + +The caching middleware collects operation statistics (hits, misses, etc.) through the underlying storage layer. Access statistics from the middleware instance: + +```python +from fastmcp.server.middleware.caching import ResponseCachingMiddleware + +middleware = ResponseCachingMiddleware() +mcp.add_middleware(middleware) + +# Later, retrieve statistics +stats = middleware.statistics() +print(f"Total cache operations: {stats}") +``` ### Logging Middleware diff --git a/docs/servers/storage-backends.mdx b/docs/servers/storage-backends.mdx new file mode 100644 index 000000000..dbb4e185f --- /dev/null +++ b/docs/servers/storage-backends.mdx @@ -0,0 +1,259 @@ +--- +title: Storage Backends +sidebarTitle: Storage Backends +description: Configure persistent and distributed storage for caching and OAuth state management +icon: database +tag: NEW +--- + +import { VersionBadge } from "/snippets/version-badge.mdx" + + + +FastMCP uses pluggable storage backends for caching responses and managing OAuth state. By default, all storage is in-memory, which is perfect for development but doesn't persist across restarts. FastMCP includes support for multiple storage backends, and you can easily extend it with custom implementations. + + +The storage layer is powered by **[py-key-value-aio](https://github.com/strawgate/py-key-value)**, an async key-value library maintained by a core FastMCP maintainer. This library provides a unified interface for multiple backends, making it easy to swap implementations based on your deployment needs. + + +## Available Backends + +### In-Memory Storage + +**Best for:** Development, testing, single-process deployments + +In-memory storage is the default for all FastMCP storage needs. It's fast, requires no setup, and is perfect for getting started. + +```python +from key_value.aio.stores.memory import MemoryStore + +# Used by default - no configuration needed +# But you can also be explicit: +cache_store = MemoryStore() +``` + +**Characteristics:** +- ✅ No setup required +- ✅ Very fast +- ❌ Data lost on restart +- ❌ Not suitable for multi-process deployments + +### Disk Storage + +**Best for:** Single-server production deployments, persistent caching + +Disk storage persists data to the filesystem, allowing it to survive server restarts. + +```python +from key_value.aio.stores.disk import DiskStore +from fastmcp.server.middleware.caching import ResponseCachingMiddleware + +# Persistent response cache +middleware = ResponseCachingMiddleware( + cache_storage=DiskStore(directory="/var/cache/fastmcp") +) +``` + +Or with OAuth token storage: + +```python +from fastmcp.server.auth.providers.github import GitHubProvider +from key_value.aio.stores.disk import DiskStore + +auth = GitHubProvider( + client_id="your-id", + client_secret="your-secret", + base_url="https://your-server.com", + client_storage=DiskStore(directory="/var/lib/fastmcp/oauth") +) +``` + +**Characteristics:** +- ✅ Data persists across restarts +- ✅ Good performance for moderate load +- ❌ Not suitable for distributed deployments +- ❌ Filesystem access required + +### Redis + +**Best for:** Distributed production deployments, shared caching across multiple servers + + +Redis support requires an optional dependency: `pip install 'py-key-value-aio[redis]'` + + +Redis provides distributed caching and state management, ideal for production deployments with multiple server instances. + +```python +from key_value.aio.stores.redis import RedisStore +from fastmcp.server.middleware.caching import ResponseCachingMiddleware + +# Distributed response cache +middleware = ResponseCachingMiddleware( + cache_storage=RedisStore(host="redis.example.com", port=6379) +) +``` + +With authentication: + +```python +from key_value.aio.stores.redis import RedisStore + +cache_store = RedisStore( + host="redis.example.com", + port=6379, + password="your-redis-password" +) +``` + +For OAuth token storage: + +```python +import os +from fastmcp.server.auth.providers.github import GitHubProvider +from key_value.aio.stores.redis import RedisStore + +auth = GitHubProvider( + client_id=os.environ["GITHUB_CLIENT_ID"], + client_secret=os.environ["GITHUB_CLIENT_SECRET"], + base_url="https://your-server.com", + jwt_signing_key=os.environ["JWT_SIGNING_KEY"], + token_encryption_key=os.environ["TOKEN_ENCRYPTION_KEY"], + client_storage=RedisStore(host="redis.example.com", port=6379) +) +``` + +**Characteristics:** +- ✅ Distributed and highly available +- ✅ Fast in-memory performance +- ✅ Works across multiple server instances +- ✅ Built-in TTL support +- ❌ Requires Redis infrastructure +- ❌ Network latency vs local storage + +### Other Backends from py-key-value-aio + +The py-key-value-aio library includes additional implementations for various storage systems: + +- **DynamoDB** - AWS distributed database +- **MongoDB** - NoSQL document store +- **Elasticsearch** - Distributed search and analytics +- **Memcached** - Distributed memory caching +- **RocksDB** - Embedded high-performance key-value store +- **Valkey** - Redis-compatible server + +For configuration details on these backends, consult the [py-key-value-aio documentation](https://github.com/strawgate/py-key-value-aio). + +## Use Cases in FastMCP + +### Server-Side OAuth Token Storage + +The [OAuth Proxy](/servers/auth/oauth-proxy) and OAuth auth providers use storage for persisting OAuth client registrations and encrypted upstream tokens. By default, registrations are stored in memory: + +```python +# In-memory storage (default behavior - lost on restart) +from fastmcp.server.auth.providers.github import GitHubProvider + +auth = GitHubProvider( + client_id="your-id", + client_secret="your-secret", + base_url="https://your-server.com" +) +``` + +For production with token persistence across restarts, configure persistent storage and encryption keys: + +```python +import os +from fastmcp.server.auth.providers.github import GitHubProvider +from key_value.aio.stores.redis import RedisStore + +auth = GitHubProvider( + client_id=os.environ["GITHUB_CLIENT_ID"], + client_secret=os.environ["GITHUB_CLIENT_SECRET"], + base_url="https://your-server.com", + # Token encryption and signing keys + jwt_signing_key=os.environ["JWT_SIGNING_KEY"], + token_encryption_key=os.environ["TOKEN_ENCRYPTION_KEY"], + # Persistent distributed storage + client_storage=RedisStore(host="redis.example.com", port=6379) +) +``` + +See [OAuth Token Security](/deployment/http#oauth-token-security) for complete production setup details. + +### Response Caching Middleware + +The [Response Caching Middleware](/servers/middleware#caching-middleware) caches tool calls, resource reads, and prompt requests. Storage configuration is passed via the `cache_storage` parameter: + +```python +from fastmcp import FastMCP +from fastmcp.server.middleware.caching import ResponseCachingMiddleware +from key_value.aio.stores.disk import DiskStore + +mcp = FastMCP("My Server") + +# Cache to disk instead of memory +mcp.add_middleware(ResponseCachingMiddleware( + cache_storage=DiskStore(directory="cache") +)) +``` + +For multi-server deployments sharing a Redis instance: + +```python +from fastmcp.server.middleware.caching import ResponseCachingMiddleware +from key_value.aio.stores.redis import RedisStore +from key_value.aio.wrappers.prefix_collections import PrefixCollectionsWrapper + +base_store = RedisStore(host="redis.example.com") +namespaced_store = PrefixCollectionsWrapper( + key_value=base_store, + prefix="my-server" +) + +middleware = ResponseCachingMiddleware(cache_storage=namespaced_store) +``` + +### Client-Side OAuth Token Storage + +The [FastMCP Client](/clients/client) uses storage for persisting OAuth tokens locally. By default, tokens are stored in memory: + +```python +from fastmcp.client.auth import OAuthClientProvider +from key_value.aio.stores.disk import DiskStore + +# Store tokens on disk for persistence across restarts +token_storage = DiskStore(directory="~/.local/share/fastmcp/tokens") + +oauth_provider = OAuthClientProvider( + mcp_url="https://your-mcp-server.com/mcp/sse", + token_storage=token_storage +) +``` + +This allows clients to reconnect without re-authenticating after restarts. + +## Choosing a Backend + +| Backend | Development | Single Server | Multi-Server | Cloud Native | +|---------|-------------|---------------|--------------|--------------| +| Memory | ✅ Best | ⚠️ Limited | ❌ | ❌ | +| Disk | ✅ Good | ✅ Recommended | ❌ | ⚠️ | +| Redis | ⚠️ Overkill | ✅ Good | ✅ Best | ✅ Best | +| DynamoDB | ❌ | ⚠️ | ✅ | ✅ Best (AWS) | +| MongoDB | ❌ | ⚠️ | ✅ | ✅ Good | + +**Decision tree:** + +1. **Just starting?** Use **Memory** (default) - no configuration needed +2. **Single server, needs persistence?** Use **Disk** +3. **Multiple servers or cloud deployment?** Use **Redis** or **DynamoDB** +4. **Existing infrastructure?** Look for a matching py-key-value-aio backend + +## More Resources + +- [py-key-value-aio GitHub](https://github.com/strawgate/py-key-value-aio) - Full library documentation +- [Response Caching Middleware](/servers/middleware#caching-middleware) - Using storage for caching +- [OAuth Token Security](/deployment/http#oauth-token-security) - Production OAuth configuration +- [HTTP Deployment](/deployment/http) - Complete deployment guide