{"record":{"id":"6aff50bc7b4429d9","repo":"shareAI-lab/learn-claude-code","slug":"duplicate-mcp-tool-name-on-server-self-name-r","errorCode":null,"errorMessage":"Duplicate MCP tool name on server {self.name!r}","messagePattern":"Duplicate MCP tool name on server (.+?)","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"s14_mcp_plugin/code.py","lineNumber":173,"sourceCode":"}\n\n\n# -- New in s14: MCP discovery and dispatch --\n\nclass MCPClient:\n    \"\"\"Small in-process stand-in for MCP tools/list and tools/call.\"\"\"\n\n    def __init__(self, name: str):\n        self.name = name\n        self.tools: list[dict] = []\n        self._handlers: dict[str, callable] = {}\n\n    def register(self, tool_defs: list[dict], handlers: dict[str, callable]):\n        names = [tool.get(\"name\") for tool in tool_defs]\n        if any(not isinstance(name, str) or not name for name in names):\n            raise ValueError(\"Every MCP tool needs a non-empty name\")\n        if len(set(names)) != len(names):\n            raise ValueError(f\"Duplicate MCP tool name on server {self.name!r}\")\n        missing = [name for name in names if name not in handlers]\n        if missing:\n            raise ValueError(f\"Missing MCP handlers: {', '.join(missing)}\")\n        self.tools = list(tool_defs)\n        self._handlers = dict(handlers)\n\n    def call_tool(self, tool_name: str, args: dict) -> str:\n        handler = self._handlers.get(tool_name)\n        if not handler:\n            return f\"MCP error: unknown tool '{tool_name}'\"\n        try:\n            return str(handler(**args))\n        except Exception as exc:\n            return f\"MCP error: {type(exc).__name__}: {exc}\"\n\n\nmcp_clients: dict[str, MCPClient] = {}\nmcp_tool_policies: dict[str, str] = {}","sourceCodeStart":155,"sourceCodeEnd":191,"githubUrl":"https://github.com/shareAI-lab/learn-claude-code/blob/985456f4adea6f4df8fbad4112245dbd97444eae/s14_mcp_plugin/code.py#L155-L191","documentation":"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.","triggerScenarios":"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.","commonSituations":"Merging tool catalogs from multiple modules; copy-paste tool authoring; regenerating defs where an old entry wasn't removed.","solutions":["Deduplicate by name before register(): keep the entry per name you actually want.","Rename one of the colliding tools to reflect its distinct purpose.","Add a unit test asserting tool name uniqueness per server."],"exampleFix":"// before\nserver.register(docs_tools + docs_tools_v2, handlers)  // both define 'search'\n\n// after\nmerged = {t['name']: t for t in docs_tools + docs_tools_v2}\nserver.register(list(merged.values()), handlers)","handlingStrategy":"validation","validationCode":"def tool_names_unique(tool_defs: list[dict]) -> bool:\n    names = [t['name'] for t in tool_defs]\n    return len(set(names)) == len(names)","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Deduplicate merged tool lists by name before register().","Add a unit test asserting per-server name uniqueness.","When copy-pasting a tool def, rename it immediately."],"tags":["mcp","duplicate","tool-registration","validation"],"backgroundTag":null,"analyzedSha":"985456f4adea6f4df8fbad4112245dbd97444eae","analyzedAt":"2026-08-14T22:02:26.028Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}