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
- Set MCP_JWT_TTL_SECONDS (or ttl_seconds) to a positive number of seconds, e.g. 3600
- If you want long-lived tokens, use a large TTL rather than 0 - zero is rejected by design
- 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
- Never use 0 to try to disable expiry - pick a real lifetime (e.g. 300-3600s)
- Omit ttl_seconds/MCP_JWT_TTL_SECONDS to inherit the signer default
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
- MCPJWTSigner guardrail requires a guardrail_name
- MCPJWTSigner guardrail '{guardrail_name}' has mode='{mode}'
- MCP Security: guardrail_name is required
- mcp_tools_config is required, please set `mcp_tools` in your
- DynamoAI API key is required. Set DYNAMOAI_API_KEY environme
AI-assisted analysis of BerriAI/litellm@77b7c6c40c (2026-08-18).
Data as JSON: /api/errors/0c662e22f1c0f27b.
Report an issue: GitHub.