Skip to content

Fixes #1174 - #1175

Open
kamisheng wants to merge 1 commit into
modelcontextprotocol:mainfrom
kamisheng:fix/resubscribe-shared-reduce-seed
Open

kamisheng wants to merge 1 commit into
modelcontextprotocol:mainfrom
kamisheng:fix/resubscribe-shared-reduce-seed

Conversation

@kamisheng

@kamisheng kamisheng commented Oct 12, 2026 •

Copy link
Copy Markdown

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.

This branch has not been deployed

No deployments
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

2 participants