PrefectHQ/fastmcp · error · ClientNotFoundError
OAuth client not found - cached credentials may be stale
Error message
OAuth client not found - cached credentials may be stale
What it means
During the OAuth browser flow, FastMCP pre-flights the authorization URL with a no-redirect GET before opening the browser. A 400 from the authorization server is interpreted as the server not recognizing the client_id, meaning cached dynamic-registration credentials are stale or invalid. ClientNotFoundError is raised so the auth flow can clear the cache and retry with a fresh registration.
Source
Thrown at fastmcp_slim/fastmcp/client/auth/oauth.py:389
client_info is not None
and client_info.client_secret is not None
and client_info.client_secret_expires_at
and client_info.client_secret_expires_at <= int(time.time())
):
raise ExpiredClientRegistrationError(
"OAuth dynamic registration returned an expired client secret"
)
return await super()._perform_authorization()
async def redirect_handler(self, authorization_url: str) -> None:
"""Open browser for authorization, with pre-flight check for invalid client."""
# Pre-flight check to detect invalid client_id before opening browser
async with self.httpx_client_factory() as client:
response = await client.get(authorization_url, follow_redirects=False)
# Check for client not found error (400 typically means bad client_id)
if response.status_code == 400:
raise ClientNotFoundError(
"OAuth client not found - cached credentials may be stale"
)
# OAuth typically returns redirects, but some providers return 200 with HTML login pages
if response.status_code not in (200, 302, 303, 307, 308):
raise RuntimeError(
f"Unexpected authorization response: {response.status_code}"
)
logger.info(f"OAuth authorization URL: {authorization_url}")
webbrowser.open(authorization_url)
async def callback_handler(self) -> AuthorizationCodeResult:
"""Handle OAuth callback and return the authorization code result."""
# Create result container and event to capture the OAuth response
result = OAuthCallbackResult()
result_ready = anyio.Event()
View on GitHub (pinned to 1f02114297)
Solutions
- Clear the cached OAuth state (delete the token storage / cache directory, or let the library do it: async_auth_flow catches ClientNotFoundError for dynamic clients and auto-clears + retries once).
- If you passed static client credentials (static_client_info), verify the client_id/client_secret are actually registered with that server.
- Re-run the client; a fresh dynamic client registration will be performed against the current server.
- Check you are hitting the correct server URL/environment and that its authorization endpoint is healthy.
Example fix
// before: stale cache directory reused across server restarts
client = Client('https://mcp.example.com/mcp', auth=OAuth(mcp_url='https://mcp.example.com/mcp'))
// after: wipe cached tokens so a fresh registration happens
import shutil; shutil.rmtree('~/.fastmcp/oauth-cache', ignore_errors=True)
client = Client('https://mcp.example.com/mcp', auth=OAuth(mcp_url='https://mcp.example.com/mcp')) Defensive patterns
Strategy: retry
Try / catch
from fastmcp.client.auth import ClientNotFoundError
try:
async with client:
result = await client.list_tools()
except ClientNotFoundError:
clear_oauth_cache() # delete token/client-info storage
async with client: # retry triggers fresh dynamic registration
result = await client.list_tools() Prevention
- Avoid token-cache directories shared across server redeployments when the server keeps registrations in memory.
- Prefer dynamic registration (no static_client_info) so the SDK can self-heal by re-registering.
- Keep environment-specific cache directories separate (staging vs prod).
- Use a persistent OAuth registration store server-side if clients cache credentials long-term.
When it happens
Trigger: Calling Client(auth=OAuth(mcp_url)) (or auth='oauth') and hitting redirect_handler when GET <authorization_url> returns HTTP 400 — typically because the stored OAuthClientInformationFull in the token storage no longer matches a client registered at the server (server restarted with in-memory registration storage, tokens cache predates a server redeploy, or the provider purged the client).
Common situations: Pointing a client at a dev MCP server that stores dynamic clients in memory after the server restarted; switching environments (staging vs prod) while reusing a token cache directory; a provider that rotates/evicts dynamically registered clients.
Related errors
- OAuth provider has no server URL. Either pass mcp_url to OAu
- OAuth server rejected the static client credentials. Verify
- Assertion must include exp claim
- Assertion must include sub claim
- Assertion missing required scopes: {sorted(missing)}
AI-assisted analysis of PrefectHQ/fastmcp@1f02114297 (2026-08-29).
Data as JSON: /api/errors/3fb3cdb485b44e8e.
Report an issue: GitHub.