PrefectHQ/fastmcp · error · ValueError

require_roles() needs at least one role; a check with no rol

Error message

require_roles() needs at least one role; a check with no roles would admit any authenticated caller.

What it means

require_roles() rejects an empty role list at construction time. A role check with zero roles would trivially pass for any authenticated user, which the API treats as almost certainly a programming mistake, so it raises ValueError instead of creating the guard.

Source

Thrown at fastmcp_slim/fastmcp/utilities/authorization.py:190

    Unlike `require_scopes`, this check cannot signal a shortfall: OAuth has no
    way to request a role, so there is no `insufficient_scope` challenge to
    emit. A role denial is therefore reported as a plain `AuthorizationError`,
    and it suppresses any scope shortfall alongside it — a caller blocked by
    their role must not be told to go obtain a scope that would not help.
    Scope shortfalls are still reported normally whenever the role check
    passes.

    Args:
        *roles: Roles the caller must hold. All are required (AND logic).
        extract: Callable mapping the token's claims to the caller's roles.

    Raises:
        ValueError: If no roles are given, which would allow any authenticated
            caller and is more likely a mistake than an intent.
    """
    if not roles:
        raise ValueError(
            "require_roles() needs at least one role; a check with no roles "
            "would admit any authenticated caller."
        )
    return _RequireRoles(roles, extract)


def restrict_tag(tag: str, *, scopes: list[str]) -> AuthCheck:
    """Require scopes when the accessed component has a specific tag."""
    return _RestrictTag(tag, scopes)


def scope_requirements(
    checks: AuthCheck | list[AuthCheck],
    ctx: AuthContext,
) -> list[str] | None:
    """Scopes a check list requires but the token lacks, without running it.

    Returns ``None`` when the list contains any opaque (non-scope) check. Such a

View on GitHub (pinned to 1f02114297)

Solutions

  1. Pass at least one explicit role to require_roles
  2. Fix the config/env source so the role list is populated
  3. If the intent is 'any authenticated user', use an authenticated-only check instead of require_roles

Example fix

// before
roles = settings.allowed_roles  # []
guard = require_roles(*roles)

// after
assert settings.allowed_roles, "allowed_roles must not be empty"
guard = require_roles(*settings.allowed_roles)
Defensive patterns

Strategy: validation

Validate before calling

roles = settings.allowed_roles or []
if not roles:
    raise ValueError("configure at least one role before building the guard")
guard = require_roles(*roles, extract=extract)

Try / catch

try:
    guard = require_roles(*configured_roles)
except ValueError:
    logger.error("allowed_roles is empty; check config")
    raise

Prevention

When it happens

Trigger: Calling require_roles() or require_roles([]) (or with only keyword defaults and no roles), typically when the role list is computed dynamically from an empty collection.

Common situations: Loading allowed roles from config/env that ends up empty; filtering out roles the user lacks before passing them in; typos reading a config key that returns [].

Understand the failure class

Related errors


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