Tool aggregation and conflict resolution
When aggregating multiple MCP servers, tool name conflicts can occur when different backends expose tools with the same name. Virtual MCP provides strategies to resolve these conflicts automatically.
Overview
Virtual MCP discovers tools from all backend MCPServers in the referenced group
and presents them as a unified set to clients. When two backends have tools with
the same name (for example, both GitHub and Jira have a create_issue tool), a
conflict resolution strategy determines how to handle the collision.
Conflict resolution strategies
Prefix strategy (default)
Prefixes all tool names with the workload identifier. This guarantees unique names and is the safest option for most deployments.
spec:
aggregation:
conflictResolution: prefix
conflictResolutionConfig:
prefixFormat: '{workload}_'
Prefix format options:
| Format | Example result |
|---|---|
{workload} | githubcreate_issue |
{workload}_ | github_create_issue |
{workload}. | github.create_issue |
Example:
With backends github and jira, both exposing create_issue:
- GitHub's tool becomes
github_create_issue - Jira's tool becomes
jira_create_issue
Priority strategy
The first workload in the priority order wins. Conflicting tools from lower-priority backends are dropped.
spec:
aggregation:
conflictResolution: priority
conflictResolutionConfig:
priorityOrder: ['github', 'jira', 'slack']
When to use: When you have a preferred backend for specific tools and want to hide duplicates.
The priority strategy drops tools from lower-priority backends. Ensure this is the intended behavior before using in production.
Manual strategy
Requires explicit overrides for all conflicting tools. Fails at startup if conflicts exist without overrides.
spec:
aggregation:
conflictResolution: manual
tools:
- workload: github
overrides:
create_issue:
name: gh_create_issue
- workload: jira
overrides:
create_issue:
name: jira_ticket
When to use: Production deployments where you want explicit control over tool names.
Tool filtering
Limit which tools are exposed from a specific backend:
spec:
aggregation:
tools:
- workload: github
filter: ['create_issue', 'list_issues', 'get_issue']
Only the listed tools are included; all others from that backend are excluded.
Tool overrides
Rename tools or update descriptions:
spec:
aggregation:
tools:
- workload: github
overrides:
create_issue:
name: gh_new_issue
description: 'Create a new GitHub issue in the repository'
You can also reference an MCPToolConfig resource using toolConfigRef instead
of inline filter and overrides. This feature is currently in development.
Combining filter and overrides
You can combine filtering and overrides for fine-grained control:
spec:
aggregation:
conflictResolution: prefix
conflictResolutionConfig:
prefixFormat: '{workload}_'
tools:
- workload: github
filter: ['create_issue', 'list_issues']
overrides:
create_issue:
description: 'Create a GitHub issue (engineering team)'
- workload: jira
filter: ['create_issue', 'search_issues']
Example: Aggregating multiple MCP servers
This example shows two MCP servers (fetch and osv) aggregated with prefix-based conflict resolution:
# MCPGroup to organize backend servers
apiVersion: toolhive.stacklok.dev/v1alpha1
kind: MCPGroup
metadata:
name: demo-tools
namespace: toolhive-system
spec:
description: Demo group for tool aggregation
---
# First backend: fetch server
apiVersion: toolhive.stacklok.dev/v1alpha1
kind: MCPServer
metadata:
name: fetch
namespace: toolhive-system
spec:
image: ghcr.io/stackloklabs/gofetch/server
transport: streamable-http
proxyPort: 8080
mcpPort: 8080
groupRef: demo-tools
---
# Second backend: osv server
apiVersion: toolhive.stacklok.dev/v1alpha1
kind: MCPServer
metadata:
name: osv
namespace: toolhive-system
spec:
image: ghcr.io/stackloklabs/osv-mcp/server
transport: streamable-http
proxyPort: 8080
mcpPort: 8080
groupRef: demo-tools
---
# VirtualMCPServer aggregating both backends
apiVersion: toolhive.stacklok.dev/v1alpha1
kind: VirtualMCPServer
metadata:
name: demo-vmcp
namespace: toolhive-system
spec:
groupRef:
name: demo-tools
incomingAuth:
type: anonymous
aggregation:
conflictResolution: prefix
conflictResolutionConfig:
prefixFormat: '{workload}_'
With this configuration, tools from each backend are prefixed:
fetch_*tools from the fetch serverosv_*tools from the osv server