-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Add Storage to FastMCP and switch OAuth to use it #1913
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR migrates OAuth storage from custom file-based storage implementations to py-kv-store-adapter, standardizing data persistence across the codebase. The change introduces a default disk storage location at ~/.fastmcp/data.db
and switches the OAuth implementation to use the new KV store adapters.
- Replace custom
JSONFileStorage
andInMemoryStorage
with py-kv-store-adapter implementations - Add
data_store
property to settings that provides aDiskStore
instance with 10MB size limit - Update OAuth proxy and client authentication to use the new storage adapters with proper collections
Reviewed Changes
Copilot reviewed 15 out of 16 changed files in this pull request and generated 2 comments.
Show a summary per file
File | Description |
---|---|
pyproject.toml | Add kv-store-adapter dependency |
src/fastmcp/settings.py | Add data_path and data_store property for centralized storage |
src/fastmcp/utilities/storage.py | Remove custom storage implementations |
src/fastmcp/server/auth/oauth_proxy.py | Migrate to PydanticAdapter with DiskStore for client storage |
src/fastmcp/client/auth/oauth.py | Replace FileTokenStorage with TokenStorageAdapter using KV store |
tests/utilities/test_storage.py | Remove tests for deleted storage classes |
tests/server/auth/test_oauth_proxy_storage.py | Update tests to use DiskStore and MemoryStore |
tests/server/auth/test_oauth_proxy_redirect_validation.py | Fix attribute access for allowed_redirect_uri_patterns |
tests/client/auth/test_oauth_token_expiry.py | Comment out tests that depend on removed FileTokenStorage |
b4dba10
to
2f8ec76
Compare
@strawgate I tested this out with the I currently don't have quick access to an |
Thank you! I can test with Elasticsearch for sure -- but if for whatever reason you'd like free access to an Elasticsearch project in Elastic Cloud for a bit to play around you just let me know :) |
This looks great and is well thought out. My only comment is that "data_path" is not a particularly descriptive setting name (nor the |
I'm going to do a little refactor of the adapter library today and then will update the pr |
f2707c4
to
ccd12c4
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
Copilot reviewed 15 out of 16 changed files in this pull request and generated 3 comments.
Hi Tried your version with storage. I need this because our version in prod required multiple running nodes in K8S. So this improvement expected to help - and file storage is not available for us. I have tried with Redis and DragonflyDB - initially it works locally (1 node) and K8S right after deployment - when cache is empty. But after token timeout ( I use OKTA with TTL 1 hour + Cursor AI as client ) It's showed that I'm logged off. Usually on memory based storage ( default OAuth Proxy) it was enough to reset client token in Cursor AI - and it forces to log off and reauth works fine. But in this version it showed in Cursor AI output MCP: And can not fix this without redeploying again. Different behavior, I'm trying figure out it. BTW:
I wonder is this expected to have some many NULLs Context:
|
I'll take a look at this but I did push an update at around the time you may have been testing Can you:
|
Also do you have a client other than cursor you can try with? https://forum.cursor.com/t/ver-1-6-42-mcp-oauth-fails-on-error-failed-to-complete-oauth-exchange-authorization-code-does-not-exist/134328 Or can you rollback to a previous version as suggested in that post and see if you get the same behavior |
@ruhulio did you test through a token refresh? |
@strawgate I had in my previous testing. I can test things out again later today with the latest updates to the KV package. |
yep Planing to try it on the latest version today-tomorrow.
For 1 node it worked fine locally, but I have not tested in pod environment for 1 node yet (will test as well) |
3173e0e
to
a7c703b
Compare
Removing central configuration of storage via settings for now and will follow-up with another PR for configuring storage via the FastMCP class |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
Copilot reviewed 15 out of 16 changed files in this pull request and generated 2 comments.
fa29e51
to
dd81a0b
Compare
Hi @strawgate, thanks for the update I see "kv_store_adapter" has been changed to "py-key-value-aio" Just for context I've been using:
Because in my installation I can not use persistent disk storage, so using Redis is preferable to omit "client_id" not found error |
Yes, I just renamed the library that's all, there's still a redis store The comment above was regarding the ability to configure some storage settings on the FastMCP.settings object and have that propagate through the various components as a fallback if nothing was passed for client_storage |
@jlowin this is ready for your review |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this looks great! Minor question on docstring and I recommend updating the PR body to detail:
- any breaking changes for users upgrading to 2.13 (I think mainly the token storage kwarg? or maybe nobody actually sets that in practice) that we'll copy into the release notes
- the PR body still says the default is disk based but now it is memory based so just want to keep that aligned
Lastly one question I wanted to check - does the pydantic adapter fail gracefully e.g. if in 2.13.1 we add a required field to the stored DCR Client details and someone has cached clients from 2.13.0, will the storage raise an error because a field is missing (which would interrupt the auth flow permanently), or will it silently fail and act as if no cached client was found (which would permit the re-auth flow and presumably overwrite the corrupt/old client on the next pass)
""" | ||
super().__init__(*args, **kwargs) | ||
self._allowed_redirect_uri_patterns = allowed_redirect_uri_patterns | ||
allowed_redirect_uri_patterns: list[str] | None = Field(default=None) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor request to copy the deleted argument docstring into the field description
@strawgate In an OAuth proxy setup a client makes Ideally in the process of this I'd separate the handling of per-user data (client codes, access/refresh tokens) and everything else (oauth_transactions, clients). |
Since we can configure auth entirely through environment variables, can we also configure the storage backend entirely through environment variables? Something like:
|
Will fix, breaking changes should just be the loss of json-based tokens and the arg name change. Right now it will raise an error on validation failure but I'll add an argument to the Pydantic adapter which you can use to bypass validation errors |
I'll be adding "central storage configuration" in a followup PR and we can consider something like this |
Awesome! This is such a major feature for 2.13. Approved, merge whenever you feel ready! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
Copilot reviewed 15 out of 16 changed files in this pull request and generated 3 comments.
""" | ||
super().__init__(*args, **kwargs) | ||
self._allowed_redirect_uri_patterns = allowed_redirect_uri_patterns | ||
allowed_redirect_uri_patterns: list[str] | None = Field(default=None) |
Copilot
AI
Oct 9, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The ProxyDCRClient class has been refactored to use a Field attribute instead of a constructor parameter. This is a breaking change that removes the ability to pass allowed_redirect_uri_patterns during initialization, which could affect code that creates ProxyDCRClient instances directly.
allowed_redirect_uri_patterns: list[str] | None = Field(default=None) | |
allowed_redirect_uri_patterns: list[str] | None = None |
Copilot uses AI. Check for mistakes.
scopes: str | list[str] | None = None, | ||
client_name: str = "FastMCP Client", | ||
token_storage_cache_dir: Path | None = None, | ||
token_storage: AsyncKeyValue | None = None, |
Copilot
AI
Oct 9, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Breaking change: The parameter name changed from token_storage_cache_dir
to token_storage
and the type changed from Path | None
to AsyncKeyValue | None
. This will break existing code that passes the old parameter name or a Path object.
Copilot uses AI. Check for mistakes.
|
||
DuplicateBehavior = Literal["warn", "error", "replace", "ignore"] | ||
|
||
TEN_MB_IN_BYTES = 1024 * 1024 * 10 |
Copilot
AI
Oct 9, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The constant TEN_MB_IN_BYTES is defined but never used in any of the changed files. Consider removing it if it's not needed, or add a comment explaining its intended purpose.
TEN_MB_IN_BYTES = 1024 * 1024 * 10 |
Copilot uses AI. Check for mistakes.
🚀 |
Description
Adds KV data storage using https://github.com/strawgate/py-key-value. py-key-value provides disk, memory, Elasticsearch, Memcached, MongoDB, Redis, and Valkey support.
Switches OAuth token and client token storage to using the py-key-value library. Defaults to memory storage unless a user provides a key_value store to the OAuth components.
Breaking changes should just be the loss of json-on-disk tokens and the arg change from
token_storage_cache_dir
totoken_storage
Contributors Checklist
Review Checklist