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 a tool list containing the same name twice on one server. Handlers are keyed by name, so duplicates would silently bind all calls to whichever handler was inserted last; registration fails fast and names the offending server in the message.
Source
Thrown at s15_integrated_harness/code.py:2434
# -- MCP System --
# MCP is modeled as late-bound tools: connect first, then discovered server
# tools are merged into the normal tool pool with mcp__server__tool names.
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] = {}
_DISALLOWED_CHARS = re.compile(r"[^a-zA-Z0-9_-]")View on GitHub (pinned to 985456f4ad)
Solutions
- De-duplicate tool_defs by name before register() (keep one, or namespace the names).
- When merging servers, prefix each tool name with its source server to guarantee uniqueness.
- Fix the upstream server/gateway so tools/list itself has unique names.
Example fix
// before
tools = read_tools + read_tools # duplicate names
client.register(tools, handlers)
// after
seen, unique = set(), []
for t in tools:
if t["name"] not in seen:
seen.add(t["name"])
unique.append(t)
client.register(unique, handlers) Defensive patterns
Strategy: validation
Validate before calling
names = [t["name"] for t in tool_defs if isinstance(t.get("name"), str) and t["name"]]
assert len(names) == len(set(names)), f"duplicate tool names: {names}"
# or de-duplicate, keeping first occurrence:
seen, unique = set(), []
for t in tool_defs:
if t["name"] not in seen:
seen.add(t["name"])
unique.append(t) Type guard
def names_unique(tool_defs) -> bool:
names = [t.get("name") for t in tool_defs]
return len(set(names)) == len(names) Try / catch
try:
client.register(tool_defs, handlers)
except ValueError as e:
if "Duplicate MCP tool name" in str(e):
# namespace by server or drop dupes, then retry once
raise Prevention
- Namespace merged tool names by source server (server__tool).
- De-duplicate lists before register().
- Check aggregating gateways for plugins advertising the same tool name.
When it happens
Trigger: Calling register() with tool_defs where two entries share a 'name' — e.g. concatenating two tool lists with overlapping names, a copy-paste duplication while hand-building a test server, or a source server that genuinely advertises a duplicate (misconfigured gateway aggregating two plugins with the same tool name).
Common situations: Test doubles assembled by extending a list; merging tools from multiple upstream MCP servers into one client without namespacing; gateway config duplicating a plugin.
Related errors
- Every MCP tool needs a non-empty name
- Duplicate MCP tool name on server {self.name!r}
- Missing MCP handlers: {', '.join(missing)}
- MCP tool name is longer than 64 characters: {prefixed}
- Invalid input schema for {origin}
AI-assisted analysis of shareAI-lab/learn-claude-code@985456f4ad (2026-08-14).
Data as JSON: /api/errors/23ffbe244cb0ac4d.
Report an issue: GitHub.