Skip to content

fix: aggregate every backend in tools/list, once each - #107

Merged
avelino merged 3 commits into
mainfrom
avelino/issue-106
Aug 4, 2026
Merged

fix: aggregate every backend in tools/list, once each#107
avelino merged 3 commits into
mainfrom
avelino/issue-106

Conversation

@avelino

@avelino avelino commented Aug 4, 2026

Copy link
Copy Markdown
Owner

Two separate faults in serve mode. register_tools appended to a Vec, so every re-discovery added another copy of a backend's whole tool list, and snapshot_cache_entries then persisted the inflated list, which is why the duplication survived restarts and grew on each one. Separately, discover_for_list only ran when the registry was empty, so a single backend restored from the tool cache was enough to skip discovering all the others. Together they answered tools/list with one backend repeated 28 times and four missing.

Registration now replaces instead of appending, and skips names already registered for that server. That second part collapses an already poisoned list on the next load, so nobody has to wipe the cache. A list request discovers whatever is still pending regardless of what the registry already holds, and the existing failure backoff keeps an unreachable backend from being retried on every call. The background refresh resets only what came off the cache instead of clearing the whole discovered set and reconnecting healthy backends.

Fixes #106

Two separate faults in serve mode. register_tools appended to a Vec, so every
re-discovery added another copy of a backend's whole tool list, and
snapshot_cache_entries then persisted the inflated list, which is why the
duplication survived restarts and grew on each one. Separately,
discover_for_list only ran when the registry was empty, so a single backend
restored from the tool cache was enough to skip discovering all the others.
Together they answered tools/list with one backend repeated 28 times and four
missing.

Registration now replaces instead of appending, and skips names already
registered for that server. That second part collapses an already poisoned list
on the next load, so nobody has to wipe the cache. A list request discovers
whatever is still pending regardless of what the registry already holds, and the
existing failure backoff keeps an unreachable backend from being retried on
every call. The background refresh resets only what came off the cache instead
of clearing the whole discovered set and reconnecting healthy backends.

Fixes #106

Signed-off-by: Avelino <31996+avelino@users.noreply.github.com>
Copilot AI lite review requested due to automatic review settings August 4, 2026 21:29

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes mcp serve registry corruption and incomplete discovery in serve mode by making backend registration idempotent (replace instead of append), healing already-poisoned cached tool lists, and ensuring */list triggers discovery for any still-pending backends (respecting existing failure backoff).

Changes:

  • Make tool/resource/prompt registration replace existing per-backend entries (and de-dupe same-name entries during a single registration) to stop persistent duplication growth.
  • Ensure tools/list, resources/list, and prompts/list always attempt discovery of any undiscovered configured backends (rather than only when the registry is empty).
  • Refine background refresh to re-discover only cache-loaded (disconnected) backends instead of clearing the full discovered set; add targeted tests for both failure modes from #106.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated no comments.

File Description
src/serve/proxy.rs Implements replace-not-append registration, adds de-dupe guards, introduces reset_cache_loaded_for_refresh, and adds regression tests for idempotency + cache healing.
src/serve/dispatch.rs Updates */list flows to discover pending backends even when registries are non-empty; adds test covering partial-cache suppression regression.
src/serve/http.rs Uses reset_cache_loaded_for_refresh() for post-cache background refresh without resetting healthy backends.
src/serve/stdio.rs Uses reset_cache_loaded_for_refresh() in the periodic refresh path instead of clearing all discovered backends.

Signed-off-by: Avelino <31996+avelino@users.noreply.github.com>
Copilot AI review requested due to automatic review settings August 4, 2026 21:33
Signed-off-by: Avelino <31996+avelino@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 6 changed files in this pull request and generated no new comments.

Copilot AI review requested due to automatic review settings August 4, 2026 21:37
@avelino
avelino merged commit 92599cb into main Aug 4, 2026
8 checks passed
@avelino
avelino deleted the avelino/issue-106 branch August 4, 2026 21:39

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 6 changed files in this pull request and generated no new comments.

Suppressed comments (1)

src/serve/http.rs:454

  • The comment describing this refresh block says it "clear[s] the discovered set", but the code now calls reset_cache_loaded_for_refresh() (which only resets cache-loaded/disconnected backends). Please update the comment so it matches the new behavior to avoid misleading future changes.
                let mut proxy = refresh_proxy.lock().await;
                proxy.reset_cache_loaded_for_refresh();
            }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

mcp serve returns one backend duplicated 28x, drops the other four

2 participants