PrefectHQ/fastmcp · error · ValueError

Could not extract project_id from config_url: {issuer_url}

Error message

Could not extract project_id from config_url: {issuer_url}

What it means

DescopeProvider._parse_descope_config_url derives the Descope project_id from the config_url (issuer URL) — typically a path segment like /v2/<project-id> or a 'ProjectId' path part. If parsing yields no project_id (including the special 'agentic' placeholder case), the provider cannot build Descope endpoints, so it raises ValueError.

Source

Thrown at fastmcp_slim/fastmcp/server/auth/providers/descope.py:55

    issuer_url = openid_url[: -len(_OPENID_WK)]
    parsed = urlparse(issuer_url)
    parts = parsed.path.strip("/").split("/")
    descope_base_url = f"{parsed.scheme}://{parsed.netloc}".rstrip("/")

    if "agentic" in parts:
        index = parts.index("agentic") + 1
        project_id = parts[index] if index < len(parts) else ""
    elif "apps" in parts:
        index = parts.index("apps") + 1
        project_id = parts[index] if index < len(parts) else ""
        if project_id == "agentic":
            project_id = ""
    else:
        project_id = ""

    if not project_id:
        raise ValueError(f"Could not extract project_id from config_url: {issuer_url}")

    return descope_base_url, project_id, issuer_url, openid_url


async def _discover_scopes(openid_configuration_url: str) -> list[str] | None:
    try:
        async with httpx2.AsyncClient() as client:
            response = await client.get(openid_configuration_url, timeout=10.0)
            response.raise_for_status()
        scopes = response.json().get("scopes_supported")
        if isinstance(scopes, list):
            parsed = [scope for scope in scopes if isinstance(scope, str)]
            if not scopes or parsed:
                return parsed
    except Exception:
        logger.warning(
            "Failed to fetch Descope OpenID configuration from %s",
            openid_configuration_url,

View on GitHub (pinned to 1f02114297)

Solutions

  1. Include the project id in config_url, e.g. https://api.descope.com/v2/P2abcd1234 or https://api.descope.com/<project_id>.
  2. Copy the Project ID from the Descope console (Project Settings) and rebuild the URL.
  3. If using an agentic URL form, supply the project id through the provider argument that DescopeProvider supports instead of relying on config_url parsing.

Example fix

// before
DescopeProvider(config_url="https://api.descope.com")
// after
DescopeProvider(config_url="https://api.descope.com/v2/P2abcd1234")
Defensive patterns

Strategy: validation

Validate before calling

import re
def validate_descope_config_url(url: str) -> bool:
    return re.search(r"/P[0-9a-zA-Z]{8,}", url) is not None

Type guard

def has_project_id(url: str) -> bool:
    from urllib.parse import urlparse
    parts = [p for p in urlparse(url).path.split("/") if p]
    return len(parts) > 1 and parts[-1] not in ("", "agentic")

Try / catch

try:
    provider = DescopeProvider(config_url=url)
except ValueError as e:
    if "Could not extract project_id" in str(e):
        provider = DescopeProvider(config_url=f"https://api.descope.com/v2/{DESCOPE_PROJECT_ID}")
    else:
        raise

Prevention

When it happens

Trigger: DescopeProvider(config_url='https://api.descope.com') or any URL whose path doesn't contain the project id, so project_id parses to '' (or 'agentic' which is reset to '').

Common situations: Omitting the project id from the issuer URL; copying a bare Descope API base URL; using the 'agentic' URL form which intentionally maps to empty project_id and requires an explicit project id elsewhere; typos like 'v1' paths without the id.

Related errors


AI-assisted analysis of PrefectHQ/fastmcp@1f02114297 (2026-08-29). Data as JSON: /api/errors/865509907a05c9dc. Report an issue: GitHub.