rohitg00/ai-engineering-from-scratch · error · ValueError
canonical collision: {canonical_name}
Error message
canonical collision: {canonical_name} What it means
Under prefix-on-collision, a tool name that already exists in the registry is retried as '{peer.name}/{local_name}', and even that prefixed name is taken. This happens when two tools on the same peer (or an earlier prefix) collide, or one peer's name makes its prefixed tool equal another canonical entry, so no unique name can be produced.
Source
Thrown at phases/13-tools-and-protocols/08-building-an-mcp-client/code/main.py:520
if peer.available:
result = self._request(peer, "tools/list", {})
peer.tools = sorted(result.get("tools", []), key=lambda tool: tool["name"])
def merge(self, policy: str = "prefix-on-collision") -> None:
if policy not in {"prefix-on-collision", "reject"}:
raise ValueError("policy must be prefix-on-collision or reject")
self.registry.clear()
for peer_name in sorted(self.peers):
peer = self.peers[peer_name]
for tool in peer.tools:
local_name = tool["name"]
canonical_name = local_name
if canonical_name in self.registry:
if policy == "reject":
continue
canonical_name = f"{peer.name}/{local_name}"
if canonical_name in self.registry:
raise ValueError(f"canonical collision: {canonical_name}")
self.registry[canonical_name] = MergedTool(
canonical_name=canonical_name,
peer_name=peer.name,
local_name=local_name,
description=tool.get("description", ""),
)
self.registry = dict(sorted(self.registry.items()))
def call(self, canonical_name: str, arguments: dict[str, Any]) -> dict[str, Any]:
merged = self.registry.get(canonical_name)
if merged is None:
return {
"resultType": "complete",
"content": [{"type": "text", "text": f"Unknown tool: {canonical_name}"}],
"isError": True,
}
peer = self.peers[merged.peer_name]
if not peer.available:View on GitHub (pinned to 39ea8a1c6d)
Solutions
- Inspect which tool names collide by dumping peer.tools per peer
- Rename the duplicated tools on the server side
- Use policy='reject' to skip collisions instead of failing
- Change the colliding peer's name so prefixed names are unique
Example fix
// before
client.merge("prefix-on-collision") # raises on double collision
// after
client.merge("reject") # colliding tools are skipped, merge completes Defensive patterns
Strategy: fallback
Validate before calling
seen = {}
for name in sorted(p.name for p in client.peers.values()):
for tool in tools_of(name):
for candidate in (tool, f'{name}/{tool}'):
if candidate in seen:
rename_or_reject(candidate) Type guard
null
Try / catch
try:
client.merge()
except ValueError as e:
if "canonical collision" in str(e):
client.merge('reject') # skip colliding tools and proceed Prevention
- Enforce unique tool names per server
- Choose peer names that cannot collide under prefixing
- Default to 'reject' in environments with untrusted tool catalogs
When it happens
Trigger: policy == 'prefix-on-collision', canonical_name is already registered, f'{peer.name}/{local_name}' is also already in self.registry.
Common situations: Duplicate tool names within one peer's tools/list; a peer literally named such that peer.name/tool collides with an existing canonical name; peers exposing overlapping prefixed namespaces.
Related errors
AI-assisted analysis of rohitg00/ai-engineering-from-scratch@39ea8a1c6d (2026-08-26).
Data as JSON: /api/errors/bb61e882f0d94dec.
Report an issue: GitHub.