{"record":{"id":"a0c743f3cb2e1a56","repo":"PrefectHQ/fastmcp","slug":"oauth-callback-handler-could-not-be-started","errorCode":null,"errorMessage":"OAuth callback handler could not be started","messagePattern":"OAuth callback handler could not be started","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"fastmcp_slim/fastmcp/client/auth/oauth.py","lineNumber":444,"sourceCode":"                    await result_ready.wait()\n                    if result.error:\n                        raise result.error\n                    # `result.code` is set once `result_ready` fires without error.\n                    return AuthorizationCodeResult(\n                        code=result.code,  # type: ignore[arg-type]  # ty:ignore[invalid-argument-type]\n                        state=result.state,\n                        iss=result.iss,\n                    )\n            except TimeoutError as e:\n                raise TimeoutError(\n                    f\"OAuth callback timed out after {self._callback_timeout} seconds\"\n                ) from e\n            finally:\n                server.should_exit = True\n                await anyio.sleep(0.1)  # Allow server to shut down gracefully\n                tg.cancel_scope.cancel()\n\n        raise RuntimeError(\"OAuth callback handler could not be started\")\n\n    async def async_auth_flow(\n        self, request: httpx2.Request\n    ) -> AsyncGenerator[httpx2.Request, httpx2.Response]:\n        \"\"\"HTTPX auth flow with automatic retry on stale cached credentials.\n\n        If the OAuth flow fails due to invalid/stale client credentials,\n        clears the cache and retries once with fresh registration.\n        \"\"\"\n        if not self._bound:\n            raise RuntimeError(\n                \"OAuth provider has no server URL. Either pass mcp_url to OAuth() \"\n                \"or use it with Client(auth=...) which provides the URL automatically.\"\n            )\n        try:\n            # First attempt with potentially cached credentials\n            async with aclosing(super().async_auth_flow(request)) as gen:\n                response = None","sourceCodeStart":426,"sourceCodeEnd":462,"githubUrl":"https://github.com/PrefectHQ/fastmcp/blob/1f021142978e0861cd910c8df4e8074bc7cf3978/fastmcp_slim/fastmcp/client/auth/oauth.py#L426-L462","documentation":"callback_handler runs the local callback server inside an anyio task group; if server.serve exits without the result event ever firing (server failed to start or died immediately), control falls through the task group and the function raises RuntimeError as a catch-all 'could not be started'.","triggerScenarios":"The uvicorn callback server at self.redirect_port fails to bind (port already in use, permission denied on privileged port) or exits immediately, so result_ready never fires and the task group ends without a result.","commonSituations":"Another process (or a leftover previous OAuth attempt) is already listening on the redirect port; trying to use a privileged port (<1024) without permissions; container networking preventing loopback binds.","solutions":["Check what is occupying the redirect port (lsof/ss) and free it, or choose a different redirect port.","Pick an unprivileged port (>1024) for the OAuth redirect.","Kill stale callback servers from earlier failed auth attempts.","Retry the auth flow once the port is available."],"exampleFix":"// before: port 80 requires root and bind fails\nauth = OAuth(mcp_url='https://mcp.example.com/mcp', redirect_port=80)\n// after: unprivileged port\nauth = OAuth(mcp_url='https://mcp.example.com/mcp', redirect_port=8080)","handlingStrategy":"validation","validationCode":"import socket\ns = socket.socket()\ntry:\n    s.bind(('127.0.0.1', redirect_port))\n    print('port free')\nexcept OSError as e:\n    print('port in use or forbidden:', e)\nfinally:\n    s.close()","typeGuard":null,"tryCatchPattern":"try:\n    async with client:\n        await client.list_tools()\nexcept RuntimeError as e:\n    if 'callback handler could not be started' in str(e):\n        pick_free_port_and_retry()\n    raise","preventionTips":["Check the redirect port is free before starting the flow (lsof/ss or a socket bind probe).","Always use an unprivileged port (>1024).","Clean up orphaned callback servers from previous failed auth attempts.","In containers, confirm loopback binds are permitted."],"tags":["oauth","callback","port-in-use","server"],"backgroundTag":"port-already-in-use","analyzedSha":"1f021142978e0861cd910c8df4e8074bc7cf3978","analyzedAt":"2026-08-29T14:31:16.082Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}