PrefectHQ/fastmcp · error · TypeError

Invalid timeout type: {type(value)}

Error message

Invalid timeout type: {type(value)}

What it means

normalize_timeout_to_timedelta converts timeout settings into datetime.timedelta, accepting None, timedelta, or int/float seconds. Any other type (str, None-like objects, Decimal, etc.) raises TypeError naming the offending type. Zero/None semantics are handled; only the type is policed here.

Source

Thrown at fastmcp_slim/fastmcp/utilities/timeout.py:25

def normalize_timeout_to_timedelta(
    value: int | float | datetime.timedelta | None,
) -> datetime.timedelta | None:
    """Normalize a timeout value to a timedelta.

    Args:
        value: Timeout value as int/float (seconds), timedelta, or None

    Returns:
        timedelta if value provided, None otherwise
    """
    if value is None:
        return None
    if isinstance(value, datetime.timedelta):
        return value
    if isinstance(value, int | float):
        return datetime.timedelta(seconds=float(value))
    raise TypeError(f"Invalid timeout type: {type(value)}")


def normalize_timeout_to_seconds(
    value: int | float | datetime.timedelta | None,
) -> float | None:
    """Normalize a timeout value to seconds (float).

    Args:
        value: Timeout value as int/float (seconds), timedelta, or None.
            Zero values are treated as "disabled" and return None.

    Returns:
        float seconds if value provided and non-zero, None otherwise
    """
    if value is None:
        return None
    if isinstance(value, datetime.timedelta):
        seconds = value.total_seconds()

View on GitHub (pinned to 1f02114297)

Solutions

  1. Convert to a numeric type or timedelta before passing: float(value) or datetime.timedelta(seconds=...)
  2. Parse config/env strings explicitly: timeout = float(os.environ["TIMEOUT"])
  3. Pass None for 'no timeout' rather than a string like "none"

Example fix

// before
component = Tool(..., task_config={"timeout": "30"})

// after
component = Tool(..., task_config={"timeout": 30})  # or datetime.timedelta(seconds=30)
Defensive patterns

Strategy: type-guard

Validate before calling

def coerce_timeout(value) -> datetime.timedelta | None:
    if value is None or isinstance(value, (datetime.timedelta, int, float)):
        return value if not isinstance(value, str) else float(value)
    if isinstance(value, str):
        return float(value)
    raise TypeError(f"unsupported timeout: {type(value)}")

Type guard

def is_valid_timeout(v) -> bool:
    return v is None or isinstance(v, (datetime.timedelta, int, float)) and not isinstance(v, bool)

Try / catch

try:
    comp = Tool(..., task_config={"timeout": raw_timeout})
except TypeError as e:
    if "Invalid timeout type" in str(e):
        raw_timeout = float(raw_timeout)  # strings from env/config
    else:
        raise

Prevention

When it happens

Trigger: Passing a timeout as a string ("30"), Decimal, or other non-numeric type to a component/constructor that normalizes via this function (e.g. timeout="30" in task configuration).

Common situations: Reading timeout values from environment variables or config files where they arrive as strings without conversion; JSON configs producing strings; mixing up seconds vs timedelta in typed APIs.

Understand the failure class

Related errors


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