BerriAI/litellm · error · ValueError

MCPJWTSigner: environment variable '{env_var}' is set but em

Error message

MCPJWTSigner: environment variable '{env_var}' is set but empty.

What it means

MCPJWTSigner loads its RSA private key from MCP_JWT_SIGNING_KEY, which may hold a PEM string or a file:// path, and _load_private_key_from_env raises ValueError when the variable's value is empty. Note the guard only runs when the variable is present, so this specifically means 'set to an empty string'; when the variable is absent entirely the signer generates an ephemeral key instead.

Source

Thrown at litellm/proxy/guardrails/guardrail_hooks/mcp_jwt_signer/mcp_jwt_signer.py:129

_mcp_jwt_signer_instance: Optional["MCPJWTSigner"] = None

_MCP_JWT_CALL_TYPES: Final = frozenset({"call_mcp_tool", "list_mcp_tools"})

# Simple in-memory JWKS cache: keyed by JWKS URI → (keys_list, fetched_at).
_jwks_cache: Final[dict[str, tuple[Sequence[Mapping[str, object]], float]]] = {}
_JWKS_CACHE_TTL: Final = 3600  # 1 hour


def get_mcp_jwt_signer() -> Optional["MCPJWTSigner"]:
    """Return the active MCPJWTSigner singleton, or None if not initialized."""
    return _mcp_jwt_signer_instance


def _load_private_key_from_env(env_var: str) -> RSAPrivateKey:
    """Load an RSA private key from an env var (PEM string or file:// path)."""
    key_material: Final = os.environ.get(env_var, "")
    if not key_material:
        raise ValueError(f"MCPJWTSigner: environment variable '{env_var}' is set but empty.")
    if key_material.startswith("file://"):
        path: Final = key_material[len("file://") :]
        with open(path, "rb") as f:
            key_bytes = f.read()
    else:
        key_bytes = key_material.encode("utf-8")
    return serialization.load_pem_private_key(key_bytes, password=None)


def _generate_rsa_key_pair() -> RSAPrivateKey:
    """Generate a new RSA-2048 private key."""
    return rsa.generate_private_key(
        public_exponent=65537,
        key_size=2048,
    )


def _int_to_base64url(n: int) -> str:

View on GitHub (pinned to 77b7c6c40c)

Solutions

  1. Populate MCP_JWT_SIGNING_KEY with a valid RSA private key in PEM form, or a file:// URL pointing at the PEM file
  2. If you do not need a stable key, unset MCP_JWT_SIGNING_KEY entirely - the signer then generates an ephemeral RSA-2048 key at startup
  3. Fix the secret source (Kubernetes secret name/key, vault path) so the variable receives a real value instead of an empty string

Example fix

# before - set but empty, raises ValueError at startup
export MCP_JWT_SIGNING_KEY=""

# after - either unset it (ephemeral key)
unset MCP_JWT_SIGNING_KEY
# or point at a real PEM file
export MCP_JWT_SIGNING_KEY="file:///etc/litellm/keys/mcp_jwt.pem"
Defensive patterns

Strategy: validation

Validate before calling

import os  
  
def check_mcp_jwt_signing_key() -> None:  
    val = os.environ.get("MCP_JWT_SIGNING_KEY")  
    if val is not None:  
        assert val.strip(), "MCP_JWT_SIGNING_KEY is set but empty - populate it or unset it (ephemeral key)"  
        material = val[len("file://"):] if val.startswith("file://") else val  
        if val.startswith("file://"):  
            import os.path  
            assert os.path.isfile(material) and os.path.getsize(material) > 0, f"key file missing/empty: {material}"  
        else:  
            assert "PRIVATE KEY" in val, "MCP_JWT_SIGNING_KEY does not look like a PEM private key"  
  
check_mcp_jwt_signing_key()  # run before starting the proxy

Prevention

When it happens

Trigger: MCP_JWT_SIGNING_KEY="" exported in the proxy environment - typically from a secrets loader writing an empty value (secret missing in the store), or a .env line like MCP_JWT_SIGNING_KEY= with no value.

Common situations: Docker/Kubernetes env populated from a missing or misnamed secret; CI pipelines where the key secret was never created; shell scripts exporting the var conditionally and leaving it empty; 'file://' pointing at an empty file is a sibling failure.

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 BerriAI/litellm@77b7c6c40c (2026-08-18). Data as JSON: /api/errors/baa241aa9061653f. Report an issue: GitHub.