PrefectHQ/fastmcp · error · RuntimeError
No authorization response stored. redirect_handler must be c
Error message
No authorization response stored. redirect_handler must be called first.
What it means
This test OAuth helper stores the redirect response when redirect_handler is called; callback_handler parses that stored response. If callback_handler runs before any redirect was captured (self._stored_response is falsy), it raises RuntimeError explaining that redirect_handler must run first. It enforces the OAuth redirect-then-callback ordering.
Source
Thrown at fastmcp_slim/fastmcp/utilities/tests.py:486
This simulates the complete OAuth flow programmatically by making HTTP requests
instead of opening a browser and running a callback server. Useful for automated testing.
"""
def __init__(self, mcp_url: str, **kwargs):
"""Initialize HeadlessOAuth with stored response tracking."""
self._stored_response = None
super().__init__(mcp_url, **kwargs)
async def redirect_handler(self, authorization_url: str) -> None:
"""Make HTTP request to authorization URL and store response for callback handler."""
async with httpx2.AsyncClient() as client:
response = await client.get(authorization_url, follow_redirects=False)
self._stored_response = response
async def callback_handler(self) -> AuthorizationCodeResult:
"""Parse stored response and return the authorization code result."""
if not self._stored_response:
raise RuntimeError(
"No authorization response stored. redirect_handler must be called first."
)
response = self._stored_response
# Extract auth code from redirect location
if response.status_code == 302:
redirect_url = response.headers["location"]
parsed = urlparse(redirect_url)
# keep_blank_values=True so explicitly-empty params (e.g. ?state=)
# survive parsing instead of being silently dropped. Real OAuth
# callbacks can include empty `state` or `error_description`,
# and downstream code distinguishes "" from missing.
query_params = parse_qs(parsed.query, keep_blank_values=True)
if "error" in query_params:
error = query_params["error"][0]
error_desc = query_params.get("error_description", ["Unknown error"])[0]View on GitHub (pinned to 1f02114297)
Solutions
- Ensure redirect_handler is awaited/called with the authorization URL before callback_handler
- Verify the client is configured to use this helper as its redirect handler so the response gets stored
- Check that the same helper instance is used for both redirect and callback steps
- Inspect the authorization flow earlier — if the client never redirects, the OAuth config (client_id, redirect URI) may be wrong
Example fix
// before result = await helper.callback_handler() # nothing stored yet // after await helper.redirect_handler(authorization_url) result = await helper.callback_handler()
Defensive patterns
Strategy: validation
Validate before calling
if not getattr(helper, "_stored_response", None):
raise RuntimeError("redirect_handler must be called before callback_handler") Try / catch
try:
result = await helper.callback_handler()
except RuntimeError as e:
if "No authorization response" in str(e):
raise AssertionError("OAuth flow never redirected; check redirect_handler wiring") from e
raise Prevention
- Always call redirect_handler before callback_handler in the flow
- Use the same helper instance for both steps
- Assert a redirect occurred (status 302/307) before parsing
- Verify the client is configured with this helper as its redirect handler
When it happens
Trigger: Calling callback_handler() before redirect_handler() on the same helper instance; redirect_handler never being invoked because the client never got redirected; a new helper instance being used for the callback; the stored response being cleared between steps.
Common situations: Hand-rolled OAuth test flows where the authorization step is skipped or fails silently before redirect; miswired redirect handler so the browser/client request never reaches it; reusing helpers incorrectly across client sessions.
Related errors
- OAuth authorization failed: {error} - {error_desc}
- Authorization failed: {response.status_code}
- OAuth client not found - cached credentials may be stale
- OAuth server rejected the static client credentials. Verify
- Assertion must include exp claim
AI-assisted analysis of PrefectHQ/fastmcp@1f02114297 (2026-08-29).
Data as JSON: /api/errors/19a2b59c59a127b0.
Report an issue: GitHub.