{"record":{"id":"a0d3920d7a5bbfa6","repo":"PrefectHQ/fastmcp","slug":"component-keys-are-missing-the-version-delimit","errorCode":null,"errorMessage":"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.","messagePattern":"Component keys are missing the '@' version delimiter and will match nothing: (.+?)\\. 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\\.","errorType":"console","errorClass":"UserWarning","httpStatus":null,"severity":"warning","filePath":"fastmcp_slim/fastmcp/server/transforms/visibility.py","lineNumber":88,"sourceCode":"        | None = None,\n        match_all: bool = False,\n    ) -> None:\n        \"\"\"Initialize a visibility marker.\n\n        Args:\n            enabled: If True, mark matching as enabled; if False, mark as disabled.\n            names: Component names or URIs to match.\n            keys: Component keys to match (e.g., {\"tool:my_tool@v1\"}).\n            version: Component version spec to match. Unversioned components (version=None)\n                will NOT match a version spec.\n            tags: Tags to match (component must have at least one).\n            components: Component types to match (e.g., {\"tool\", \"prompt\"}).\n            match_all: If True, matches all components regardless of other criteria.\n        \"\"\"\n        if keys:\n            malformed = sorted(key for key in keys if \"@\" not in key)\n            if malformed:\n                warnings.warn(\n                    f\"Component keys are missing the '@' version delimiter and will \"\n                    f\"match nothing: {malformed}. A key always ends in '@' for an \"\n                    f\"unversioned component (e.g. 'tool:my_tool@') or '@<version>' \"\n                    f\"for a versioned one. Read the value from `component.key`, or \"\n                    f\"filter by `names` instead.\",\n                    UserWarning,\n                    stacklevel=3,\n                )\n\n        self._enabled = enabled\n        self.names = names\n        self.keys = keys\n        self.version = version\n        self.tags = tags  # e.g., {\"internal\", \"deprecated\"}\n        self.components = components  # e.g., {\"tool\", \"prompt\"}\n        self.match_all = match_all\n\n    def __repr__(self) -> str:","sourceCodeStart":70,"sourceCodeEnd":106,"githubUrl":"https://github.com/PrefectHQ/fastmcp/blob/1f021142978e0861cd910c8df4e8074bc7cf3978/fastmcp_slim/fastmcp/server/transforms/visibility.py#L70-L106","documentation":"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`.","triggerScenarios":"`VisibilityTransform(keys={\"my_tool\"})` or passing plain names/URIs into `keys=` — any string without `@` in the `keys` set.","commonSituations":"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.","solutions":["Use full keys read from the component: e.g. `keys={tool.key}` or `keys={\"tool:my_tool@\"}`.","If you intend plain-name matching, pass those strings via the `names` parameter instead.","Normalize stored keys to include the `@` suffix when persisting filters.","Run with the warning enabled in tests to catch malformed keys early."],"exampleFix":"// before\nVisibilityTransform(keys={\"my_tool\"})\n// after\nVisibilityTransform(keys={\"tool:my_tool@\"})  # or VisibilityTransform(names={\"my_tool\"})","handlingStrategy":"validation","validationCode":"def ensure_keys_valid(keys: set[str]) -> None:\n    bad = [k for k in keys if \"@\" not in k]\n    assert not bad, f\"keys missing '@' version delimiter: {bad}\"","typeGuard":"def is_well_formed_key(key: str) -> bool:\n    return \"@\" in key","tryCatchPattern":"import warnings\nwith warnings.catch_warnings(record=True) as caught:\n    warnings.simplefilter(\"always\")\n    t = VisibilityTransform(keys=keys)\nif any(\"version delimiter\" in str(w.message) for w in caught):\n    raise ValueError(\"malformed component keys in VisibilityTransform\")","preventionTips":["Always copy keys from `component.key`, never hand-write them","Use the `names` parameter for plain-name filtering","Validate persisted filter configs for the '@' delimiter"],"tags":["python","transforms","component-keys","filtering"],"backgroundTag":"malformed-component-key","analyzedSha":"1f021142978e0861cd910c8df4e8074bc7cf3978","analyzedAt":"2026-08-29T14:31:16.082Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}