Background
ToolUniverse.load_tools() (src/mcphub/__init__.py) registers a bare
(unqualified) alias for each discovered tool name, so callers can do
tu.run("status") instead of tu.run("iot.status"). When a second server
exposes the same bare name, the collision is detected, a warning is logged,
and the alias is removed (self._alias.pop(t.name, None)) so the ambiguous
name stops resolving.
Removing the alias also erases all memory that the name was ever ambiguous.
If a third (or any later) server also happens to expose that same bare
name, the registration check sees the name is absent from self._alias again
and silently re-adds it, pointing at whichever server loaded last. No warning
fires on this second-and-later collision, only on the very first one. A
caller invoking the bare name afterward gets routed to one of 3+ legitimate
servers with no indication anything is ambiguous.
With the 6 MCP servers currently shipped (iot, utilities, fmsr, wo, tsfm,
vibration) I did not find a live 3-way name collision, so today this is a
latent defect rather than an observed production failure, but it would bite
silently the moment a third server (including a user-added one) reuses a
bare name already used by two others.
Steps to Reproduce
Ran against a real ToolUniverse instance and the real load_tools() code
path; only _connect and _worker.list are stubbed out so the repro doesn't
need three live stdio MCP server processes.
import logging, sys
sys.path.insert(0, "src")
from mcphub import ToolUniverse
logging.basicConfig(level=logging.WARNING)
class FakeTool:
def __init__(self, name):
self.name = name
self.description = ""
self.inputSchema = {}
class FakeListResult:
def __init__(self, tools):
self.tools = tools
FAKE_SERVERS = {
"server1": [FakeTool("status")],
"server2": [FakeTool("status")],
"server3": [FakeTool("status")],
}
tu = ToolUniverse(servers={k: [] for k in FAKE_SERVERS})
tu._connect = lambda name: None # skip real stdio connect
tu._worker.list = lambda name: FakeListResult(FAKE_SERVERS[name])
count = tu.load_tools(servers=list(FAKE_SERVERS))
print("tools registered:", count)
print("alias table:", tu._alias)
tu.close()
Run with:
python -m pip install mcp
python repro.py
Observed
WARNING:mcphub:Ambiguous tool name 'status'; use 'server2.status'.
tools registered: 3
alias table: {'status': 'server3.status'}
Only one warning is logged (for the 2nd collision, server2 vs server1). The
3rd collision (server3) silently re-registers the bare alias with no warning
at all, and status now resolves to server3.status as if it had never been
ambiguous.
Expected
Either no bare alias for status after any collision, or a warning on every
collision (2nd, 3rd, ...), not just the first.
Environment
- Repo HEAD:
248204c4cce9f9e3c3f3234e466f6694a080f6f7
- Python 3.12.14,
python:3.12-slim Docker image
mcp client package (latest from PyPI at repro time)
Reproduced end to end in a clean container against current main. Happy to
send a PR if useful; the fix looks like replacing the pop-then-forget pattern
with a separate set of names known to be ambiguous that's never cleared, and
warning on every collision.
Background
ToolUniverse.load_tools()(src/mcphub/__init__.py) registers a bare(unqualified) alias for each discovered tool name, so callers can do
tu.run("status")instead oftu.run("iot.status"). When a second serverexposes the same bare name, the collision is detected, a warning is logged,
and the alias is removed (
self._alias.pop(t.name, None)) so the ambiguousname stops resolving.
Removing the alias also erases all memory that the name was ever ambiguous.
If a third (or any later) server also happens to expose that same bare
name, the registration check sees the name is absent from
self._aliasagainand silently re-adds it, pointing at whichever server loaded last. No warning
fires on this second-and-later collision, only on the very first one. A
caller invoking the bare name afterward gets routed to one of 3+ legitimate
servers with no indication anything is ambiguous.
With the 6 MCP servers currently shipped (iot, utilities, fmsr, wo, tsfm,
vibration) I did not find a live 3-way name collision, so today this is a
latent defect rather than an observed production failure, but it would bite
silently the moment a third server (including a user-added one) reuses a
bare name already used by two others.
Steps to Reproduce
Ran against a real
ToolUniverseinstance and the realload_tools()codepath; only
_connectand_worker.listare stubbed out so the repro doesn'tneed three live stdio MCP server processes.
Run with:
Observed
Only one warning is logged (for the 2nd collision, server2 vs server1). The
3rd collision (server3) silently re-registers the bare alias with no warning
at all, and
statusnow resolves toserver3.statusas if it had never beenambiguous.
Expected
Either no bare alias for
statusafter any collision, or a warning on everycollision (2nd, 3rd, ...), not just the first.
Environment
248204c4cce9f9e3c3f3234e466f6694a080f6f7python:3.12-slimDocker imagemcpclient package (latest from PyPI at repro time)Reproduced end to end in a clean container against current
main. Happy tosend a PR if useful; the fix looks like replacing the pop-then-forget pattern
with a separate set of names known to be ambiguous that's never cleared, and
warning on every collision.