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 aView on GitHub (pinned to 1f02114297)
Solutions
- Pass at least one explicit role to require_roles
- Fix the config/env source so the role list is populated
- 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
- Fail fast at config load if role lists are empty
- Never pass dynamically filtered role lists directly into require_roles
- Use an authenticated-only policy when 'any user' is the real intent
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
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- Subclasses must implement verify_token
- MultiAuth requires at least a server or one verifier
- Unknown tool: {name!r}
- Invalid JWT format (expected 3 parts)
- OAuth authorization failed: {error} - {error_desc}
AI-assisted analysis of PrefectHQ/fastmcp@1f02114297 (2026-08-29).
Data as JSON: /api/errors/cb8dda61777bb19d.
Report an issue: GitHub.