Skip to content

listXyz() methods return re-subscribable Monos - #1175

Merged
Kehrlann merged 1 commit into
modelcontextprotocol:mainfrom
kamisheng:fix/resubscribe-shared-reduce-seed
Oct 13, 2026
Merged

Kehrlann merged 1 commit into
modelcontextprotocol:mainfrom
kamisheng:fix/resubscribe-shared-reduce-seed

Conversation

@kamisheng

@kamisheng kamisheng commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

Motivation and Context

The Mono returned by listTools, listResources, listResourceTemplates, listPrompts
and listRoots is not safe to subscribe more than once: reduce(seed, accumulator)
captures one mutable seed instance created when the method is called, so every
subscription keeps appending to it.

Mono<McpSchema.ListRootsResult> listRoots = exchange.listRoots();
listRoots.block().roots().size();   // 2
listRoots.block().roots().size();   // 4
listRoots.block().roots().size();   // 6

Concurrent subscriptions also race on the shared ArrayList.

Description

Replaced reduce(seed, accumulator) with reduceWith(Supplier, accumulator) at the five
call sites, so a fresh accumulator is created per subscription. Behavior for a single
subscription is unchanged.

Fixes #1174

How Has This Been Tested?

  • Added McpAsyncServerExchangeTests#testListRootsCanBeResubscribed, which subscribes the
    same Mono twice and asserts both subscriptions see the same number of roots. It fails
    before the change (2 then 4) and passes after.
  • Client-side: added a re-subscription test to AbstractMcpAsyncClientTests, which runs
    against the SSE, stdio and Streamable HTTP transports.
  • ./mvnw -pl mcp-core spring-javaformat:apply
  • ./mvnw -pl mcp-core test (73 tests in the affected classes pass)

Types of changes

  • Bug fix (non-breaking change which fixes an issue)

The Mono returned by listTools, listResources, listResourceTemplates,
listPrompts and listRoots captured a single mutable accumulator created when
the method was called, so every subscription kept appending to it:

    Mono<ListRootsResult> listRoots = exchange.listRoots();
    listRoots.block().roots().size();   // 2
    listRoots.block().roots().size();   // 4
    listRoots.block().roots().size();   // 6

Concurrent subscriptions also raced on the shared ArrayList.

Use reduceWith(Supplier, accumulator) so that a fresh accumulator is created
for every subscription. A single subscription behaves exactly as before.
@Kehrlann Kehrlann changed the title Fixes #1174 listXyz() methods return re-subscribable Monos Oct 13, 2026
@Kehrlann
Kehrlann merged commit 5be23a9 into modelcontextprotocol:main Oct 13, 2026
21 checks passed
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.

[BUG] Re-subscribing the Mono returned by listTools/listResources/listResourceTemplates/listPrompts/listRoots duplicates the results

3 participants