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

Duplicate MCP tool name on server {self.name!r}

Error message

Duplicate MCP tool name on server {self.name!r}

What it means

MCPClient.register() rejects tool_defs containing duplicate names, since the handlers dict is keyed by name and a duplicate would silently shadow one tool's handler. Uniqueness is checked per server before any tool is exposed.

Source

Thrown at s14_mcp_plugin/code.py:173

}


# -- New in s14: MCP discovery and dispatch --

class MCPClient:
    """Small in-process stand-in for MCP tools/list and tools/call."""

    def __init__(self, name: str):
        self.name = name
        self.tools: list[dict] = []
        self._handlers: dict[str, callable] = {}

    def register(self, tool_defs: list[dict], handlers: dict[str, callable]):
        names = [tool.get("name") for tool in tool_defs]
        if any(not isinstance(name, str) or not name for name in names):
            raise ValueError("Every MCP tool needs a non-empty name")
        if len(set(names)) != len(names):
            raise ValueError(f"Duplicate MCP tool name on server {self.name!r}")
        missing = [name for name in names if name not in handlers]
        if missing:
            raise ValueError(f"Missing MCP handlers: {', '.join(missing)}")
        self.tools = list(tool_defs)
        self._handlers = dict(handlers)

    def call_tool(self, tool_name: str, args: dict) -> str:
        handler = self._handlers.get(tool_name)
        if not handler:
            return f"MCP error: unknown tool '{tool_name}'"
        try:
            return str(handler(**args))
        except Exception as exc:
            return f"MCP error: {type(exc).__name__}: {exc}"


mcp_clients: dict[str, MCPClient] = {}
mcp_tool_policies: dict[str, str] = {}

View on GitHub (pinned to 985456f4ad)

Solutions

  1. Deduplicate by name before register(): keep the entry per name you actually want.
  2. Rename one of the colliding tools to reflect its distinct purpose.
  3. Add a unit test asserting tool name uniqueness per server.

Example fix

// before
server.register(docs_tools + docs_tools_v2, handlers)  // both define 'search'

// after
merged = {t['name']: t for t in docs_tools + docs_tools_v2}
server.register(list(merged.values()), handlers)
Defensive patterns

Strategy: validation

Validate before calling

def tool_names_unique(tool_defs: list[dict]) -> bool:
    names = [t['name'] for t in tool_defs]
    return len(set(names)) == len(names)

Prevention

When it happens

Trigger: register([{'name': 'search', ...}, {'name': 'search', ...}], handlers) — e.g. concatenating two tool lists that both define 'search', or copy-pasting a def and editing only its description.

Common situations: Merging tool catalogs from multiple modules; copy-paste tool authoring; regenerating defs where an old entry wasn't removed.

Related errors


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