{"record":{"id":"101f7f29519ddb42","repo":"PrefectHQ/fastmcp","slug":"either-config-url-new-api-or-both-project-id-and","errorCode":null,"errorMessage":"Either config_url (new API) or both project_id and descope_base_url (old API) must be provided","messagePattern":"Either config_url \\(new API\\) or both project_id and descope_base_url \\(old API\\) must be provided","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"fastmcp_slim/fastmcp/server/auth/providers/descope.py","lineNumber":171,"sourceCode":"        )\n\n        if config_url is not None:\n            (\n                self.descope_base_url,\n                self.project_id,\n                issuer_url,\n                self.openid_configuration_url,\n            ) = _parse_descope_config_url(str(config_url))\n        elif project_id is not None and descope_base_url is not None:\n            self.project_id = project_id\n            descope_base_url_str = str(descope_base_url).rstrip(\"/\")\n            if not descope_base_url_str.startswith((\"http://\", \"https://\")):\n                descope_base_url_str = f\"https://{descope_base_url_str}\"\n            self.descope_base_url = descope_base_url_str\n            issuer_url = f\"{self.descope_base_url}/v1/apps/{self.project_id}\"\n            self.openid_configuration_url = f\"{issuer_url}{_OPENID_WK}\"\n        else:\n            raise ValueError(\n                \"Either config_url (new API) or both project_id and descope_base_url (old API) must be provided\"\n            )\n\n        self.oauth_authorization_server_metadata_url = (\n            self.openid_configuration_url.replace(_OPENID_WK, _OAUTH_WK)\n        )\n\n        # Advertised scopes are discovered from Descope's OpenID configuration\n        # only when the caller supplied neither explicit advertised scopes nor\n        # required scopes. Discovery is deferred to the first protected resource\n        # metadata request (see get_routes) so construction never performs I/O\n        # and a transient failure can be retried instead of being frozen for the\n        # provider's lifetime.\n        custom_verifier_scopes = (\n            token_verifier.scopes_supported if token_verifier is not None else []\n        )\n        self._scopes_discovery_enabled = (\n            parsed_scopes_supported is None","sourceCodeStart":153,"sourceCodeEnd":189,"githubUrl":"https://github.com/PrefectHQ/fastmcp/blob/1f021142978e0861cd910c8df4e8074bc7cf3978/fastmcp_slim/fastmcp/server/auth/providers/descope.py#L153-L189","documentation":"DescopeAuthProvider requires either the new API form (config_url) or the old API form (project_id plus descope_base_url). The constructor raises this ValueError when neither form is satisfied — typically when only project_id is given without a base URL, or a base URL without project_id, or neither argument at all.","triggerScenarios":"Calling DescopeAuthProvider(...) with: (1) no arguments, (2) only project_id but no descope_base_url, (3) only descope_base_url but no project_id, (4) both but one is None/empty so the `if` branch fails.","commonSituations":"Migrating from the old Descope setup to the new config_url-based setup and removing old kwargs piecemeal; copying example code that assumes env vars supply project_id/base_url; typos in keyword names (e.g. base_url instead of descope_base_url) silently leaving them unset.","solutions":["Pass config_url pointing to Descope's OIDC configuration (new API), e.g. config_url=f\"https://api.descope.com/v1/apps/<project_id>/.well-known/openid-configuration\"","Alternatively pass BOTH project_id and descope_base_url (e.g. project_id='P2abc...', descope_base_url='https://api.descope.com')","Verify keyword argument names and that the values are non-empty strings, not None from failed env lookups"],"exampleFix":"// before\nauth = DescopeAuthProvider(project_id=os.getenv(\"DESCOPE_PROJECT_ID\"))\n// after\nauth = DescopeAuthProvider(\n    project_id=os.environ[\"DESCOPE_PROJECT_ID\"],\n    descope_base_url=\"https://api.descope.com\",\n)\n# or the new API:\nauth = DescopeAuthProvider(\n    config_url=\"https://api.descope.com/v1/apps/P2abc/.well-known/openid-configuration\"\n)","handlingStrategy":"validation","validationCode":"config_url = os.getenv(\"DESCOPE_CONFIG_URL\")\nproject_id = os.getenv(\"DESCOPE_PROJECT_ID\")\nbase_url = os.getenv(\"DESCOPE_BASE_URL\")\nif not config_url and not (project_id and base_url):\n    raise ValueError(\"Provide either DESCOPE_CONFIG_URL or both DESCOPE_PROJECT_ID and DESCOPE_BASE_URL\")\nprovider = DescopeAuthProvider(config_url=config_url) if config_url else DescopeAuthProvider(project_id=project_id, descope_base_url=base_url)","typeGuard":"def has_descope_args(config_url: str | None, project_id: str | None, base_url: str | None) -> bool:\n    return bool(config_url) or bool(project_id and base_url)","tryCatchPattern":"try:\n    provider = DescopeAuthProvider(project_id=pid, descope_base_url=base)\nexcept ValueError as e:\n    logger.error(\"Descope auth misconfigured: %s\", e)\n    raise SystemExit(1) from e","preventionTips":["Validate provider configuration at application startup, not lazily","Prefer the single config_url API to reduce the argument combinations","Use required env lookups (os.environ[...]) instead of os.getenv with None fallbacks"],"tags":["configuration","oauth","constructor"],"backgroundTag":"missing-required-config","analyzedSha":"1f021142978e0861cd910c8df4e8074bc7cf3978","analyzedAt":"2026-08-29T14:31:16.082Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}