PrefectHQ/fastmcp · error · ValueError

Discovery tool name '{self.execute_tool_name}' collides with

Error message

Discovery tool name '{self.execute_tool_name}' collides with execute_tool_name.

What it means

When CodeMode builds its discovery tools it checks that none of them shares the reserved execute_tool_name used for the execute tool. A duplicate means two tools with the same name would be exposed, so a ValueError is raised at transform build time.

Source

Thrown at fastmcp_slim/fastmcp/experimental/transforms/code_mode.py:540

        self.max_tool_calls = max_tool_calls
        self.sandbox_provider = sandbox_provider or MontySandboxProvider()

        self._discovery_factories = (
            discovery_tools
            if discovery_tools is not None
            else _default_discovery_tools()
        )
        self._built_discovery_tools: list[Tool] | None = None
        self._cached_execute_tool: Tool | None = None

    def _build_discovery_tools(self) -> list[Tool]:
        if self._built_discovery_tools is None:
            tools = [
                factory(self.get_tool_catalog) for factory in self._discovery_factories
            ]
            names = {t.name for t in tools}
            if self.execute_tool_name in names:
                raise ValueError(
                    f"Discovery tool name '{self.execute_tool_name}' "
                    f"collides with execute_tool_name."
                )
            if len(names) != len(tools):
                raise ValueError("Discovery tools must have unique names.")
            self._built_discovery_tools = tools
        return self._built_discovery_tools

    async def transform_tools(self, tools: Sequence[Tool]) -> Sequence[Tool]:
        return [*self._build_discovery_tools(), self._get_execute_tool()]

    async def get_tool(
        self,
        name: str,
        call_next: GetToolNext,
        *,
        version: VersionSpec | None = None,
    ) -> Tool | None:

View on GitHub (pinned to 1f02114297)

Solutions

  1. Rename the discovery tool returned by your custom factory so it differs from execute_tool_name
  2. Change CodeModeTransform's execute_tool_name to a non-colliding value
  3. Review your discovery factories' naming scheme to avoid the reserved execute name

Example fix

// before
CodeModeTransform(execute_tool_name="catalog")  # factory also emits "catalog"

// after
CodeModeTransform(execute_tool_name="execute_tool")  # no collision
Defensive patterns

Strategy: validation

Validate before calling

names = [t.name for f in discovery_factories for t in [f(get_tool_catalog)]]
if execute_tool_name in names:
    raise ValueError(f"Discovery tool '{execute_tool_name}' collides with execute_tool_name")

Type guard

def discovery_names_valid(names: list[str], execute_tool_name: str) -> bool:
    return execute_tool_name not in names

Try / catch

try:
    tools = await transform.transform_tools(existing_tools)
except ValueError as e:
    if "collides with execute_tool_name" in str(e):
        # rename your discovery tool or change execute_tool_name
        ...
    raise

Prevention

When it happens

Trigger: Constructing CodeModeTransform with discovery tool factories (or an execute_tool_name) such that a discovery tool's generated name equals execute_tool_name; _build_discovery_tools is then invoked via transform_tools() or get_tool().

Common situations: Custom discovery factory returning a tool named 'execute' (the default execute name); explicitly setting execute_tool_name to a name a discovery factory already uses.

Related errors


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