PrefectHQ/fastmcp · error · ValueError

Tool name collision: {public_name!r}

Error message

Tool name collision: {public_name!r}

What it means

ClientGroup.namespaces every tool as `{server_name}_{tool.name}` and raises this ValueError during list_tools() when two servers produce the same public name — i.e. the same server name appears twice in the mapping, or (given the server_name prefix) one server advertises a tool whose namespaced form collides with another entry in the same catalog pass. The error fires before any routes are committed, so the catalog is not half-updated.

Source

Thrown at fastmcp_slim/fastmcp/client/group.py:177

        (SEP-2549) is repopulated rather than served, and the routes reflect
        what every server advertises now. Pass `cache_mode="use"` to allow
        cache hits when staleness within the server's hint is acceptable.
        """
        self._require_connected()
        tools: list[mcp_types.Tool] = []
        routes: dict[str, ToolRoute] = {}
        clients = list(self._clients.items())
        tool_lists = await gather(
            client.list_tools(cache_mode=cache_mode) for _, client in clients
        )

        for (server_name, client), server_tools in zip(
            clients, tool_lists, strict=True
        ):
            for tool in server_tools:
                public_name = f"{server_name}_{tool.name}"
                if public_name in routes:
                    raise ValueError(f"Tool name collision: {public_name!r}")
                routes[public_name] = ToolRoute(
                    server_name=server_name,
                    client=client,
                    upstream_name=tool.name,
                )
                tools.append(tool.model_copy(update={"name": public_name}))

        self._tool_routes = routes
        self._catalog_loaded = True
        return tools

    async def resolve_tool(self, name: str) -> ToolRoute:
        """Resolve a public tool name to its client and upstream identity.

        A known route only requires its own client to be connected; one dead
        server does not couple failures onto calls routed to healthy servers.
        Loading the catalog (the first resolution, or after a refresh) still
        requires every client, since discovery queries them all.

View on GitHub (pinned to 1f02114297)

Solutions

  1. Rename one of the colliding server entries in your MCPConfig/dict so each server has a unique name prefix.
  2. If the dict was built programmatically, dedupe/validate keys before constructing ClientGroup: `assert len(clients) == len(set(k.lower() for k in clients))`.
  3. Inspect the message's public_name to identify the offending prefix, then adjust that server's name in the config file under `mcpServers`.
  4. Catch ValueError around list_tools() and surface a config-level diagnostic to the user rather than a raw traceback.

Example fix

// before (mcpServers)
{ "weather": {"url": "https://a.example/mcp"}, "Weather": {"url": "https://b.example/mcp"} }

// after
{ "weather-primary": {"url": "https://a.example/mcp"}, "weather-fallback": {"url": "https://b.example/mcp"} }
Defensive patterns

Strategy: validation

Validate before calling

def validate_unique_prefixes(config: dict) -> None:
    names = list(config["mcpServers"].keys())
    normalized = [n.strip().lower() for n in names]
    dupes = {n for n in normalized if normalized.count(n) > 1}
    if dupes:
        raise ValueError(f"Duplicate server names would collide tool names: {dupes}")

Try / catch

try:
    tools = await group.list_tools()
except ValueError as e:
    if "Tool name collision" in str(e):
        raise ConfigError(f"Fix server names in your MCP config: {e}") from e
    raise

Prevention

When it happens

Trigger: Calling group.list_tools() when the clients mapping passed to ClientGroup contains duplicate server names mapped to distinct clients (dict construction would dedupe identical keys, but name collisions can arise from constructing the mapping programmatically or from `from_config` with a config whose mcpServers keys normalize to the same public name), such that two `(server_name, tool)` pairs yield the same `f"{server_name}_{tool.name}"`.

Common situations: MCP config files where two server entries differ only by case or whitespace but resolve to the same prefix; programmatic client dicts built by merging configs that overwrite or duplicate keys; a server renamed so its prefix now swallows another's namespaced tool name; aggregator setups generated from templates that emit repeated server names.

Related errors


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