feat: add pagination support for listRoots operation (#336)

- Add nextCursor field to ListRootsResult for cursor-based pagination
- Implement automatic pagination in McpAsyncServerExchange.listRoots()
 - Automatically fetches and combines all pages into single result
 - Refactor McpAsyncServerExchange.listRoots() to return Collections.unmodifiableList
- Update tests to verify pagination functionality

- Add test suite for McpAsyncServerExchange covering:
  - listRoots() pagination scenarios and edge cases
  - Logging notification with level filtering
  - Elicitation creation with various capabilities
  - Message creation with sampling capabilities
  - Error handling and validation scenarios
  - Verify sendNotification calls in logging notification tests
  - Verify sendRequest is never called when capabilities are missing

Signed-off-by: Christian Tzolov <christian.tzolov@broadcom.com>
This commit is contained in:
Christian Tzolov 2025-06-24 10:56:07 +02:00
commit 1cbace3353
2 changed files with 770 additions and 4 deletions

View file

@ -4,6 +4,9 @@
package io.modelcontextprotocol.server;
import java.util.ArrayList;
import java.util.Collections;
import com.fasterxml.jackson.core.type.TypeReference;
import io.modelcontextprotocol.spec.McpError;
import io.modelcontextprotocol.spec.McpSchema;
@ -126,7 +129,19 @@ public class McpAsyncServerExchange {
* @return A Mono that emits the list of roots result.
*/
public Mono<McpSchema.ListRootsResult> listRoots() {
return this.listRoots(null);
// @formatter:off
return this.listRoots(McpSchema.FIRST_PAGE)
.expand(result -> (result.nextCursor() != null) ?
this.listRoots(result.nextCursor()) : Mono.empty())
.reduce(new McpSchema.ListRootsResult(new ArrayList<>(), null),
(allRootsResult, result) -> {
allRootsResult.roots().addAll(result.roots());
return allRootsResult;
})
.map(result -> new McpSchema.ListRootsResult(Collections.unmodifiableList(result.roots()),
result.nextCursor()));
// @formatter:on
}
/**

View file

@ -1628,11 +1628,19 @@ public final class McpSchema {
*
* @param roots An array of Root objects, each representing a root directory or file
* that the server can operate on.
* @param nextCursor An optional cursor for pagination. If present, indicates there
* are more roots available. The client can use this cursor to request the next page
* of results by sending a roots/list request with the cursor parameter set to this
*/
@JsonInclude(JsonInclude.Include.NON_ABSENT)
@JsonIgnoreProperties(ignoreUnknown = true)
public record ListRootsResult( // @formatter:off
@JsonProperty("roots") List<Root> roots) {
@JsonProperty("roots") List<Root> roots,
@JsonProperty("nextCursor") String nextCursor) {
public ListRootsResult(List<Root> roots) {
this(roots, null);
}
} // @formatter:on
}