PrefectHQ/fastmcp · warning · UserWarning

Component keys are missing the '@' version delimiter and wil

Error message

Component keys are missing the '@' version delimiter and will match nothing: {malformed}. A key always ends in '@' for an unversioned component (e.g. 'tool:my_tool@') or '@<version>' for a versioned one. Read the value from `component.key`, or filter by `names` instead.

What it means

Visibility/filter transforms match components by `key`, and keys always contain a version delimiter: `type:name@` when unversioned or `type:name@<version>` when versioned. A `keys` entry lacking `@` can never match, so `VisibilityTransform.__init__` warns that the filter is effectively dead and suggests using `component.key` or `names`.

Source

Thrown at fastmcp_slim/fastmcp/server/transforms/visibility.py:88

        | None = None,
        match_all: bool = False,
    ) -> None:
        """Initialize a visibility marker.

        Args:
            enabled: If True, mark matching as enabled; if False, mark as disabled.
            names: Component names or URIs to match.
            keys: Component keys to match (e.g., {"tool:my_tool@v1"}).
            version: Component version spec to match. Unversioned components (version=None)
                will NOT match a version spec.
            tags: Tags to match (component must have at least one).
            components: Component types to match (e.g., {"tool", "prompt"}).
            match_all: If True, matches all components regardless of other criteria.
        """
        if keys:
            malformed = sorted(key for key in keys if "@" not in key)
            if malformed:
                warnings.warn(
                    f"Component keys are missing the '@' version delimiter and will "
                    f"match nothing: {malformed}. A key always ends in '@' for an "
                    f"unversioned component (e.g. 'tool:my_tool@') or '@<version>' "
                    f"for a versioned one. Read the value from `component.key`, or "
                    f"filter by `names` instead.",
                    UserWarning,
                    stacklevel=3,
                )

        self._enabled = enabled
        self.names = names
        self.keys = keys
        self.version = version
        self.tags = tags  # e.g., {"internal", "deprecated"}
        self.components = components  # e.g., {"tool", "prompt"}
        self.match_all = match_all

    def __repr__(self) -> str:

View on GitHub (pinned to 1f02114297)

Solutions

  1. Use full keys read from the component: e.g. `keys={tool.key}` or `keys={"tool:my_tool@"}`.
  2. If you intend plain-name matching, pass those strings via the `names` parameter instead.
  3. Normalize stored keys to include the `@` suffix when persisting filters.
  4. Run with the warning enabled in tests to catch malformed keys early.

Example fix

// before
VisibilityTransform(keys={"my_tool"})
// after
VisibilityTransform(keys={"tool:my_tool@"})  # or VisibilityTransform(names={"my_tool"})
Defensive patterns

Strategy: validation

Validate before calling

def ensure_keys_valid(keys: set[str]) -> None:
    bad = [k for k in keys if "@" not in k]
    assert not bad, f"keys missing '@' version delimiter: {bad}"

Type guard

def is_well_formed_key(key: str) -> bool:
    return "@" in key

Try / catch

import warnings
with warnings.catch_warnings(record=True) as caught:
    warnings.simplefilter("always")
    t = VisibilityTransform(keys=keys)
if any("version delimiter" in str(w.message) for w in caught):
    raise ValueError("malformed component keys in VisibilityTransform")

Prevention

When it happens

Trigger: `VisibilityTransform(keys={"my_tool"})` or passing plain names/URIs into `keys=` — any string without `@` in the `keys` set.

Common situations: Users confusing `names` with `keys` and passing tool names into `keys`; reading keys from logs that truncated the version suffix; pre-versioning code from before keys gained the `@` delimiter.

Understand the failure class

Related errors


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