{"record":{"id":"19a2b59c59a127b0","repo":"PrefectHQ/fastmcp","slug":"no-authorization-response-stored-redirect-handler","errorCode":null,"errorMessage":"No authorization response stored. redirect_handler must be called first.","messagePattern":"No authorization response stored\\. redirect_handler must be called first\\.","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"fastmcp_slim/fastmcp/utilities/tests.py","lineNumber":486,"sourceCode":"    This simulates the complete OAuth flow programmatically by making HTTP requests\n    instead of opening a browser and running a callback server. Useful for automated testing.\n    \"\"\"\n\n    def __init__(self, mcp_url: str, **kwargs):\n        \"\"\"Initialize HeadlessOAuth with stored response tracking.\"\"\"\n        self._stored_response = None\n        super().__init__(mcp_url, **kwargs)\n\n    async def redirect_handler(self, authorization_url: str) -> None:\n        \"\"\"Make HTTP request to authorization URL and store response for callback handler.\"\"\"\n        async with httpx2.AsyncClient() as client:\n            response = await client.get(authorization_url, follow_redirects=False)\n            self._stored_response = response\n\n    async def callback_handler(self) -> AuthorizationCodeResult:\n        \"\"\"Parse stored response and return the authorization code result.\"\"\"\n        if not self._stored_response:\n            raise RuntimeError(\n                \"No authorization response stored. redirect_handler must be called first.\"\n            )\n\n        response = self._stored_response\n\n        # Extract auth code from redirect location\n        if response.status_code == 302:\n            redirect_url = response.headers[\"location\"]\n            parsed = urlparse(redirect_url)\n            # keep_blank_values=True so explicitly-empty params (e.g. ?state=)\n            # survive parsing instead of being silently dropped. Real OAuth\n            # callbacks can include empty `state` or `error_description`,\n            # and downstream code distinguishes \"\" from missing.\n            query_params = parse_qs(parsed.query, keep_blank_values=True)\n\n            if \"error\" in query_params:\n                error = query_params[\"error\"][0]\n                error_desc = query_params.get(\"error_description\", [\"Unknown error\"])[0]","sourceCodeStart":468,"sourceCodeEnd":504,"githubUrl":"https://github.com/PrefectHQ/fastmcp/blob/1f021142978e0861cd910c8df4e8074bc7cf3978/fastmcp_slim/fastmcp/utilities/tests.py#L468-L504","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nresult = await helper.callback_handler()  # nothing stored yet\n\n// after\nawait helper.redirect_handler(authorization_url)\nresult = await helper.callback_handler()","handlingStrategy":"validation","validationCode":"if not getattr(helper, \"_stored_response\", None):\n    raise RuntimeError(\"redirect_handler must be called before callback_handler\")","typeGuard":null,"tryCatchPattern":"try:\n    result = await helper.callback_handler()\nexcept RuntimeError as e:\n    if \"No authorization response\" in str(e):\n        raise AssertionError(\"OAuth flow never redirected; check redirect_handler wiring\") from e\n    raise","preventionTips":["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"],"tags":["oauth","testing","auth","flow-order"],"backgroundTag":"oauth-redirect-handler-not-called","analyzedSha":"1f021142978e0861cd910c8df4e8074bc7cf3978","analyzedAt":"2026-08-29T14:31:16.082Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}