diff --git a/docs/integrations/descope.mdx b/docs/integrations/descope.mdx index fdb8b0230..81eb0642b 100644 --- a/docs/integrations/descope.mdx +++ b/docs/integrations/descope.mdx @@ -6,7 +6,7 @@ icon: shield-check tag: NEW --- -import { VersionBadge } from "/snippets/version-badge.mdx" +import { VersionBadge } from "/snippets/version-badge.mdx"; @@ -17,28 +17,29 @@ This guide shows you how to secure your FastMCP server using [**Descope**](https ### Prerequisites Before you begin, you will need: + 1. To [sign up](https://www.descope.com/sign-up) for a Free Forever Descope account -2. Your **Project ID** from the [Descope Console](https://app.descope.com/settings/project) -3. Your FastMCP server's URL (can be localhost for development, e.g., `http://localhost:3000`) +2. Your FastMCP server's URL (can be localhost for development, e.g., `http://localhost:3000`) ### Step 1: Configure Descope - - 1. Go to the [Inbound Apps page](https://app.descope.com/apps/inbound) of the Descope Console - 2. Click **DCR Settings** - 3. Enable **Dynamic Client Registration (DCR)** - 4. Define allowed scopes + + 1. Go to the [MCP Servers page](https://app.descope.com/mcp-servers) of the Descope Console, and create a new MCP Server. + 2. Give the MCP server a name and description. + 3. Ensure that **Dynamic Client Registration (DCR)** is enabled. Then click **Create**. + 4. Once you've created the MCP Server, note your Well-Known URL. + DCR is required for FastMCP clients to automatically register with your authentication server. - - Save your Project ID from [Project Settings](https://app.descope.com/settings/project): + + Save your Well-Known URL from [MCP Server Settings](https://app.descope.com/mcp-servers): ``` - Project ID: P2abc...123 + Well-Known URL: https://.../v1/apps/agentic/P.../M.../.well-known/openid-configuration ``` @@ -48,15 +49,10 @@ Before you begin, you will need: Create a `.env` file with your Descope configuration: ```bash -DESCOPE_PROJECT_ID=P2abc...123 # Your Descope Project ID -DESCOPE_BASE_URL=https://api.descope.com # Descope API URL +DESCOPE_CONFIG_URL=https://.../v1/apps/agentic/P.../M.../.well-known/openid-configuration # Your Descope Well-Known URL SERVER_URL=http://localhost:3000 # Your server's base URL ``` - -You can find your project's Descope Base URL in the [Multi-Region Support Guide](https://docs.descope.com/management/project-settings/multi-regional). - - ### Step 3: FastMCP Configuration Create your FastMCP server file and use the DescopeProvider to handle all the OAuth integration automatically: @@ -68,9 +64,8 @@ from fastmcp.server.auth.providers.descope import DescopeProvider # The DescopeProvider automatically discovers Descope endpoints # and configures JWT token validation auth_provider = DescopeProvider( - project_id=DESCOPE_PROJECT_ID, # Your Descope Project ID + config_url=https://.../.well-known/openid-configuration, # Your MCP Server .well-known URL base_url=SERVER_URL, # Your server's public URL - descope_base_url=DESCOPE_BASE_URL, # Descope API base URL ) # Create FastMCP server with auth @@ -80,7 +75,7 @@ mcp = FastMCP(name="My Descope Protected Server", auth=auth_provider) ## Testing -To test your server, you can use the `fastmcp` CLI to run it locally. Assuming you've saved the above code to `server.py` (after replacing the `project_id`, `base_url`, and `descope_base_url` with your actual values!), you can run the following command: +To test your server, you can use the `fastmcp` CLI to run it locally. Assuming you've saved the above code to `server.py` (after replacing the environment variables with your actual values!), you can run the following command: ```bash fastmcp run server.py --transport http --port 8000 @@ -102,7 +97,6 @@ if __name__ == "__main__": ## Environment Variables - For production deployments, use environment variables instead of hardcoding credentials. ### Provider Selection @@ -110,9 +104,10 @@ For production deployments, use environment variables instead of hardcoding cred Setting this environment variable allows the Descope provider to be used automatically without explicitly instantiating it in code. - -Set to `fastmcp.server.auth.providers.descope.DescopeProvider` to use Descope authentication. - + + Set to `fastmcp.server.auth.providers.descope.DescopeProvider` to use + Descope authentication. + ### Descope-Specific Configuration @@ -120,28 +115,25 @@ Set to `fastmcp.server.auth.providers.descope.DescopeProvider` to use Descope au These environment variables provide default values for the Descope provider, whether it's instantiated manually or configured via `FASTMCP_SERVER_AUTH`. - -Your Descope Project ID from the [Descope Console](https://app.descope.com/settings/project) + +Your Well-Known URL from the [Descope Console](https://app.descope.com/mcp-servers) -Public URL of your FastMCP server (e.g., `https://your-server.com` or `http://localhost:8000` for development) - - - -Descope API base URL for your [region/environment](https://docs.descope.com/management/project-settings/multi-regional) + Public URL of your FastMCP server (e.g., `https://your-server.com` or + `http://localhost:8000` for development) Example `.env` file: + ```bash # Use the Descope provider FASTMCP_SERVER_AUTH=fastmcp.server.auth.providers.descope.DescopeProvider # Descope configuration -FASTMCP_SERVER_AUTH_DESCOPEPROVIDER_PROJECT_ID=P2abc...123 +FASTMCP_SERVER_AUTH_DESCOPEPROVIDER_CONFIG_URL=https://.../v1/apps/agentic/P.../M.../.well-known/openid-configuration FASTMCP_SERVER_AUTH_DESCOPEPROVIDER_BASE_URL=https://your-server.com -FASTMCP_SERVER_AUTH_DESCOPEPROVIDER_DESCOPE_BASE_URL=https://api.descope.com ``` With environment variables set, your server code simplifies to: @@ -151,4 +143,4 @@ from fastmcp import FastMCP # Authentication is automatically configured from environment mcp = FastMCP(name="My Descope Protected Server") -``` \ No newline at end of file +``` diff --git a/docs/python-sdk/fastmcp-server-auth-providers-descope.mdx b/docs/python-sdk/fastmcp-server-auth-providers-descope.mdx index 9d14c4964..8b282c398 100644 --- a/docs/python-sdk/fastmcp-server-auth-providers-descope.mdx +++ b/docs/python-sdk/fastmcp-server-auth-providers-descope.mdx @@ -5,21 +5,18 @@ sidebarTitle: descope # `fastmcp.server.auth.providers.descope` - Descope authentication provider for FastMCP. This module provides DescopeProvider - a complete authentication solution that integrates with Descope's OAuth 2.1 and OpenID Connect services, supporting Dynamic Client Registration (DCR) for seamless MCP client authentication. - ## Classes ### `DescopeProviderSettings` ### `DescopeProvider` - Descope metadata provider for DCR (Dynamic Client Registration). This provider implements Descope integration using metadata forwarding. @@ -29,20 +26,20 @@ as a resource server. IMPORTANT SETUP REQUIREMENTS: -1. Enable Dynamic Client Registration in Descope Console: - - Go to the [Inbound Apps page](https://app.descope.com/apps/inbound) of the Descope Console - - Click **DCR Settings** - - Enable **Dynamic Client Registration (DCR)** - - Define allowed scopes +1. Create an MCP Server in Descope Console: -2. Note your Project ID: - - Save your Project ID from [Project Settings](https://app.descope.com/settings/project) - - Example: P2abc...123 + - Go to the [MCP Servers page](https://app.descope.com/mcp-servers) of the Descope Console + - Create a new MCP Server + - Ensure that **Dynamic Client Registration (DCR)** is enabled + - Note your Well-Known URL + +2. Note your Well-Known URL: + - Save your Well-Known URL from [MCP Server Settings](https://app.descope.com/mcp-servers) + - Format: `https://.../v1/apps/agentic/P.../M.../.well-known/openid-configuration` For detailed setup instructions, see: https://docs.descope.com/identity-federation/inbound-apps/creating-inbound-apps#method-2-dynamic-client-registration-dcr - **Methods:** #### `get_routes` @@ -57,6 +54,6 @@ This returns the standard protected resource routes plus an authorization server metadata endpoint that forwards Descope's OAuth metadata to clients. **Args:** -- `mcp_path`: The path where the MCP endpoint is mounted (e.g., "/mcp") -This is used to advertise the resource URL in metadata. +- `mcp_path`: The path where the MCP endpoint is mounted (e.g., "/mcp") + This is used to advertise the resource URL in metadata. diff --git a/src/fastmcp/server/auth/providers/descope.py b/src/fastmcp/server/auth/providers/descope.py index 9e89339aa..610acf429 100644 --- a/src/fastmcp/server/auth/providers/descope.py +++ b/src/fastmcp/server/auth/providers/descope.py @@ -7,8 +7,10 @@ for seamless MCP client authentication. from __future__ import annotations +from urllib.parse import urlparse + import httpx -from pydantic import AnyHttpUrl +from pydantic import AnyHttpUrl, field_validator from pydantic_settings import BaseSettings, SettingsConfigDict from starlette.responses import JSONResponse from starlette.routing import Route @@ -16,6 +18,7 @@ from starlette.routing import Route from fastmcp.server.auth import RemoteAuthProvider, TokenVerifier from fastmcp.server.auth.providers.jwt import JWTVerifier from fastmcp.settings import ENV_FILE +from fastmcp.utilities.auth import parse_scopes from fastmcp.utilities.logging import get_logger from fastmcp.utilities.types import NotSet, NotSetT @@ -29,9 +32,16 @@ class DescopeProviderSettings(BaseSettings): extra="ignore", ) - project_id: str + config_url: AnyHttpUrl | None = None + project_id: str | None = None + descope_base_url: AnyHttpUrl | str | None = None base_url: AnyHttpUrl - descope_base_url: AnyHttpUrl = AnyHttpUrl("https://api.descope.com") + required_scopes: list[str] | None = None + + @field_validator("required_scopes", mode="before") + @classmethod + def _parse_scopes(cls, v): + return parse_scopes(v) class DescopeProvider(RemoteAuthProvider): @@ -44,15 +54,15 @@ class DescopeProvider(RemoteAuthProvider): IMPORTANT SETUP REQUIREMENTS: - 1. Enable Dynamic Client Registration in Descope Console: - - Go to the [Inbound Apps page](https://app.descope.com/apps/inbound) of the Descope Console - - Click **DCR Settings** - - Enable **Dynamic Client Registration (DCR)** - - Define allowed scopes + 1. Create an MCP Server in Descope Console: + - Go to the [MCP Servers page](https://app.descope.com/mcp-servers) of the Descope Console + - Create a new MCP Server + - Ensure that **Dynamic Client Registration (DCR)** is enabled + - Note your Well-Known URL - 2. Note your Project ID: - - Save your Project ID from [Project Settings](https://app.descope.com/settings/project) - - Example: P2abc...123 + 2. Note your Well-Known URL: + - Save your Well-Known URL from [MCP Server Settings](https://app.descope.com/mcp-servers) + - Format: ``https://.../v1/apps/agentic/P.../M.../.well-known/openid-configuration`` For detailed setup instructions, see: https://docs.descope.com/identity-federation/inbound-apps/creating-inbound-apps#method-2-dynamic-client-registration-dcr @@ -63,9 +73,8 @@ class DescopeProvider(RemoteAuthProvider): # Create Descope metadata provider (JWT verifier created automatically) descope_auth = DescopeProvider( - project_id="P2abc...123", + config_url="https://.../v1/apps/agentic/P.../M.../.well-known/openid-configuration", base_url="https://your-fastmcp-server.com", - descope_base_url="https://api.descope.com", ) # Use with FastMCP @@ -76,50 +85,100 @@ class DescopeProvider(RemoteAuthProvider): def __init__( self, *, + config_url: AnyHttpUrl | str | NotSetT = NotSet, project_id: str | NotSetT = NotSet, - base_url: AnyHttpUrl | str | NotSetT = NotSet, descope_base_url: AnyHttpUrl | str | NotSetT = NotSet, + base_url: AnyHttpUrl | str | NotSetT = NotSet, + required_scopes: list[str] | NotSetT | None = NotSet, token_verifier: TokenVerifier | None = None, ): """Initialize Descope metadata provider. Args: - project_id: Your Descope Project ID (e.g., "P2abc...123") + config_url: Your Descope Well-Known URL (e.g., "https://.../v1/apps/agentic/P.../M.../.well-known/openid-configuration") + This is the new recommended way. If provided, project_id and descope_base_url are ignored. + project_id: Your Descope Project ID (e.g., "P2abc123"). Used with descope_base_url for backwards compatibility. + descope_base_url: Your Descope base URL (e.g., "https://api.descope.com"). Used with project_id for backwards compatibility. base_url: Public URL of this FastMCP server - descope_base_url: Descope API base URL (defaults to https://api.descope.com) + required_scopes: Optional list of scopes that must be present in validated tokens. + These scopes will be included in the protected resource metadata. token_verifier: Optional token verifier. If None, creates JWT verifier for Descope """ settings = DescopeProviderSettings.model_validate( { k: v for k, v in { + "config_url": config_url, "project_id": project_id, - "base_url": base_url, "descope_base_url": descope_base_url, + "base_url": base_url, + "required_scopes": required_scopes, }.items() if v is not NotSet } ) - self.project_id = settings.project_id self.base_url = AnyHttpUrl(str(settings.base_url).rstrip("/")) - self.descope_base_url = str(settings.descope_base_url).rstrip("/") + + # Determine which API is being used + if settings.config_url is not None: + # New API: use config_url + # Strip /.well-known/openid-configuration from config_url if present + issuer_url = str(settings.config_url) + if issuer_url.endswith("/.well-known/openid-configuration"): + issuer_url = issuer_url[: -len("/.well-known/openid-configuration")] + + # Parse the issuer URL to extract descope_base_url and project_id for other uses + parsed_url = urlparse(issuer_url) + path_parts = parsed_url.path.strip("/").split("/") + + # Extract project_id from path (format: /v1/apps/agentic/P.../M...) + if "agentic" in path_parts: + agentic_index = path_parts.index("agentic") + if agentic_index + 1 < len(path_parts): + self.project_id = path_parts[agentic_index + 1] + else: + raise ValueError( + f"Could not extract project_id from config_url: {issuer_url}" + ) + else: + raise ValueError( + f"Could not find 'agentic' in config_url path: {issuer_url}" + ) + + # Extract descope_base_url (scheme + netloc) + self.descope_base_url = f"{parsed_url.scheme}://{parsed_url.netloc}".rstrip( + "/" + ) + elif settings.project_id is not None and settings.descope_base_url is not None: + # Old API: use project_id and descope_base_url + self.project_id = settings.project_id + descope_base_url_str = str(settings.descope_base_url).rstrip("/") + # Ensure descope_base_url has a scheme + if not descope_base_url_str.startswith(("http://", "https://")): + descope_base_url_str = f"https://{descope_base_url_str}" + self.descope_base_url = descope_base_url_str + # Old issuer format + issuer_url = f"{self.descope_base_url}/v1/apps/{self.project_id}" + else: + raise ValueError( + "Either config_url (new API) or both project_id and descope_base_url (old API) must be provided" + ) # Create default JWT verifier if none provided if token_verifier is None: token_verifier = JWTVerifier( jwks_uri=f"{self.descope_base_url}/{self.project_id}/.well-known/jwks.json", - issuer=f"{self.descope_base_url}/v1/apps/{self.project_id}", + issuer=issuer_url, algorithm="RS256", audience=self.project_id, + required_scopes=settings.required_scopes, ) # Initialize RemoteAuthProvider with Descope as the authorization server super().__init__( token_verifier=token_verifier, - authorization_servers=[ - AnyHttpUrl(f"{self.descope_base_url}/v1/apps/{self.project_id}") - ], + authorization_servers=[AnyHttpUrl(issuer_url)], base_url=self.base_url, ) diff --git a/tests/server/auth/providers/test_descope.py b/tests/server/auth/providers/test_descope.py index eb6d63b47..7ddeeb0ee 100644 --- a/tests/server/auth/providers/test_descope.py +++ b/tests/server/auth/providers/test_descope.py @@ -18,30 +18,21 @@ class TestDescopeProvider: def test_init_with_explicit_params(self): """Test DescopeProvider initialization with explicit parameters.""" provider = DescopeProvider( - project_id="P2abc123", + config_url="https://api.descope.com/v1/apps/agentic/P2abc123/M123/.well-known/openid-configuration", base_url="https://myserver.com", - descope_base_url="https://api.descope.com", ) assert provider.project_id == "P2abc123" assert str(provider.base_url) == "https://myserver.com/" assert str(provider.descope_base_url) == "https://api.descope.com" - @pytest.mark.parametrize( - "scopes_env", - [ - "openid,email", - '["openid", "email"]', - ], - ) - def test_init_with_env_vars(self, scopes_env): + def test_init_with_env_vars(self): """Test DescopeProvider initialization from environment variables.""" with patch.dict( os.environ, { - "FASTMCP_SERVER_AUTH_DESCOPEPROVIDER_PROJECT_ID": "P2env123", + "FASTMCP_SERVER_AUTH_DESCOPEPROVIDER_CONFIG_URL": "https://api.descope.com/v1/apps/agentic/P2env123/M123/.well-known/openid-configuration", "FASTMCP_SERVER_AUTH_DESCOPEPROVIDER_BASE_URL": "https://envserver.com", - "FASTMCP_SERVER_AUTH_DESCOPEPROVIDER_DESCOPE_BASE_URL": "https://api.descope.com", }, ): provider = DescopeProvider() @@ -50,11 +41,32 @@ class TestDescopeProvider: assert str(provider.base_url) == "https://envserver.com/" assert str(provider.descope_base_url) == "https://api.descope.com" + def test_init_with_old_env_vars(self): + """Test DescopeProvider initialization from old environment variables (backwards compatibility).""" + with patch.dict( + os.environ, + { + "FASTMCP_SERVER_AUTH_DESCOPEPROVIDER_PROJECT_ID": "P2oldenv123", + "FASTMCP_SERVER_AUTH_DESCOPEPROVIDER_DESCOPE_BASE_URL": "https://api.descope.com", + "FASTMCP_SERVER_AUTH_DESCOPEPROVIDER_BASE_URL": "https://envserver.com", + }, + ): + provider = DescopeProvider() + + assert provider.project_id == "P2oldenv123" + assert str(provider.base_url) == "https://envserver.com/" + assert str(provider.descope_base_url) == "https://api.descope.com" + assert ( + provider.token_verifier.issuer # type: ignore[attr-defined] + == "https://api.descope.com/v1/apps/P2oldenv123" + ) + def test_environment_variable_loading(self): """Test that environment variables are loaded correctly.""" # This test verifies that the provider can be created with environment variables provider = DescopeProvider( - project_id="P2env123", base_url="http://env-server.com" + config_url="https://api.descope.com/v1/apps/agentic/P2env123/M123/.well-known/openid-configuration", + base_url="http://env-server.com", ) # Should have loaded from environment @@ -62,48 +74,101 @@ class TestDescopeProvider: assert str(provider.base_url) == "http://env-server.com/" assert str(provider.descope_base_url) == "https://api.descope.com" - def test_descope_base_url_https_prefix_handling(self): - """Test that descope_base_url handles missing https:// prefix.""" - # Without https:// - should add it + def test_config_url_parsing(self): + """Test that config_url is parsed correctly to extract base URL and project ID.""" + # Standard HTTPS URL provider1 = DescopeProvider( - project_id="P2abc123", + config_url="https://api.descope.com/v1/apps/agentic/P2abc123/M123/.well-known/openid-configuration", base_url="https://myserver.com", - descope_base_url="https://api.descope.com", ) assert str(provider1.descope_base_url) == "https://api.descope.com" + assert provider1.project_id == "P2abc123" - # With https:// - should keep it + # HTTP URL (for local testing) provider2 = DescopeProvider( - project_id="P2abc123", + config_url="http://localhost:8080/v1/apps/agentic/P2abc123/M123/.well-known/openid-configuration", base_url="https://myserver.com", - descope_base_url="https://api.descope.com", ) - assert str(provider2.descope_base_url) == "https://api.descope.com" + assert str(provider2.descope_base_url) == "http://localhost:8080" + assert provider2.project_id == "P2abc123" - # With http:// - should be preserved + # URL without .well-known/openid-configuration suffix provider3 = DescopeProvider( - project_id="P2abc123", + config_url="https://api.descope.com/v1/apps/agentic/P2abc123/M123", base_url="https://myserver.com", - descope_base_url="http://localhost:8080", ) - assert str(provider3.descope_base_url) == "http://localhost:8080" + assert str(provider3.descope_base_url) == "https://api.descope.com" + assert provider3.project_id == "P2abc123" - def test_init_defaults(self): - """Test that default values are applied correctly.""" + def test_requires_config_url_or_project_id_and_descope_base_url(self): + """Test that either config_url or both project_id and descope_base_url are required.""" + # Should raise error when neither API is provided + with pytest.raises(ValueError, match="Either config_url"): + DescopeProvider( + base_url="https://myserver.com", + ) + + def test_backwards_compatibility_with_project_id_and_descope_base_url(self): + """Test backwards compatibility with old API using project_id and descope_base_url.""" provider = DescopeProvider( project_id="P2abc123", + descope_base_url="https://api.descope.com", base_url="https://myserver.com", ) - # Check defaults + assert provider.project_id == "P2abc123" assert str(provider.descope_base_url) == "https://api.descope.com" + assert str(provider.base_url) == "https://myserver.com/" + + # Check that JWT verifier uses the old issuer format + assert ( + provider.token_verifier.issuer # type: ignore[attr-defined] + == "https://api.descope.com/v1/apps/P2abc123" + ) + assert ( + provider.token_verifier.jwks_uri # type: ignore[attr-defined] + == "https://api.descope.com/P2abc123/.well-known/jwks.json" + ) + + def test_backwards_compatibility_descope_base_url_without_scheme(self): + """Test that descope_base_url without scheme gets https:// prefix added.""" + provider = DescopeProvider( + project_id="P2abc123", + descope_base_url="api.descope.com", + base_url="https://myserver.com", + ) + + assert str(provider.descope_base_url) == "https://api.descope.com" + assert ( + provider.token_verifier.issuer # type: ignore[attr-defined] + == "https://api.descope.com/v1/apps/P2abc123" + ) + + def test_config_url_takes_precedence_over_old_api(self): + """Test that config_url takes precedence when both APIs are provided.""" + provider = DescopeProvider( + config_url="https://api.descope.com/v1/apps/agentic/P2new123/M123/.well-known/openid-configuration", + project_id="P2old123", # Should be ignored + descope_base_url="https://old.descope.com", # Should be ignored + base_url="https://myserver.com", + ) + + # Should use values from config_url, not the old API + assert provider.project_id == "P2new123" + assert str(provider.descope_base_url) == "https://api.descope.com" + assert ( + provider.token_verifier.issuer # type: ignore[attr-defined] + == "https://api.descope.com/v1/apps/agentic/P2new123/M123" + ) def test_jwt_verifier_configured_correctly(self): """Test that JWT verifier is configured correctly.""" + config_url = "https://api.descope.com/v1/apps/agentic/P2abc123/M123/.well-known/openid-configuration" + issuer_url = "https://api.descope.com/v1/apps/agentic/P2abc123/M123" + provider = DescopeProvider( - project_id="P2abc123", + config_url=config_url, base_url="https://myserver.com", - descope_base_url="https://api.descope.com", ) # Check that JWT verifier uses the correct endpoints @@ -112,19 +177,55 @@ class TestDescopeProvider: == "https://api.descope.com/P2abc123/.well-known/jwks.json" ) assert ( - provider.token_verifier.issuer == "https://api.descope.com/v1/apps/P2abc123" # type: ignore[attr-defined] + provider.token_verifier.issuer == issuer_url # type: ignore[attr-defined] ) assert provider.token_verifier.audience == "P2abc123" # type: ignore[attr-defined] + def test_required_scopes_support(self): + """Test that required_scopes are supported and passed to JWT verifier.""" + provider = DescopeProvider( + config_url="https://api.descope.com/v1/apps/agentic/P2abc123/M123/.well-known/openid-configuration", + base_url="https://myserver.com", + required_scopes=["read", "write"], + ) + + # Check that required_scopes are set on the token verifier + assert provider.token_verifier.required_scopes == ["read", "write"] # type: ignore[attr-defined] + + def test_required_scopes_with_old_api(self): + """Test that required_scopes work with the old API (project_id + descope_base_url).""" + provider = DescopeProvider( + project_id="P2abc123", + descope_base_url="https://api.descope.com", + base_url="https://myserver.com", + required_scopes=["openid", "email"], + ) + + # Check that required_scopes are set on the token verifier + assert provider.token_verifier.required_scopes == ["openid", "email"] # type: ignore[attr-defined] + + def test_required_scopes_from_env(self): + """Test that required_scopes can be set via environment variable.""" + with patch.dict( + os.environ, + { + "FASTMCP_SERVER_AUTH_DESCOPEPROVIDER_CONFIG_URL": "https://api.descope.com/v1/apps/agentic/P2env123/M123/.well-known/openid-configuration", + "FASTMCP_SERVER_AUTH_DESCOPEPROVIDER_BASE_URL": "https://envserver.com", + "FASTMCP_SERVER_AUTH_DESCOPEPROVIDER_REQUIRED_SCOPES": "read,write", + }, + ): + provider = DescopeProvider() + + assert provider.token_verifier.required_scopes == ["read", "write"] # type: ignore[attr-defined] + @pytest.fixture async def mcp_server_url(): """Start Descope server.""" mcp = FastMCP( auth=DescopeProvider( - project_id="P2test123", + config_url="https://api.descope.com/v1/apps/agentic/P2test123/M123/.well-known/openid-configuration", base_url="http://localhost:4321", - descope_base_url="https://api.descope.com", ) )