PrefectHQ/fastmcp · error · ValueError

Either high_entropy_material or low_entropy_material must be

Error message

Either high_entropy_material or low_entropy_material must be provided, but not both

What it means

derive_jwt_key() accepts exactly one kind of key material: high-entropy material (used directly via HKDF) or low-entropy material (run through PBKDF2 with iterations). Passing both is ambiguous — the function cannot know which derivation the server that will verify the token expects — so it raises ValueError immediately.

Source

Thrown at fastmcp_slim/fastmcp/server/auth/jwt_issuer.py:47

@overload
def derive_jwt_key(*, high_entropy_material: str, salt: str) -> bytes:
    """Derive JWT signing key from a high-entropy key material and server salt."""


@overload
def derive_jwt_key(*, low_entropy_material: str, salt: str) -> bytes:
    """Derive JWT signing key from a low-entropy key material and server salt."""


def derive_jwt_key(
    *,
    high_entropy_material: str | None = None,
    low_entropy_material: str | None = None,
    salt: str,
) -> bytes:
    """Derive JWT signing key from a high-entropy or low-entropy key material and server salt."""
    if high_entropy_material is not None and low_entropy_material is not None:
        raise ValueError(
            "Either high_entropy_material or low_entropy_material must be provided, but not both"
        )

    if high_entropy_material is not None:
        derived_key = HKDF(
            algorithm=hashes.SHA256(),
            length=32,
            salt=salt.encode(),
            info=b"Fernet",
        ).derive(key_material=high_entropy_material.encode())

        return base64.urlsafe_b64encode(derived_key)

    if low_entropy_material is not None:
        iterations = (
            KDF_ITERATIONS_TEST if fastmcp.settings.test_mode else KDF_ITERATIONS
        )
        pbkdf2 = PBKDF2HMAC(

View on GitHub (pinned to 1f02114297)

Solutions

  1. Remove one of the two arguments so only high_entropy_material or only low_entropy_material is passed
  2. If migrating from low- to high-entropy material, gate with an if/else choosing exactly one based on which secret exists
  3. Validate your settings object before constructing the issuer and fail fast with a clear config error
  4. Ensure only the matching derivation is used on all server replicas — verifier and issuer must agree

Example fix

// before
derive_jwt_key(high_entropy_material=high, low_entropy_material=low, salt=salt)
// after
if high:
    derive_jwt_key(high_entropy_material=high, salt=salt)
else:
    derive_jwt_key(low_entropy_material=low, salt=salt)
Defensive patterns

Strategy: validation

Validate before calling

if key_material.high is not None and key_material.low is not None:
    raise ValueError("Configure exactly one of high- or low-entropy signing material")
derive_jwt_key(
    high_entropy_material=key_material.high,
    low_entropy_material=key_material.low,
    salt=salt,
)

Type guard

def has_exactly_one_material(high: str | None, low: str | None) -> bool:
    return (high is None) != (low is None)

Prevention

When it happens

Trigger: Calling derive_jwt_key(salt=...) with both high_entropy_material and low_entropy_material set to non-None values (jwt_issuer.py:47).

Common situations: Config loaders populate both settings from env vars and pass everything through; a refactor added low_entropy_material without removing the old high_entropy_material argument; secrets management injects both a legacy secret and a new generated key.

Related errors


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