You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
pkg/vmcp/aggregator/default_aggregator.go resolves tool name conflicts through a map keyed by name (ResolveToolConflicts → map[string]*ResolvedTool), so duplicate tool names across backends are handled. Resources, resource templates and prompts are a plain append across backends, under a comment that says so:
// Collect resources, resource templates, and prompts (no conflict resolution for these yet)
Nothing downstream deduplicates them — FilterResources only filters. So two backends exposing the same Resource.URI, ResourceTemplate.URITemplate or Prompt.Name produce a list containing both, with no resolution and no diagnostic.
This is ordinary, not pathological. Two instances of the same server type, or any two backends both exposing file:///README.md, hit it.
Why it's being filed now
It was the root cause of a data-loss defect in Modern list pagination, found during review of #6050. That code assumed the aggregator had already made keys unique — its comment cited this exact resolver — and used the key as a cursor position. With a duplicate at a page boundary, every item sharing the boundary key was permanently dropped: measured 1100 items in, 1097 delivered, scaling with the size of the duplicate run and reaching a full page when all keys collide.
The paginator has since been made collision-safe (it carries (LastKey, Delivered) and resumes inside a run of equal keys), so pagination is fixed and this issue is not a regression risk there. But the underlying non-uniqueness remains, and it is a trap for anything else that treats these fields as identities.
There is at least one other consumer already relying on it: MergeCapabilities keys routing as map[string]*vmcp.BackendTarget by URI, so duplicate resource URIs already collapse to one nondeterministically-chosen backend for reads. A client asking for a duplicated URI gets whichever backend won the map write, and which one that is may vary between runs.
Options
Resolve conflicts for the other three types, mirroring the tool path. Needs a decision on the policy — prefix with the backend name as tools do, prefer a priority order, or drop on collision as composite tools do — and on whether a duplicate URI should be surfaced to clients as one entry or two.
Leave the lists unresolved but make it explicit: document the property at the aggregation boundary and require consumers to handle collisions. This is closer to today's behaviour, and is what Complete Modern client-facing dispatch: listen + pagination #6050 now does locally.
Option 1 is the more correct fix but it changes what clients see on both revisions, including Legacy, so it wants deliberate scoping rather than being folded into unrelated work.
Whichever way it goes, MergeCapabilities' nondeterministic routing collapse should be addressed — silently picking a backend is worse than either resolving or erroring.
Notes
docs/arch/10-virtual-mcp-architecture.md now documents the tools-only uniqueness property in its Conflict Resolution section, added in #6050, so the trap is at least written down.
Summary
pkg/vmcp/aggregator/default_aggregator.goresolves tool name conflicts through a map keyed by name (ResolveToolConflicts→map[string]*ResolvedTool), so duplicate tool names across backends are handled. Resources, resource templates and prompts are a plainappendacross backends, under a comment that says so:// Collect resources, resource templates, and prompts (no conflict resolution for these yet)Nothing downstream deduplicates them —
FilterResourcesonly filters. So two backends exposing the sameResource.URI,ResourceTemplate.URITemplateorPrompt.Nameproduce a list containing both, with no resolution and no diagnostic.This is ordinary, not pathological. Two instances of the same server type, or any two backends both exposing
file:///README.md, hit it.Why it's being filed now
It was the root cause of a data-loss defect in Modern list pagination, found during review of #6050. That code assumed the aggregator had already made keys unique — its comment cited this exact resolver — and used the key as a cursor position. With a duplicate at a page boundary, every item sharing the boundary key was permanently dropped: measured 1100 items in, 1097 delivered, scaling with the size of the duplicate run and reaching a full page when all keys collide.
The paginator has since been made collision-safe (it carries
(LastKey, Delivered)and resumes inside a run of equal keys), so pagination is fixed and this issue is not a regression risk there. But the underlying non-uniqueness remains, and it is a trap for anything else that treats these fields as identities.There is at least one other consumer already relying on it:
MergeCapabilitieskeys routing asmap[string]*vmcp.BackendTargetby URI, so duplicate resource URIs already collapse to one nondeterministically-chosen backend for reads. A client asking for a duplicated URI gets whichever backend won the map write, and which one that is may vary between runs.Options
Option 1 is the more correct fix but it changes what clients see on both revisions, including Legacy, so it wants deliberate scoping rather than being folded into unrelated work.
Whichever way it goes,
MergeCapabilities' nondeterministic routing collapse should be addressed — silently picking a backend is worse than either resolving or erroring.Notes
docs/arch/10-virtual-mcp-architecture.mdnow documents the tools-only uniqueness property in its Conflict Resolution section, added in #6050, so the trap is at least written down.Refs #6050.