How the MCP Client Handles Tool Name Collisions When Multiple Servers Expose the Same Tool
The MCP client prevents tool name collisions by namespacing each tool with a unique server identifier using the composite key format server-id::tool-name, while requiring explicit server_id parameters during invocation to disambiguate between duplicate implementations.
When integrating multiple Model Context Protocol (MCP) servers into the Dify plugin ecosystem, tool name collisions are inevitable. The junjiem/dify-plugin-agent-mcp_sse repository implements a robust collision resolution strategy in its MCP client that ensures tools with identical names from different servers coexist without conflict.
Understanding the Tool Registry Architecture
The collision handling logic centers on the ToolRegistry struct defined in internal/mcp/tool_registry.go. This registry maintains an internal map that stores tool definitions using composite keys rather than simple tool names, ensuring that no server can overwrite another's tools.
Composite Key Strategy
Each tool is registered under a composite key formatted as <server-id>::<tool-name>. The server-id is a unique identifier generated when the client establishes a connection to an MCP server, typically derived from the server's URL or connection metadata. This approach guarantees that search from server-a and search from server-b occupy distinct entries in the registry map, preventing accidental overwrites.
Collision Resolution Strategies
The MCP client implements a multi-layered approach to handle collisions across storage, presentation, and execution contexts.
Internal Storage with Namespacing
In internal/mcp/tool_registry.go, the Register method iterates through tools advertised by a server and prefixes each with the server's unique ID before insertion. This prevents any possibility of one server's tools overwriting another's, even when names are identical. The internal storage maintains separate entries for each namespaced tool, preserving the full fidelity of each server's implementation.
Deduplication for UI Discovery
When building the public tool list for UI presentation via PublicToolList(), the registry groups tools by their base name. For each collision group, it creates a merged descriptor containing:
- The original tool name
- A list of source server IDs providing this tool
- The union of all supported parameters and metadata
This allows the Dify interface to display a single "search" entry while maintaining awareness of multiple implementations, simplifying the user experience while preserving backend granularity.
Runtime Dispatch and Error Handling
The InvokeTool method in internal/mcp/mcp_client.go requires explicit server identification when ambiguities exist. When a tool name exists on multiple servers and the invocation request lacks a server_id parameter, the client returns a descriptive error:
Tool 'search' is provided by multiple servers (server-a, server-b).
Specify the target server using the 'server_id' field.
If the request includes a valid server_id, the client routes the call to the specific server's implementation using the composite key lookup, ensuring deterministic execution.
Implementation Details in the MCP Client
The collision resolution logic spans three primary files in the internal/mcp package:
| File | Purpose |
|---|---|
internal/mcp/tool_registry.go |
Defines ToolRegistry, composite key storage, and PublicToolList() deduplication |
internal/mcp/mcp_client.go |
Implements RegisterServerTools, InvokeTool, and conflict error handling |
internal/mcp/types.go |
Defines Tool, ToolInvocation, and ServerID types |
Registering Tools from Multiple Servers
When the client connects to multiple MCP servers, each connection receives a unique ServerID. The registration process automatically handles collisions:
// Example: Registering tools from two servers
srvA := client.Connect("wss://srv-a.example.com/mcp")
srvB := client.Connect("wss://srv-b.example.com/mcp")
// Both servers expose a tool named "search"
toolsA := []Tool{{Name: "search", Params: []string{"query"}}}
toolsB := []Tool{{Name: "search", Params: []string{"query", "lang"}}}
// Registration automatically namespaces with server ID
client.ToolRegistry.Register(srvA.ID, toolsA)
client.ToolRegistry.Register(srvB.ID, toolsB)
// Internal storage:
// "srvA::search" → Tool{...}
// "srvB::search" → Tool{...}
Invoking a Specific Tool Implementation
To avoid ambiguity, invocations must specify the target server:
// Explicit server selection prevents collisions
inv := ToolInvocation{
ServerID: "srvB", // Required when duplicates exist
Name: "search",
Args: map[string]any{
"query": "golang concurrency",
"lang": "en",
},
}
result, err := client.InvokeTool(context.Background(), inv)
Handling Ambiguous Invocations
When the server ID is omitted for a duplicate tool name, the client returns an error:
// This will fail if "search" exists on multiple servers
inv := ToolInvocation{
Name: "search", // Missing ServerID
Args: map[string]any{"query": "test"},
}
_, err := client.InvokeTool(context.Background(), inv)
// Error: "Tool 'search' is provided by multiple servers (srvA, srvB).
// Specify the target server using the 'server_id' field."
Summary
- The MCP client uses composite keys (
server-id::tool-name) to store tools internally, preventing any possibility of name collisions overwriting registry entries. - The ToolRegistry in
internal/mcp/tool_registry.gohandles namespacing during registration and deduplication when building public tool lists for UI consumption. - For runtime invocation, the client requires explicit
server_idparameters when duplicate tool names exist, returning descriptive errors if the server is ambiguous. - The UI layer receives merged tool descriptors that group duplicate names while preserving metadata about which servers provide each implementation.
Frequently Asked Questions
What happens if I don't specify a server_id when invoking a tool that exists on multiple servers?
The client returns an explicit error message listing all servers that provide the requested tool name, prompting you to include the server_id field in your invocation request to disambiguate the target. This prevents non-deterministic behavior by refusing to guess which implementation to use.
How does the UI display tools when multiple servers expose the same name?
The PublicToolList() method generates merged descriptors that present a single entry per tool name while including metadata about all source servers. This allows the interface to show one "search" tool with indicators of multiple implementations, simplifying the user experience while maintaining backend granularity.
Can I force a specific server implementation without modifying the tool name?
Yes, by providing the server_id parameter in your ToolInvocation request, you directly target the specific server's implementation without needing to rename or alias the tool itself. The client uses the composite key lookup to route the call to the exact server implementation you specified.
Where is the collision detection logic implemented in the source code?
The core collision handling resides in internal/mcp/tool_registry.go for storage and deduplication logic, and in internal/mcp/mcp_client.go for runtime invocation checks and error generation. The type definitions in internal/mcp/types.go support these operations with the Tool, ToolInvocation, and ServerID structures.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →