FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

fix: stop pagination on empty cursors by nanookclaw · Pull Request #954 · modelcontextprotocol/java-sdk · GitHub

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension .java  (2) All 1 file type selected
Viewed files
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Unified
Split
Hide whitespace
Diff view
Unified
Split
Hide whitespace
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -818,13 +818,13 @@ private NotificationHandler asyncToolsChangeNotificationHandler(
* @see #readResource(McpSchema.Resource)
*/
public Mono<McpSchema.ListResourcesResult> listResources() {
return this.listResources(McpSchema.FIRST_PAGE)
.expand(result -> (result.nextCursor() != null) ? this.listResources(result.nextCursor()) : Mono.empty())
.reduce(new ArrayList<McpSchema.Resource>(), (accumulated, result) -> {
accumulated.addAll(result.resources());
return accumulated;
})
.map(all -> McpSchema.ListResourcesResult.builder(Collections.unmodifiableList(all)).build());
return this.listResources(McpSchema.FIRST_PAGE).expand(result -> {
String next = result.nextCursor();
return (next != null && !next.isEmpty()) ? this.listResources(next) : Mono.empty();
}).reduce(new ArrayList<McpSchema.Resource>(), (accumulated, result) -> {
accumulated.addAll(result.resources());
return accumulated;
}).map(all -> McpSchema.ListResourcesResult.builder(Collections.unmodifiableList(all)).build());
}

/**
Expand Down Expand Up @@ -904,14 +904,13 @@ public Mono<McpSchema.ReadResourceResult> readResource(McpSchema.ReadResourceReq
* @see McpSchema.ListResourceTemplatesResult
*/
public Mono<McpSchema.ListResourceTemplatesResult> listResourceTemplates() {
return this.listResourceTemplates(McpSchema.FIRST_PAGE)
.expand(result -> (result.nextCursor() != null) ? this.listResourceTemplates(result.nextCursor())
: Mono.empty())
.reduce(new ArrayList<McpSchema.ResourceTemplate>(), (accumulated, result) -> {
accumulated.addAll(result.resourceTemplates());
return accumulated;
})
.map(all -> McpSchema.ListResourceTemplatesResult.builder(Collections.unmodifiableList(all)).build());
return this.listResourceTemplates(McpSchema.FIRST_PAGE).expand(result -> {
String next = result.nextCursor();
return (next != null && !next.isEmpty()) ? this.listResourceTemplates(next) : Mono.empty();
}).reduce(new ArrayList<McpSchema.ResourceTemplate>(), (accumulated, result) -> {
accumulated.addAll(result.resourceTemplates());
return accumulated;
}).map(all -> McpSchema.ListResourceTemplatesResult.builder(Collections.unmodifiableList(all)).build());
}

/**
Expand Down Expand Up @@ -1024,13 +1023,13 @@ private NotificationHandler asyncResourcesUpdatedNotificationHandler(
* @see #getPrompt(GetPromptRequest)
*/
public Mono<ListPromptsResult> listPrompts() {
return this.listPrompts(McpSchema.FIRST_PAGE)
.expand(result -> (result.nextCursor() != null) ? this.listPrompts(result.nextCursor()) : Mono.empty())
.reduce(new ArrayList<McpSchema.Prompt>(), (accumulated, result) -> {
accumulated.addAll(result.prompts());
return accumulated;
})
.map(all -> McpSchema.ListPromptsResult.builder(Collections.unmodifiableList(all)).build());
return this.listPrompts(McpSchema.FIRST_PAGE).expand(result -> {
String next = result.nextCursor();
return (next != null && !next.isEmpty()) ? this.listPrompts(next) : Mono.empty();
}).reduce(new ArrayList<McpSchema.Prompt>(), (accumulated, result) -> {
accumulated.addAll(result.prompts());
return accumulated;
}).map(all -> McpSchema.ListPromptsResult.builder(Collections.unmodifiableList(all)).build());
}

/**
Expand Down
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Function;
import java.util.stream.Collectors;
Expand Down Expand Up @@ -298,6 +299,42 @@ void testListPromptsWithCursorAndMeta() {

}

@Test
void listResourcesStopsOnEmptyNextCursor() {
var transport = new EmptyCursorTestMcpClientTransport(McpSchema.METHOD_RESOURCES_LIST);
McpAsyncClient client = McpClient.async(transport).build();

McpSchema.ListResourcesResult result = client.listResources().block();

assertThat(result).isNotNull();
assertThat(result.resources()).extracting(McpSchema.Resource::name).containsExactly("test.txt");
assertThat(transport.getRequestCount()).isEqualTo(1);
}

@Test
void listResourceTemplatesStopsOnEmptyNextCursor() {
var transport = new EmptyCursorTestMcpClientTransport(McpSchema.METHOD_RESOURCES_TEMPLATES_LIST);
McpAsyncClient client = McpClient.async(transport).build();

McpSchema.ListResourceTemplatesResult result = client.listResourceTemplates().block();

assertThat(result).isNotNull();
assertThat(result.resourceTemplates()).extracting(McpSchema.ResourceTemplate::name).containsExactly("template");
assertThat(transport.getRequestCount()).isEqualTo(1);
}

@Test
void listPromptsStopsOnEmptyNextCursor() {
var transport = new EmptyCursorTestMcpClientTransport(McpSchema.METHOD_PROMPT_LIST);
McpAsyncClient client = McpClient.async(transport).build();

McpSchema.ListPromptsResult result = client.listPrompts().block();

assertThat(result).isNotNull();
assertThat(result.prompts()).extracting(McpSchema.Prompt::name).containsExactly("test-prompt");
assertThat(transport.getRequestCount()).isEqualTo(1);
}

static class TestMcpClientTransport implements McpClientTransport {

private Function<Mono<McpSchema.JSONRPCMessage>, Mono<McpSchema.JSONRPCMessage>> handler;
Expand Down Expand Up @@ -397,4 +434,90 @@ public McpSchema.PaginatedRequest getCapturedRequest() {

}

static class EmptyCursorTestMcpClientTransport implements McpClientTransport {

private final String listMethod;

private final AtomicInteger requestCount = new AtomicInteger();

private Function<Mono<McpSchema.JSONRPCMessage>, Mono<McpSchema.JSONRPCMessage>> handler;

EmptyCursorTestMcpClientTransport(String listMethod) {
this.listMethod = listMethod;
}

@Override
public Mono<Void> connect(Function<Mono<McpSchema.JSONRPCMessage>, Mono<McpSchema.JSONRPCMessage>> handler) {
this.handler = handler;
return Mono.empty();
}

@Override
public Mono<Void> closeGracefully() {
return Mono.empty();
}

@Override
public Mono<Void> sendMessage(McpSchema.JSONRPCMessage message) {
if (!(message instanceof McpSchema.JSONRPCRequest request)) {
return Mono.empty();
}

McpSchema.JSONRPCResponse response;
if (McpSchema.METHOD_INITIALIZE.equals(request.method())) {
McpSchema.ServerCapabilities caps = McpSchema.ServerCapabilities.builder()
.prompts(false)
.resources(false, false)
.tools(false)
.build();

McpSchema.InitializeResult initResult = McpSchema.InitializeResult
.builder(ProtocolVersions.MCP_2024_11_05, caps, MOCK_SERVER_INFO)
.build();
response = McpSchema.JSONRPCResponse.result(request.id(), initResult);
}
else if (this.listMethod.equals(request.method())) {
this.requestCount.incrementAndGet();
response = McpSchema.JSONRPCResponse.result(request.id(), resultForMethod(request.method()));
}
else {
return Mono.empty();
}

return this.handler.apply(Mono.just(response)).then();
}

private Object resultForMethod(String method) {
if (McpSchema.METHOD_RESOURCES_LIST.equals(method)) {
McpSchema.Resource resource = McpSchema.Resource.builder("file:///test.txt", "test.txt").build();
return McpSchema.ListResourcesResult.builder(List.of(resource)).nextCursor("").build();
}
if (McpSchema.METHOD_RESOURCES_TEMPLATES_LIST.equals(method)) {
McpSchema.ResourceTemplate template = McpSchema.ResourceTemplate.builder("file:///{name}", "template")
.build();
return McpSchema.ListResourceTemplatesResult.builder(List.of(template)).nextCursor("").build();
}
if (McpSchema.METHOD_PROMPT_LIST.equals(method)) {
McpSchema.Prompt prompt = McpSchema.Prompt.builder("test-prompt").build();
return McpSchema.ListPromptsResult.builder(List.of(prompt)).nextCursor("").build();
}
throw new IllegalArgumentException("Unsupported method: " + method);
}

@Override
public <T> T unmarshalFrom(Object data, TypeRef<T> typeRef) {
return JSON_MAPPER.convertValue(data, new TypeRef<>() {
@Override
public java.lang.reflect.Type getType() {
return typeRef.getType();
}
});
}

int getRequestCount() {
return this.requestCount.get();
}

}

}
Loading

Back | FazBrowse Home | New Git URL