BerriAI/litellm · error · ValueError

MCPJWTSigner: ttl_seconds must be > 0, got {resolved_ttl}

Error message

MCPJWTSigner: ttl_seconds must be > 0, got {resolved_ttl}

What it means

MCPJWTSigner resolves its JWT lifetime from the ttl_seconds constructor argument or the MCP_JWT_TTL_SECONDS environment variable (defaulting to DEFAULT_TTL), then requires the value to be a positive integer. A zero or negative resolved value raises ValueError at construction, i.e. at proxy startup.

Source

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

            self._persistent_key = False
            verbose_proxy_logger.info(
                "MCPJWTSigner: auto-generated RSA-2048 keypair (set %s to use your own key)",
                self.SIGNING_KEY_ENV,
            )

        self._public_key = self._private_key.public_key()
        self._kid = _compute_kid(self._public_key)

        # --- Core config ---
        self.issuer: str = (
            issuer or os.environ.get("MCP_JWT_ISSUER") or os.environ.get("LITELLM_EXTERNAL_URL") or "litellm"
        )
        self.audience: str = audience or os.environ.get("MCP_JWT_AUDIENCE") or self.DEFAULT_AUDIENCE
        resolved_ttl: Final = int(
            ttl_seconds if ttl_seconds is not None else os.environ.get("MCP_JWT_TTL_SECONDS", str(self.DEFAULT_TTL))
        )
        if resolved_ttl <= 0:
            raise ValueError(f"MCPJWTSigner: ttl_seconds must be > 0, got {resolved_ttl}")
        self.ttl_seconds: int = resolved_ttl

        # --- FR-5: Verify + re-sign ---
        self.access_token_discovery_uri: str | None = access_token_discovery_uri
        self.token_introspection_endpoint: str | None = token_introspection_endpoint
        self.verify_issuer: str | None = verify_issuer
        self.verify_audience: str | None = verify_audience
        # Cached OIDC discovery document (fetched lazily, TTL = 24 h)
        self._oidc_discovery_doc: _OIDCDiscoveryDocument | None = None
        self._oidc_discovery_fetched_at: float = 0.0

        # --- FR-12: End-user identity mapping ---
        # Default chain: try incoming JWT sub, fall back to litellm user_id
        self.end_user_claim_sources: list[str] = end_user_claim_sources or [
            "token:sub",
            "litellm:user_id",
        ]

View on GitHub (pinned to 77b7c6c40c)

Solutions

  1. Set MCP_JWT_TTL_SECONDS (or ttl_seconds) to a positive number of seconds, e.g. 3600
  2. If you want long-lived tokens, use a large TTL rather than 0 - zero is rejected by design
  3. Remove the variable to fall back to the signer's DEFAULT_TTL

Example fix

# before - zero TTL rejected
litellm_params:
  ttl_seconds: 0

# after - one-hour tokens
litellm_params:
  ttl_seconds: 3600
Defensive patterns

Strategy: validation

Validate before calling

import os  
  
ttl = os.environ.get("MCP_JWT_TTL_SECONDS")  
if ttl is not None:  
    ttl_i = int(ttl)  
    assert ttl_i > 0, f"MCP_JWT_TTL_SECONDS must be > 0, got {ttl_i}"

Type guard

def is_positive_int(v: object) -> bool:  
    return isinstance(v, int) and not isinstance(v, bool) and v > 0

Prevention

When it happens

Trigger: MCP_JWT_TTL_SECONDS=0 (or a negative number) in the proxy environment, or ttl_seconds <= 0 passed via guardrail litellm_params. A non-numeric value fails earlier inside int().

Common situations: 'Disable expiry' attempts by setting TTL to 0; copy-paste from examples with placeholder values; templating that renders an empty or zero default; unit tests constructing the signer with ttl_seconds=0.

Understand the failure class

Background: Config validation failed: what "invalid value for {key}" and settings-rejection errors mean across 19 open-source libraries — this error's family across 19 libraries.

Related errors


AI-assisted analysis of BerriAI/litellm@77b7c6c40c (2026-08-18). Data as JSON: /api/errors/0c662e22f1c0f27b. Report an issue: GitHub.