PrefectHQ/fastmcp · error · ValueError

Version string cannot contain '@' (used as key delimiter): {

Error message

Version string cannot contain '@' (used as key delimiter): {version!r}

What it means

FastMCPComponent versions are embedded in the component's canonical `key`, which uses '@' as a delimiter between the identifier and version. A version string containing '@' would corrupt key parsing and component identity, so `_coerce_version` rejects it eagerly at construction time. Versions may be str, int, or float; only the '@' character is forbidden.

Source

Thrown at fastmcp_slim/fastmcp/utilities/components.py:64


def _coerce_version(v: str | int | float | None) -> str | None:
    """Coerce version to string, accepting int, float, or str.

    Raises TypeError for non-scalar types (list, dict, set, etc.).
    Raises ValueError if version contains '@' (used as key delimiter).
    """
    if v is None:
        return None
    if isinstance(v, bool):
        raise TypeError(f"Version must be a string, int, or float, got bool: {v!r}")
    if not isinstance(v, (str, int, float)):
        raise TypeError(
            f"Version must be a string, int, or float, got {type(v).__name__}: {v!r}"
        )
    version = str(v)
    if "@" in version:
        raise ValueError(
            f"Version string cannot contain '@' (used as key delimiter): {version!r}"
        )
    return version


class FastMCPComponent(FastMCPBaseModel):
    """Base class for FastMCP tools, prompts, resources, and resource templates."""

    KEY_PREFIX: ClassVar[str] = ""

    def __init_subclass__(cls, **kwargs: Any) -> None:
        super().__init_subclass__(**kwargs)
        # Warn if a subclass doesn't define KEY_PREFIX (inherited or its own)
        if not cls.KEY_PREFIX:
            import warnings

            warnings.warn(
                f"{cls.__name__} does not define KEY_PREFIX. "

View on GitHub (pinned to 1f02114297)

Solutions

  1. Remove the '@' character from the version string (e.g. '1.0@beta' -> '1.0-beta').
  2. Pass a plain str/int/float version; keep '@' out of it.
  3. If the '@' encodes extra metadata, move it into the component's `meta` dict instead.

Example fix

// before
mcp.add_tool(fn, version="2.1@rc1")
// after
mcp.add_tool(fn, version="2.1-rc1")
Defensive patterns

Strategy: validation

Validate before calling

def check_version(v):
    if isinstance(v, str) and '@' in v:
        raise ValueError(f"version must not contain '@': {v!r}")
    if not isinstance(v, (str, int, float)):
        raise TypeError(f"version must be str/int/float, got {type(v).__name__}")

Type guard

def is_valid_version(v) -> bool:
    return isinstance(v, (str, int, float)) and not (isinstance(v, str) and '@' in v)

Try / catch

try:
    comp = make_component(version=v)
except (TypeError, ValueError) as e:
    log.error('invalid component version: %s', e)
    comp = make_component(version=str(v).replace('@', '-'))

Prevention

When it happens

Trigger: Passing `version="1.0@beta"` (or any string containing '@') to a Tool/Resource/Prompt/FastMCPComponent constructor or decorator argument.

Common situations: Encoding 'name@version' copied from package-manager notation (npm, uv) into the version field; templated version strings built from 'user@host' values.

Related errors


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