# How the MCP Client Handles Tool Name Collisions When Multiple Servers Expose the Same Tool

> Learn how the MCP client resolves tool name collisions using server-specific namespaces and explicit server IDs to ensure correct tool invocation across multiple servers.

- Repository: [Junjie.M/dify-plugin-agent-mcp_sse](https://github.com/junjiem/dify-plugin-agent-mcp_sse)
- Tags: internals
- Published: 2026-03-05

---

**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`](https://github.com/junjiem/dify-plugin-agent-mcp_sse/blob/main/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`](https://github.com/junjiem/dify-plugin-agent-mcp_sse/blob/main/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`](https://github.com/junjiem/dify-plugin-agent-mcp_sse/blob/main/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`](https://github.com/junjiem/dify-plugin-agent-mcp_sse/blob/main/internal/mcp/tool_registry.go) | Defines `ToolRegistry`, composite key storage, and `PublicToolList()` deduplication |
| [`internal/mcp/mcp_client.go`](https://github.com/junjiem/dify-plugin-agent-mcp_sse/blob/main/internal/mcp/mcp_client.go) | Implements `RegisterServerTools`, `InvokeTool`, and conflict error handling |
| [`internal/mcp/types.go`](https://github.com/junjiem/dify-plugin-agent-mcp_sse/blob/main/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:

```go
// 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:

```go
// 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:

```go
// 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.go`](https://github.com/junjiem/dify-plugin-agent-mcp_sse/blob/main/internal/mcp/tool_registry.go) handles namespacing during registration and deduplication when building public tool lists for UI consumption.
- For **runtime invocation**, the client requires explicit `server_id` parameters 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`](https://github.com/junjiem/dify-plugin-agent-mcp_sse/blob/main/internal/mcp/tool_registry.go) for storage and deduplication logic, and in [`internal/mcp/mcp_client.go`](https://github.com/junjiem/dify-plugin-agent-mcp_sse/blob/main/internal/mcp/mcp_client.go) for runtime invocation checks and error generation. The type definitions in [`internal/mcp/types.go`](https://github.com/junjiem/dify-plugin-agent-mcp_sse/blob/main/internal/mcp/types.go) support these operations with the `Tool`, `ToolInvocation`, and `ServerID` structures.