PrefectHQ/fastmcp · error · TimeoutError
OAuth callback timed out after {self._callback_timeout} seco
Error message
OAuth callback timed out after {self._callback_timeout} seconds What it means
The OAuth callback handler starts a temporary local HTTP server on the redirect port and waits (anyio.fail_after) for the provider to redirect back with the authorization code. If no callback arrives within _callback_timeout seconds, TimeoutError is raised; the local server is then shut down in the finally block.
Source
Thrown at fastmcp_slim/fastmcp/client/auth/oauth.py:436
async with anyio.create_task_group() as tg:
tg.start_soon(server.serve)
logger.info(
f"🎧 OAuth callback server started on http://{self._callback_host}:{self.redirect_port}"
)
try:
with anyio.fail_after(self._callback_timeout):
await result_ready.wait()
if result.error:
raise result.error
# `result.code` is set once `result_ready` fires without error.
return AuthorizationCodeResult(
code=result.code, # type: ignore[arg-type] # ty:ignore[invalid-argument-type]
state=result.state,
iss=result.iss,
)
except TimeoutError as e:
raise TimeoutError(
f"OAuth callback timed out after {self._callback_timeout} seconds"
) from e
finally:
server.should_exit = True
await anyio.sleep(0.1) # Allow server to shut down gracefully
tg.cancel_scope.cancel()
raise RuntimeError("OAuth callback handler could not be started")
async def async_auth_flow(
self, request: httpx2.Request
) -> AsyncGenerator[httpx2.Request, httpx2.Response]:
"""HTTPX auth flow with automatic retry on stale cached credentials.
If the OAuth flow fails due to invalid/stale client credentials,
clears the cache and retries once with fresh registration.
"""
if not self._bound:View on GitHub (pinned to 1f02114297)
Solutions
- Ensure you are on an environment with an interactive browser, or complete the authorization URL manually (copy the logged 'OAuth authorization URL' into a browser that can reach the server).
- Increase the callback timeout when constructing OAuth(...) (callback timeout parameter).
- Verify the redirect port matches what the provider/registration expects and that localhost on that port is reachable/firewalled-open.
- Retry the flow; a fresh callback server is started on each attempt.
Example fix
// before: default timeout too short for slow SSO auth = OAuth(mcp_url='https://mcp.example.com/mcp') // after: longer window for the user to finish logging in auth = OAuth(mcp_url='https://mcp.example.com/mcp', callback_timeout=300)
Defensive patterns
Strategy: retry
Try / catch
import timeout as _ # noqa — pattern only
try:
async with client:
await client.list_tools()
except TimeoutError as e:
print('OAuth callback timed out:', e)
# retry with a larger callback_timeout or complete the flow interactively Prevention
- Only run browser-based OAuth on machines with an interactive browser; use a different auth path in headless/CI environments.
- Increase callback_timeout for slow SSO providers.
- Ensure the redirect port is open in the firewall and matches the registered redirect URI.
- Tell users to complete the browser login promptly; the local callback server shuts down on timeout.
When it happens
Trigger: Calling client.call_tool/list_tools with auth='oauth' or OAuth(...) when the user never completes the browser sign-in, the browser fails to open (headless environment), the redirect port is wrong/blocked so the provider's redirect can't reach localhost, or the callback server took longer than the timeout to receive the result.
Common situations: Running in SSH/CI/headless containers where webbrowser.open does nothing; firewall blocking the localhost redirect port; user closing the browser tab; slow SSO login exceeding the timeout; redirect URI registered with a different port than the one the callback server listens on.
Understand the failure class
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Related errors
- The device authorization request expired
- OAuth callback handler could not be started
- User server did not start on port {mcp_port}
- The device authorization request was denied
- Device authorization failed
AI-assisted analysis of PrefectHQ/fastmcp@1f02114297 (2026-08-29).
Data as JSON: /api/errors/26544c5e349558b2.
Report an issue: GitHub.