{"record":{"id":"1e772016602096d3","repo":"PrefectHQ/fastmcp","slug":"discovery-tools-must-have-unique-names","errorCode":null,"errorMessage":"Discovery tools must have unique names.","messagePattern":"Discovery tools must have unique names\\.","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"fastmcp_slim/fastmcp/experimental/transforms/code_mode.py","lineNumber":545,"sourceCode":"            if discovery_tools is not None\n            else _default_discovery_tools()\n        )\n        self._built_discovery_tools: list[Tool] | None = None\n        self._cached_execute_tool: Tool | None = None\n\n    def _build_discovery_tools(self) -> list[Tool]:\n        if self._built_discovery_tools is None:\n            tools = [\n                factory(self.get_tool_catalog) for factory in self._discovery_factories\n            ]\n            names = {t.name for t in tools}\n            if self.execute_tool_name in names:\n                raise ValueError(\n                    f\"Discovery tool name '{self.execute_tool_name}' \"\n                    f\"collides with execute_tool_name.\"\n                )\n            if len(names) != len(tools):\n                raise ValueError(\"Discovery tools must have unique names.\")\n            self._built_discovery_tools = tools\n        return self._built_discovery_tools\n\n    async def transform_tools(self, tools: Sequence[Tool]) -> Sequence[Tool]:\n        return [*self._build_discovery_tools(), self._get_execute_tool()]\n\n    async def get_tool(\n        self,\n        name: str,\n        call_next: GetToolNext,\n        *,\n        version: VersionSpec | None = None,\n    ) -> Tool | None:\n        for tool in self._build_discovery_tools():\n            if tool.name == name:\n                return tool\n        if name == self.execute_tool_name:\n            return self._get_execute_tool()","sourceCodeStart":527,"sourceCodeEnd":563,"githubUrl":"https://github.com/PrefectHQ/fastmcp/blob/1f021142978e0861cd910c8df4e8074bc7cf3978/fastmcp_slim/fastmcp/experimental/transforms/code_mode.py#L527-L563","documentation":"CodeMode's discovery tools must each have a unique name; when the set of built discovery tools is smaller than the list, duplicates exist and a ValueError is raised before the transform is usable.","triggerScenarios":"Providing multiple discovery tool factories whose generated tools produce the same name — _build_discovery_tools() (via transform_tools() or get_tool()) detects len(names) != len(tools) and raises.","commonSituations":"Reusing the same factory (or two factories that emit identical names) in the discovery_factories list; programmatic name generation that collides for different catalogs.","solutions":["Ensure each discovery factory emits a distinct tool name; add a distinguishing prefix/suffix per factory","Remove duplicate factories from the discovery list","Add an assertion or unit test on generated names before constructing the transform"],"exampleFix":"// before\nfactories = [make_catalog_tool, make_catalog_tool]  # same name twice\n\n// after\nfactories = [make_catalog_tool, make_usage_tool]  # unique names","handlingStrategy":"validation","validationCode":"names = [t.name for f in discovery_factories for t in [f(get_tool_catalog)]]\nif len(set(names)) != len(names):\n    raise ValueError(f\"Duplicate discovery tool names: {sorted(n for n in names if names.count(n) > 1)}\")","typeGuard":"def discovery_names_unique(names: list[str]) -> bool:\n    return len(set(names)) == len(names)","tryCatchPattern":"try:\n    tools = await transform.transform_tools(existing_tools)\nexcept ValueError as e:\n    if \"unique names\" in str(e):\n        # dedupe/rename your discovery factories' outputs\n        ...\n    raise","preventionTips":["Give each discovery factory a unique naming scheme/prefix","Deduplicate the factory list before constructing CodeModeTransform","Test-transform a sample tool list in CI to surface collisions early"],"tags":["code-mode","naming-collision","value-error","fastmcp"],"backgroundTag":"tool-name-collision","analyzedSha":"1f021142978e0861cd910c8df4e8074bc7cf3978","analyzedAt":"2026-08-29T14:31:16.082Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}