What version of Kimi Code is running?
1.47.0
Which open platform/subscription were you using?
kimi for coding
Which model were you using?
kimi-k2.6
What platform is your computer?
Darwin 25.5.0 arm64 arm
What issue are you seeing?
Summary
When multiple MCP servers expose tools with the same name (e.g., read_node, search_nodes), Kimi CLI registers them into a single flat dictionary keyed only by tool.name. This causes later-loaded servers to silently overwrite earlier ones. The user has no way to know which server’s tool is actually being invoked, leading to unpredictable behavior and access-denied errors.
Environment
- kimi-cli version: 1.47.0
- Python version: 3.13
- OS: macOS
- Open platform/subscription: kimi for coding
- Model: kimi-for-coding
- MCP servers tested:
zeeta_masa, zeeta_tp (both HTTP, same endpoint URL with different tokens)
Steps to Reproduce
-
Configure two MCP servers that expose tools with identical names:
{
"mcpServers": {
"zeeta_masa": {
"type": "http",
"url": "https://zeetaweb.jp/api/mcp?token=<token_a>"
},
"zeeta_tp": {
"type": "http",
"url": "https://zeetaweb.jp/api/mcp?token=<token_b>"
}
}
}
Both servers expose tools named: read_node, search_nodes, create_node, etc.
-
Start a session and verify both servers appear as "connected":
kimi mcp test zeeta_masa
kimi mcp test zeeta_tp
Both show ✓ Connected with 15 tools each.
-
Attempt to read a node that exists on zeeta_masa:
read_node(node_id="8524")
→ Success (returns node data)
-
Attempt to read a node that exists on zeeta_tp (and you have access to via Web UI):
read_node(node_id="8522")
→ Access denied to node
Expected Behavior
Both servers’ tools should be usable simultaneously. Either:
- Tools should be namespaced:
zeeta_masa__read_node, zeeta_tp__read_node
- OR the runtime should route tool calls to the correct server internally.
Actual Behavior
Only one server’s tools are effective. Which server "wins" is non-deterministic because _connect_server runs in parallel via asyncio.gather, and the last call to self.add(tool) overwrites the previous one.
Root Cause Analysis
In kimi_cli/soul/toolset.py, tools are stored in a flat dictionary keyed only by name:
# Line ~198-199
class KimiToolset:
def add(self, tool: ToolType) -> None:
self._tool_dict[tool.name] = tool # <-- key is ONLY tool.name
During MCP loading (load_mcp_tools, line ~629), each server is connected in parallel:
# Line ~270-273 in _connect()
tasks = [
asyncio.create_task(_connect_server(server_name, server_info))
for server_name, server_info in self._mcp_servers.items()
if server_info.status == "pending"
]
results = await asyncio.gather(*tasks) if tasks else []
Inside _connect_server:
# Line ~255-257
for tool in server_info.tools:
self.add(tool) # <-- silently overwrites any previous tool with same name
Because multiple zeeta_* servers all expose read_node, whichever server finishes last determines which read_node is actually callable. The user cannot target a specific server.
Impact
| Severity |
Description |
| Functional |
Impossible to use multiple MCP servers with overlapping tool names |
| Security |
Risk of invoking the wrong server’s tool (e.g., write_file targeting unintended filesystem) |
| Debugging |
Users cannot determine which server’s tool was actually executed |
| UX |
kimi mcp test shows all servers as "connected", creating false confidence |
This is not specific to Zeeta — any two MCP servers exposing common names like read_file, query, search, save_data will collide.
Suggested Fix
Option A: Prefix tool names with server name (recommended)
Change tool registration so that MCP tools are keyed as {server_name}__{tool_name}:
# In _connect_server or add()
prefixed_name = f"{server_name}__{tool.name}"
self._tool_dict[prefixed_name] = tool
The LLM prompt should include both the prefixed name and the original server context.
Option B: Use composite key internally
Keep the LLM-facing name as read_node but internally route using (server_name, tool_name). Requires the LLM to also specify which server to target, or for the runtime to intelligently route.
Option C: Detect collisions and warn/fail
At minimum, if tool.name already exists in _tool_dict, log a clear warning or raise an error instead of silently overwriting:
def add(self, tool: ToolType) -> None:
if tool.name in self._tool_dict:
logger.warning(f"Tool '{tool.name}' from server X overwrites tool from server Y")
self._tool_dict[tool.name] = tool
Additional Context
- The
zeeta_* servers use the same endpoint URL (https://zeetaweb.jp/api/mcp) but different token query parameters to scope to different workspaces. Even if Kimi CLI currently deduplicates by URL, the tool-level collision is the deeper issue.
- I observed a CI branch named
mcp-tool-name-collision (author: salmandeniz), suggesting the team may already be aware of this. However, no open issue currently tracks it.
- This problem is fundamental to MCP adoption at scale — as more servers are published, name collisions will become inevitable.
What steps can reproduce the bug?
- Configure two MCP servers that expose tools with identical names (e.g.
, zeeta_masa and zeeta_tp)
- Start a session and verify both servers appear as "connected" via
kim i mcp test
- Call read_node(node_id="8524") → success (node exists on zeeta_masa)
- Call read_node(node_id="8522") → Access denied (node exists on zeeta_t
p but the wrong server's tool is invoked)
What is the expected behavior?
No response
Additional information
No response
What version of Kimi Code is running?
1.47.0
Which open platform/subscription were you using?
kimi for coding
Which model were you using?
kimi-k2.6
What platform is your computer?
Darwin 25.5.0 arm64 arm
What issue are you seeing?
Summary
When multiple MCP servers expose tools with the same name (e.g.,
read_node,search_nodes), Kimi CLI registers them into a single flat dictionary keyed only bytool.name. This causes later-loaded servers to silently overwrite earlier ones. The user has no way to know which server’s tool is actually being invoked, leading to unpredictable behavior and access-denied errors.Environment
zeeta_masa,zeeta_tp(both HTTP, same endpoint URL with different tokens)Steps to Reproduce
Configure two MCP servers that expose tools with identical names:
{ "mcpServers": { "zeeta_masa": { "type": "http", "url": "https://zeetaweb.jp/api/mcp?token=<token_a>" }, "zeeta_tp": { "type": "http", "url": "https://zeetaweb.jp/api/mcp?token=<token_b>" } } }Both servers expose tools named:
read_node,search_nodes,create_node, etc.Start a session and verify both servers appear as "connected":
Both show ✓ Connected with 15 tools each.
Attempt to read a node that exists on zeeta_masa:
→ Success (returns node data)
Attempt to read a node that exists on zeeta_tp (and you have access to via Web UI):
→ Access denied to node
Expected Behavior
Both servers’ tools should be usable simultaneously. Either:
zeeta_masa__read_node,zeeta_tp__read_nodeActual Behavior
Only one server’s tools are effective. Which server "wins" is non-deterministic because
_connect_serverruns in parallel viaasyncio.gather, and the last call toself.add(tool)overwrites the previous one.Root Cause Analysis
In
kimi_cli/soul/toolset.py, tools are stored in a flat dictionary keyed only by name:During MCP loading (
load_mcp_tools, line ~629), each server is connected in parallel:Inside
_connect_server:Because multiple
zeeta_*servers all exposeread_node, whichever server finishes last determines whichread_nodeis actually callable. The user cannot target a specific server.Impact
write_filetargeting unintended filesystem)kimi mcp testshows all servers as "connected", creating false confidenceThis is not specific to Zeeta — any two MCP servers exposing common names like
read_file,query,search,save_datawill collide.Suggested Fix
Option A: Prefix tool names with server name (recommended)
Change tool registration so that MCP tools are keyed as
{server_name}__{tool_name}:The LLM prompt should include both the prefixed name and the original server context.
Option B: Use composite key internally
Keep the LLM-facing name as
read_nodebut internally route using(server_name, tool_name). Requires the LLM to also specify which server to target, or for the runtime to intelligently route.Option C: Detect collisions and warn/fail
At minimum, if
tool.namealready exists in_tool_dict, log a clear warning or raise an error instead of silently overwriting:Additional Context
zeeta_*servers use the same endpoint URL (https://zeetaweb.jp/api/mcp) but differenttokenquery parameters to scope to different workspaces. Even if Kimi CLI currently deduplicates by URL, the tool-level collision is the deeper issue.mcp-tool-name-collision(author:salmandeniz), suggesting the team may already be aware of this. However, no open issue currently tracks it.What steps can reproduce the bug?
, zeeta_masa and zeeta_tp)
kim i mcp testp but the wrong server's tool is invoked)
What is the expected behavior?
No response
Additional information
No response