Skip to content

feat: add pagination support for listRoots operation #336

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

package io.modelcontextprotocol.server;

import java.util.ArrayList;

import com.fasterxml.jackson.core.type.TypeReference;
import io.modelcontextprotocol.spec.McpError;
import io.modelcontextprotocol.spec.McpSchema;
Expand Down Expand Up @@ -126,7 +128,16 @@ public Mono<McpSchema.ElicitResult> createElicitation(McpSchema.ElicitRequest el
* @return A Mono that emits the list of roots result.
*/
public Mono<McpSchema.ListRootsResult> listRoots() {
return this.listRoots(null);

return this.listRoots(McpSchema.FIRST_PAGE).expand(result -> {
Copy link
Member

Choose a reason for hiding this comment

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

There are no tests for this? Can we have a test for 0 pages, 1 page, 5 pages? Network error after 3rd page?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've added a new Test for the aync exchange.

if (result.nextCursor() != null) {
return this.listRoots(result.nextCursor());
}
return Mono.empty();
}).reduce(new McpSchema.ListRootsResult(new ArrayList<>(), null), (allRootssResult, result) -> {
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
}).reduce(new McpSchema.ListRootsResult(new ArrayList<>(), null), (allRootssResult, result) -> {
}).reduce(new McpSchema.ListRootsResult(new ArrayList<>(), null), (allRootsResult, result) -> {

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

allRootssResult.roots().addAll(result.roots());
return allRootssResult;
Copy link
Member

Choose a reason for hiding this comment

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

The backing list needs to be made unmodifiable after being accumulated.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I made some adjustments , but not sure if this is what you've meant.

});
}

/**
Expand Down
10 changes: 9 additions & 1 deletion mcp/src/main/java/io/modelcontextprotocol/spec/McpSchema.java
Original file line number Diff line number Diff line change
Expand Up @@ -1615,11 +1615,19 @@ public record Root( // @formatter:off
*
* @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

}
Original file line number Diff line number Diff line change
Expand Up @@ -984,7 +984,7 @@ void testListRootsResult() throws Exception {

McpSchema.Root root2 = new McpSchema.Root("file:///path/to/root2", "Second Root");

McpSchema.ListRootsResult result = new McpSchema.ListRootsResult(Arrays.asList(root1, root2));
McpSchema.ListRootsResult result = new McpSchema.ListRootsResult(Arrays.asList(root1, root2), "next-cursor");

String value = mapper.writeValueAsString(result);

Expand All @@ -993,7 +993,7 @@ void testListRootsResult() throws Exception {
.isObject()
.isEqualTo(
json("""
{"roots":[{"uri":"file:///path/to/root1","name":"First Root"},{"uri":"file:///path/to/root2","name":"Second Root"}]}"""));
{"roots":[{"uri":"file:///path/to/root1","name":"First Root"},{"uri":"file:///path/to/root2","name":"Second Root"}],"nextCursor":"next-cursor"}"""));

}

Expand Down