PrefectHQ/fastmcp · error · ValueError

cache_ttl must be a positive integer, got {cache_ttl}

Error message

cache_ttl must be a positive integer, got {cache_ttl}

What it means

build_cache_hints validates the FastMCP constructor's cache_ttl argument (seconds) used to emit SEP-2549 client-side cache hints. A non-positive TTL (zero or negative) cannot produce a meaningful cache lifetime, so the server refuses to start rather than silently emitting an inert or invalid hint. The TTL is converted to milliseconds for the wire (ttl_ms = cache_ttl * 1000).

Source

Thrown at fastmcp_slim/fastmcp/server/caching.py:56

    `cache_ttl` is meaningless (the client gates caching on the presence of a
    TTL) and is rejected rather than silently ignored.

    Returns `None` when no hint is set, or a map applying the same hint to every
    SDK-cacheable method otherwise.

    Raises:
        ValueError: If `cache_ttl` is not positive, or if `cache_scope` is set
            without `cache_ttl`.
    """
    if cache_ttl is None:
        if cache_scope is not None:
            raise ValueError(
                "cache_scope requires cache_ttl; a scope without a TTL does not "
                "enable caching"
            )
        return None
    if cache_ttl <= 0:
        raise ValueError(f"cache_ttl must be a positive integer, got {cache_ttl}")
    hint = CacheHint(ttl_ms=cache_ttl * 1000, scope=cache_scope or "private")
    return dict.fromkeys(get_args(CacheableMethod), hint)

View on GitHub (pinned to 1f02114297)

Solutions

  1. Pass a positive integer for cache_ttl (seconds), e.g. cache_ttl=60.
  2. To disable caching, omit cache_ttl entirely (pass None), not 0.
  3. If the value comes from config/env, coerce and validate before constructing FastMCP: ttl = int(raw) if raw else None.
  4. Note cache_scope cannot be given without cache_ttl; set both together or neither.

Example fix

// before
mcp = FastMCP("srv", cache_ttl=0, cache_scope="private")
// after
mcp = FastMCP("srv", cache_ttl=300, cache_scope="private")
# or disable: mcp = FastMCP("srv")
Defensive patterns

Strategy: validation

Validate before calling

if cache_ttl is not None and (not isinstance(cache_ttl, int) or cache_ttl <= 0):
    raise ValueError(f"cache_ttl must be a positive integer, got {cache_ttl!r}")

Try / catch

try:
    mcp = FastMCP("srv", cache_ttl=cfg.ttl, cache_scope=cfg.scope)
except ValueError as e:
    logging.error("bad cache config: %s", e)
    mcp = FastMCP("srv")  # caching disabled

Prevention

When it happens

Trigger: Passing cache_ttl=0, cache_ttl<0, or a non-integer (e.g. 1.5) to FastMCP(..., cache_ttl=...). The check runs in build_cache_hints, called from FastMCP.__init__, so any construction with such a value raises immediately.

Common situations: Copy-pasted config where the TTL was left at a placeholder 0; unit tests probing invalid input; config-driven servers where a YAML/env value of 0 or -1 means 'disabled' to the author but is passed straight through as a number instead of None.

Understand the failure class

Background: "Invalid configuration value" and "Unsupported/Unknown setting value" errors: why libraries reject your config strings, numbers, and types — this error's family across 30 libraries.

Related errors


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