{"record":{"id":"9e1163bdef84190b","repo":"PrefectHQ/fastmcp","slug":"oauth-provider-has-no-server-url-either-pass-mcp","errorCode":null,"errorMessage":"OAuth provider has no server URL. Either pass mcp_url to OAuth() or use it with Client(auth=...) which provides the URL automatically.","messagePattern":"OAuth provider has no server URL\\. Either pass mcp_url to OAuth\\(\\) or use it with Client\\(auth=\\.\\.\\.\\) which provides the URL automatically\\.","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"fastmcp_slim/fastmcp/client/auth/oauth.py","lineNumber":455,"sourceCode":"                    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\n                while True:\n                    try:\n                        # First iteration sends None, subsequent iterations send response\n                        yielded_request = await gen.asend(response)  # ty: ignore[invalid-argument-type]\n                        response = yield yielded_request\n                    except StopAsyncIteration:\n                        break\n\n        except (ClientNotFoundError, ExpiredClientRegistrationError) as exc:\n            # Static credentials are fixed — retrying won't help. Surface the\n            # error so the user can correct their client_id / client_secret.","sourceCodeStart":437,"sourceCodeEnd":473,"githubUrl":"https://github.com/PrefectHQ/fastmcp/blob/1f021142978e0861cd910c8df4e8074bc7cf3978/fastmcp_slim/fastmcp/client/auth/oauth.py#L437-L473","documentation":"The OAuth provider must know the MCP server URL to perform discovery, registration, and token exchange. If OAuth() was constructed without mcp_url and was never bound by being attached to a Client (which supplies the URL automatically), async_auth_flow raises this RuntimeError because the flow cannot proceed unbound.","triggerScenarios":"Creating OAuth() with no mcp_url and then using it as an httpx auth outside of Client(auth=...) — e.g. passing it directly to a raw httpx client, or reusing the auth object detached from its client — so self._bound remains False when async_auth_flow runs.","commonSituations":"Hand-rolling an HTTPX request with the OAuth object instead of going through fastmcp's Client; constructing OAuth() for later configuration but calling the flow before binding; copying an auth object between clients.","solutions":["Pass mcp_url when constructing OAuth: OAuth(mcp_url='https://mcp.example.com/mcp').","Or use the provider via Client(transport_or_url, auth=oauth), which binds the URL automatically.","Ensure the flow is invoked through the Client rather than a bare httpx client."],"exampleFix":"// before: unbound provider used with raw httpx\nauth = OAuth()\nhttpx.get('https://mcp.example.com/mcp', auth=auth)\n// after: bind via Client, which supplies the URL\nclient = Client('https://mcp.example.com/mcp', auth=OAuth())\nasync with client: await client.list_tools()","handlingStrategy":"validation","validationCode":"auth = OAuth()  # or OAuth(mcp_url=...)\nif not getattr(auth, 'mcp_url', None) and not getattr(auth, '_bound', False):\n    raise ValueError('Pass mcp_url to OAuth() or use Client(auth=...) to bind the URL')","typeGuard":"def is_oauth_bound(auth) -> bool:\n    return bool(getattr(auth, 'mcp_url', None)) or bool(getattr(auth, '_bound', False))","tryCatchPattern":"try:\n    async with client:\n        await client.list_tools()\nexcept RuntimeError as e:\n    if 'no server URL' in str(e):\n        raise ValueError('Construct OAuth with mcp_url or attach it via Client(auth=...)') from e\n    raise","preventionTips":["Always construct OAuth with an explicit mcp_url, or always attach it through Client(auth=...).","Never pass an OAuth provider to a raw httpx client; route requests through fastmcp's Client.","Don't reuse a single OAuth auth object detached from its client.","Assert the provider is bound in tests before exercising the auth flow."],"tags":["oauth","configuration","misuse","client"],"backgroundTag":"missing-required-config","analyzedSha":"1f021142978e0861cd910c8df4e8074bc7cf3978","analyzedAt":"2026-08-29T14:31:16.082Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}