Skip to content

feat(client): add Flow-based pagination helpers (#1045) - #1134

Open
anzal1 wants to merge 1 commit into
modelcontextprotocol:mainfrom
anzal1:feat/1045-list-flows
Open

anzal1 wants to merge 1 commit into
modelcontextprotocol:mainfrom
anzal1:feat/1045-list-flows

Conversation

@anzal1

@anzal1 anzal1 commented Oct 24, 2026

Copy link
Copy Markdown

Closes #1045

What was added

Four new Client functions that return a cold Flow and follow nextCursor until the server returns none:

  • toolsFlow(request, options): Flow<Tool>
  • promptsFlow(request, options): Flow<Prompt>
  • resourcesFlow(request, options): Flow<Resource>
  • resourceTemplatesFlow(request, options): Flow<ResourceTemplate>

Behaviour:

  • Cold: nothing is sent until the flow is collected, and each collection starts from the first page.
  • Lazy: one page is requested at a time, as the collector consumes items. Cancelling the collector, take(n) or first() stops further requests, and a pending page request is cancelled with it.
  • Errors from any page request (for example an McpException on page 2) are thrown to the collector after the earlier items were emitted.
  • request is optional. If it carries a cursor, the flow starts there. Its _meta is sent with every page request. options apply to every page request.
  • Pagination guard: if the server returns a nextCursor that was already used in this collection (including the starting cursor), the flow throws IllegalStateException instead of looping forever. The guard keeps a set of the cursors seen so far, so it also catches cycles longer than one page.

API

client.toolsFlow().collect { tool -> println(tool.name) }

val firstTen = client.toolsFlow().take(10).toList()

ABI change

This is an intentional API addition. updateKotlinAbi added 8 lines to kotlin-sdk-client/api/kotlin-sdk-client.api (the four functions and their $default synthetics) and 4 lines to kotlin-sdk-client.klib.api. No existing declaration changed.

Tests

ClientListFlowTest (commonTest, in-memory transport, backtick names) covers: multiple pages collected in order, a single page, an empty result, a cold flow that restarts per collection, cancellation mid-way (no further requests), take/first stopping early, cancelling an in-flight page request, an error on page 2 propagating, a repeated cursor, a cycle back to an earlier cursor, starting cursor and _meta handling, plus one ordering test each for prompts, resources and resource templates.

Run with JAVA_HOME=<JDK 21>:

./gradlew :kotlin-sdk-client:jvmTest --tests '*Flow*'
./gradlew :kotlin-sdk-client:jvmTest
./gradlew :kotlin-sdk-client:ktlintCheck :kotlin-sdk-client:detekt
./gradlew :kotlin-sdk-client:checkKotlinAbi   # fails before updateKotlinAbi, passes after
./gradlew :kotlin-sdk-client:updateKotlinAbi

I also checked the tests by mutation. With the cursor-follow broken, 10 of 14 tests fail. With the repeated-cursor guard removed, the two guard tests fail.

Only the client module JVM tests were run locally.

…#1045)

Add toolsFlow, promptsFlow, resourcesFlow and resourceTemplatesFlow to
Client. Each returns a cold Flow that follows nextCursor page by page until
the server returns none, and fails with IllegalStateException if the server
repeats a cursor instead of looping forever.

@jetbrains-air jetbrains-air Bot 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.

Nice work — the pagination helpers look solid, approving!

Produced by Air Automations. Name: Code review / Run: https://air.jetbrains.cloud/org/05cf1a7f-6ab5-713b-abd3-29d0c8a05e2d/automations/409567ec-d3a7-40ac-ba95-8518ec577ac2?run=08197b1d-36d6-41b3-bddf-40fd100ef424

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.

Client: Flow-based pagination for list operations

1 participant