shareAI-lab/learn-claude-code · error · ValueError

MCP tool name collision after normalization: {prefixed!r} ma

Error message

MCP tool name collision after normalization: {prefixed!r} maps both {origins[prefixed]} and {origin}

What it means

Normalization (disallowed chars → '_') plus the mcp__server__tool prefix can make two distinct tools collide: e.g. 'my-tool' and 'my_tool' on the same server both become mcp__srv__my_tool, or a server/tool pair that prefixes to a built-in tool name. The catalog build detects the collision against the origins map (which pre-seeds built-in tools) and raises with both conflicting origins.

Source

Thrown at s14_mcp_plugin/code.py:334

    tools = list(BUILTIN_TOOLS)
    handlers = dict(BUILTIN_HANDLERS)
    policies: dict[str, str] = {}
    origins = {
        tool["name"]: f"built-in tool {tool['name']!r}"
        for tool in tools
    }

    for server_name, server in mcp_clients.items():
        safe_server = normalize_mcp_name(server_name)
        for tool_def in server.tools:
            raw_name = tool_def["name"]
            safe_tool = normalize_mcp_name(raw_name)
            prefixed = f"mcp__{safe_server}__{safe_tool}"
            if len(prefixed) > 64:
                raise ValueError(f"MCP tool name is longer than 64 characters: {prefixed}")
            origin = f"MCP tool {server_name!r}/{raw_name!r}"
            if prefixed in origins:
                raise ValueError(
                    "MCP tool name collision after normalization: "
                    f"{prefixed!r} maps both {origins[prefixed]} and {origin}"
                )
            schema = tool_def.get("inputSchema", {})
            if not isinstance(schema, dict) or schema.get("type", "object") != "object":
                raise ValueError(f"Invalid input schema for {origin}")
            origins[prefixed] = origin
            tools.append({
                "name": prefixed,
                "description": tool_def.get("description", ""),
                "input_schema": schema,
            })
            handlers[prefixed] = (
                lambda *, client=server, tool=raw_name, **kwargs:
                client.call_tool(tool, kwargs)
            )
            policies[prefixed] = MCP_HOST_POLICY.get(
                (server_name, raw_name), "confirm"

View on GitHub (pinned to 985456f4ad)

Solutions

  1. Rename one of the colliding servers or tools so their normalized forms differ (change letters, not just punctuation).
  2. Check the error message: it names both origins — rename the less important one.
  3. If mirroring built-ins, drop or prefix the MCP variant (e.g. tool name 'ext_search').

Example fix

// before
servers = {'my.server': srv_a, 'my_server': srv_b}  // both -> mcp__my_server__*

// after
servers = {'myserver_docs': srv_a, 'myserver_ops': srv_b}
Defensive patterns

Strategy: validation

Validate before calling

def catalog_names_unique(existing: set[str], server: str, tools: list[str]) -> list[str]:
    import re
    norm = lambda s: re.sub(r'[^A-Za-z0-9_-]', '_', s)
    clashes = []
    for t in tools:
        p = f'mcp__{norm(server)}__{norm(t)}'
        if p in existing:
            clashes.append(p)
        existing.add(p)
    return clashes  # empty means safe to register

Try / catch

try:
    build_catalog(tools, mcp_clients)
except ValueError as exc:
    if 'collision after normalization' in str(exc):
        # error names both origins; rename the less critical one and rebuild
        logging.error('MCP name collision: %s', exc)
        raise SystemExit(2) from exc
    raise

Prevention

When it happens

Trigger: Server 'my.server' with tool 'search' vs server 'my_server' with tool 'search' → both 'mcp__my_server__search'; tools 'a-b' and 'a_b' on one server; an MCP tool that normalizes to a name already used by a built-in tool.

Common situations: Registering multiple MCP servers whose names differ only in punctuation; tools whose raw names differ only by case/punctuation; mirroring a tool that collides with built-ins.

Related errors


AI-assisted analysis of shareAI-lab/learn-claude-code@985456f4ad (2026-08-14). Data as JSON: /api/errors/a0523f511bb217c2. Report an issue: GitHub.