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

What it means

derive_jwt_key() requires some key material: with neither high_entropy_material nor low_entropy_material provided there is nothing to derive a signing key from, so the function raises ValueError. High-entropy material goes through HKDF, low-entropy through PBKDF2; the 'neither' path falls through to this error at jwt_issuer.py:74.

Source

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

            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(
            algorithm=hashes.SHA256(),
            length=32,
            salt=salt.encode(),
            iterations=iterations,
        ).derive(key_material=low_entropy_material.encode())

        return base64.urlsafe_b64encode(pbkdf2)

    raise ValueError(
        "Either high_entropy_material or low_entropy_material must be provided"
    )


class JWTIssuer:
    """Issues and validates FastMCP-signed JWT tokens using HS256.

    This issuer creates JWT tokens for MCP clients with proper audience claims,
    maintaining OAuth 2.0 token boundaries. Tokens are signed with HS256 using
    a key derived from the upstream client secret.
    """

    def __init__(
        self,
        issuer: str,
        audience: str,
        signing_key: bytes,
    ):

View on GitHub (pinned to 1f02114297)

Solutions

  1. Set the signing-secret environment variable / config so one material is populated before calling derive_jwt_key
  2. Check that your .env or secret mount is actually loaded (wrong working directory, missing env_file in deployment)
  3. Add startup validation that fails fast if neither material is configured, with a message naming the expected variable
  4. Generate a strong high-entropy secret (e.g. secrets.token_urlsafe(32)) if none exists

Example fix

// before
derive_jwt_key(salt=salt)  # no material configured
// after
secret = os.environ["FASTMCP_JWT_SECRET"]
derive_jwt_key(high_entropy_material=secret, salt=salt)
Defensive patterns

Strategy: validation

Validate before calling

secret = os.environ.get("FASTMCP_JWT_SECRET")
if not secret:
    raise RuntimeError("FASTMCP_JWT_SECRET is not set; cannot derive JWT signing key")
derive_jwt_key(high_entropy_material=secret, salt=salt)

Type guard

def can_derive_key(high: str | None, low: str | None) -> bool:
    return high is not None or low is not None

Prevention

When it happens

Trigger: Calling derive_jwt_key(salt=...) with both material arguments as None (or omitted) — typically when settings/env vars for the signing secret are unset so the caller passes None through.

Common situations: Missing environment variable (e.g. FASTMCP_JWT_SECRET not set in a fresh deployment); secrets manager returned empty on startup and the code passes None silently; a config branch that only fills one material under certain flags; local dev without the .env file loaded.

Understand the failure class

Background: "environment variable is not set" and "Missing keys in environment" errors: what missing required env var messages mean and how to fix them — this error's family across 28 libraries.

Related errors


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