Skip to content

Commit 86a9579

Browse files
committed
Add client side list handlers to handle pagination
Add client side list handlers to handle pagination for prompts, resources and resource templates.
1 parent 62d1552 commit 86a9579

File tree

6 files changed

+389
-45
lines changed

6 files changed

+389
-45
lines changed

mcp-test/src/main/java/io/modelcontextprotocol/client/AbstractMcpAsyncClientTests.java

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,23 @@ void testListTools() {
162162
});
163163
}
164164

165+
@Test
166+
void testListAllTools() {
167+
withClient(createMcpTransport(), mcpAsyncClient -> {
168+
StepVerifier.create(mcpAsyncClient.initialize().then(mcpAsyncClient.listAllTools()))
169+
.consumeNextWith(tools -> {
170+
assertThat(tools).isNotNull().satisfies(result -> {
171+
assertThat(result).isNotEmpty();
172+
173+
Tool firstTool = result.get(0);
174+
assertThat(firstTool.name()).isNotNull();
175+
assertThat(firstTool.description()).isNotNull();
176+
});
177+
})
178+
.verifyComplete();
179+
});
180+
}
181+
165182
@Test
166183
void testPingWithoutInitialization() {
167184
verifyCallSucceedsWithImplicitInitialization(client -> client.ping(), "pinging the server");
@@ -293,6 +310,23 @@ void testListResources() {
293310
});
294311
}
295312

313+
@Test
314+
void testListAllResources() {
315+
withClient(createMcpTransport(), mcpAsyncClient -> {
316+
StepVerifier.create(mcpAsyncClient.initialize().then(mcpAsyncClient.listAllResources()))
317+
.consumeNextWith(resources -> {
318+
assertThat(resources).isNotNull().satisfies(result -> {
319+
assertThat(result).isNotEmpty();
320+
321+
Resource firstResource = result.get(0);
322+
assertThat(firstResource.uri()).isNotNull();
323+
assertThat(firstResource.name()).isNotNull();
324+
});
325+
})
326+
.verifyComplete();
327+
});
328+
}
329+
296330
@Test
297331
void testMcpAsyncClientState() {
298332
withClient(createMcpTransport(), mcpAsyncClient -> {
@@ -324,6 +358,23 @@ void testListPrompts() {
324358
});
325359
}
326360

361+
@Test
362+
void testListAllPrompts() {
363+
withClient(createMcpTransport(), mcpAsyncClient -> {
364+
StepVerifier.create(mcpAsyncClient.initialize().then(mcpAsyncClient.listAllPrompts()))
365+
.consumeNextWith(prompts -> {
366+
assertThat(prompts).isNotNull().satisfies(result -> {
367+
assertThat(result).isNotEmpty();
368+
369+
Prompt firstPrompt = result.get(0);
370+
assertThat(firstPrompt.name()).isNotNull();
371+
assertThat(firstPrompt.description()).isNotNull();
372+
});
373+
})
374+
.verifyComplete();
375+
});
376+
}
377+
327378
@Test
328379
void testGetPromptWithoutInitialization() {
329380
GetPromptRequest request = new GetPromptRequest("simple_prompt", Map.of());
@@ -439,6 +490,23 @@ void testListResourceTemplates() {
439490
});
440491
}
441492

493+
@Test
494+
void testListAllResourceTemplates() {
495+
withClient(createMcpTransport(), mcpAsyncClient -> {
496+
StepVerifier.create(mcpAsyncClient.initialize().then(mcpAsyncClient.listAllResourceTemplates()))
497+
.consumeNextWith(resourceTemplates -> {
498+
assertThat(resourceTemplates).isNotNull().satisfies(result -> {
499+
assertThat(result).isNotEmpty();
500+
501+
McpSchema.ResourceTemplate firstResourceTemplate = result.get(0);
502+
assertThat(firstResourceTemplate.name()).isNotNull();
503+
assertThat(firstResourceTemplate.description()).isNotNull();
504+
});
505+
})
506+
.verifyComplete();
507+
});
508+
}
509+
442510
// @Test
443511
void testResourceSubscription() {
444512
withClient(createMcpTransport(), mcpAsyncClient -> {

mcp-test/src/main/java/io/modelcontextprotocol/client/AbstractMcpSyncClientTests.java

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,22 @@ void testListTools() {
161161
});
162162
}
163163

164+
@Test
165+
void testListAllTools() {
166+
withClient(createMcpTransport(), mcpSyncClient -> {
167+
mcpSyncClient.initialize();
168+
List<Tool> tools = mcpSyncClient.listAllTools();
169+
170+
assertThat(tools).isNotNull().satisfies(result -> {
171+
assertThat(result).isNotEmpty();
172+
173+
Tool firstTool = result.get(0);
174+
assertThat(firstTool.name()).isNotNull();
175+
assertThat(firstTool.description()).isNotNull();
176+
});
177+
});
178+
}
179+
164180
@Test
165181
void testCallToolsWithoutInitialization() {
166182
verifyCallSucceedsWithImplicitInitialization(
@@ -320,6 +336,22 @@ void testListResources() {
320336
});
321337
}
322338

339+
@Test
340+
void testListAllResources() {
341+
withClient(createMcpTransport(), mcpSyncClient -> {
342+
mcpSyncClient.initialize();
343+
List<Resource> resources = mcpSyncClient.listAllResources();
344+
345+
assertThat(resources).isNotNull().satisfies(result -> {
346+
assertThat(result).isNotEmpty();
347+
348+
Resource firstResource = result.get(0);
349+
assertThat(firstResource.uri()).isNotNull();
350+
assertThat(firstResource.name()).isNotNull();
351+
});
352+
});
353+
}
354+
323355
@Test
324356
void testClientSessionState() {
325357
withClient(createMcpTransport(), mcpSyncClient -> {
@@ -418,6 +450,22 @@ void testListResourceTemplates() {
418450
});
419451
}
420452

453+
@Test
454+
void testListAllResourceTemplates() {
455+
withClient(createMcpTransport(), mcpSyncClient -> {
456+
mcpSyncClient.initialize();
457+
List<McpSchema.ResourceTemplate> resourceTemplates = mcpSyncClient.listAllResourceTemplates();
458+
459+
assertThat(resourceTemplates).isNotNull().satisfies(result -> {
460+
assertThat(result).isNotEmpty();
461+
462+
McpSchema.ResourceTemplate firstResourceTemplate = result.get(0);
463+
assertThat(firstResourceTemplate.name()).isNotNull();
464+
assertThat(firstResourceTemplate.description()).isNotNull();
465+
});
466+
});
467+
}
468+
421469
// @Test
422470
void testResourceSubscription() {
423471
withClient(createMcpTransport(), mcpSyncClient -> {

mcp/src/main/java/io/modelcontextprotocol/client/McpAsyncClient.java

Lines changed: 81 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,11 @@
1313
import java.util.function.Function;
1414
import java.util.function.Supplier;
1515

16+
import org.slf4j.Logger;
17+
import org.slf4j.LoggerFactory;
18+
1619
import com.fasterxml.jackson.core.type.TypeReference;
20+
1721
import io.modelcontextprotocol.spec.McpClientSession;
1822
import io.modelcontextprotocol.spec.McpClientSession.NotificationHandler;
1923
import io.modelcontextprotocol.spec.McpClientSession.RequestHandler;
@@ -35,8 +39,6 @@
3539
import io.modelcontextprotocol.spec.McpTransportSessionNotFoundException;
3640
import io.modelcontextprotocol.util.Assert;
3741
import io.modelcontextprotocol.util.Utils;
38-
import org.slf4j.Logger;
39-
import org.slf4j.LoggerFactory;
4042
import reactor.core.publisher.Flux;
4143
import reactor.core.publisher.Mono;
4244
import reactor.core.publisher.Sinks;
@@ -656,7 +658,7 @@ public Mono<McpSchema.CallToolResult> callTool(McpSchema.CallToolRequest callToo
656658
}
657659

658660
/**
659-
* Retrieves the list of all tools provided by the server.
661+
* Retrieves the first page of tools provided by the server.
660662
* @return A Mono that emits the list of tools result.
661663
*/
662664
public Mono<McpSchema.ListToolsResult> listTools() {
@@ -679,18 +681,26 @@ public Mono<McpSchema.ListToolsResult> listTools(String cursor) {
679681
});
680682
}
681683

682-
private NotificationHandler asyncToolsChangeNotificationHandler(
683-
List<Function<List<McpSchema.Tool>, Mono<Void>>> toolsChangeConsumers) {
684-
// TODO: params are not used yet
685-
return params -> this.listTools().expand(result -> {
684+
/**
685+
* Retrieves list of all tools provided by the server after handling pagination.
686+
* @return A Mono that emits a list of all tools
687+
*/
688+
public Mono<List<McpSchema.Tool>> listAllTools() {
689+
return this.listTools().expand(result -> {
686690
if (result.nextCursor() != null) {
687691
return this.listTools(result.nextCursor());
688692
}
689693
return Mono.empty();
690-
}).reduce(new ArrayList<McpSchema.Tool>(), (allTools, result) -> {
694+
}).reduce(new ArrayList<>(), (allTools, result) -> {
691695
allTools.addAll(result.tools());
692696
return allTools;
693-
})
697+
});
698+
}
699+
700+
private NotificationHandler asyncToolsChangeNotificationHandler(
701+
List<Function<List<McpSchema.Tool>, Mono<Void>>> toolsChangeConsumers) {
702+
// TODO: params are not used yet
703+
return params -> this.listAllTools()
694704
.flatMap(allTools -> Flux.fromIterable(toolsChangeConsumers)
695705
.flatMap(consumer -> consumer.apply(allTools))
696706
.onErrorResume(error -> {
@@ -714,9 +724,9 @@ private NotificationHandler asyncToolsChangeNotificationHandler(
714724
};
715725

716726
/**
717-
* Retrieves the list of all resources provided by the server. Resources represent any
718-
* kind of UTF-8 encoded data that an MCP server makes available to clients, such as
719-
* database records, API responses, log files, and more.
727+
* Retrieves the first page of resources provided by the server. Resources represent
728+
* any kind of UTF-8 encoded data that an MCP server makes available to clients, such
729+
* as database records, API responses, log files, and more.
720730
* @return A Mono that completes with the list of resources result.
721731
* @see McpSchema.ListResourcesResult
722732
* @see #readResource(McpSchema.Resource)
@@ -745,6 +755,26 @@ public Mono<McpSchema.ListResourcesResult> listResources(String cursor) {
745755
});
746756
}
747757

758+
/**
759+
* Retrieves the list of all resources provided by the server. Resources represent any
760+
* kind of UTF-8 encoded data that an MCP server makes available to clients, such as
761+
* database records, API responses, log files, and more.
762+
* @return A Mono that completes with list of all resources.
763+
* @see McpSchema.Resource
764+
* @see #readResource(McpSchema.Resource)
765+
*/
766+
public Mono<List<McpSchema.Resource>> listAllResources() {
767+
return this.listResources().expand(result -> {
768+
if (result.nextCursor() != null) {
769+
return this.listResources(result.nextCursor());
770+
}
771+
return Mono.empty();
772+
}).reduce(new ArrayList<>(), (allResources, result) -> {
773+
allResources.addAll(result.resources());
774+
return allResources;
775+
});
776+
}
777+
748778
/**
749779
* Reads the content of a specific resource identified by the provided Resource
750780
* object. This method fetches the actual data that the resource represents.
@@ -777,7 +807,7 @@ public Mono<McpSchema.ReadResourceResult> readResource(McpSchema.ReadResourceReq
777807
}
778808

779809
/**
780-
* Retrieves the list of all resource templates provided by the server. Resource
810+
* Retrieves the first page of resource templates provided by the server. Resource
781811
* templates allow servers to expose parameterized resources using URI templates,
782812
* enabling dynamic resource access based on variable parameters.
783813
* @return A Mono that completes with the list of resource templates result.
@@ -806,6 +836,25 @@ public Mono<McpSchema.ListResourceTemplatesResult> listResourceTemplates(String
806836
});
807837
}
808838

839+
/**
840+
* Retrieves the list of all resource templates provided by the server. Resource
841+
* templates allow servers to expose parameterized resources using URI templates,
842+
* enabling dynamic resource access based on variable parameters.
843+
* @return A Mono that completes with the list of all resource templates.
844+
* @see McpSchema.ResourceTemplate
845+
*/
846+
public Mono<List<McpSchema.ResourceTemplate>> listAllResourceTemplates() {
847+
return this.listResourceTemplates().expand(result -> {
848+
if (result.nextCursor() != null) {
849+
return this.listResourceTemplates(result.nextCursor());
850+
}
851+
return Mono.empty();
852+
}).reduce(new ArrayList<>(), (allResourceTemplates, result) -> {
853+
allResourceTemplates.addAll(result.resourceTemplates());
854+
return allResourceTemplates;
855+
});
856+
}
857+
809858
/**
810859
* Subscribes to changes in a specific resource. When the resource changes on the
811860
* server, the client will receive notifications through the resources change
@@ -855,7 +904,7 @@ private NotificationHandler asyncResourcesChangeNotificationHandler(
855904
};
856905

857906
/**
858-
* Retrieves the list of all prompts provided by the server.
907+
* Retrieves the first page of prompts provided by the server.
859908
* @return A Mono that completes with the list of prompts result.
860909
* @see McpSchema.ListPromptsResult
861910
* @see #getPrompt(GetPromptRequest)
@@ -876,6 +925,24 @@ public Mono<ListPromptsResult> listPrompts(String cursor) {
876925
.sendRequest(McpSchema.METHOD_PROMPT_LIST, new PaginatedRequest(cursor), LIST_PROMPTS_RESULT_TYPE_REF));
877926
}
878927

928+
/**
929+
* Retrieves the list of all prompts provided by the server.
930+
* @return A Mono that completes with the list of all prompts.
931+
* @see McpSchema.Prompt
932+
* @see #getPrompt(GetPromptRequest)
933+
*/
934+
public Mono<List<McpSchema.Prompt>> listAllPrompts() {
935+
return this.listPrompts().expand(result -> {
936+
if (result.nextCursor() != null) {
937+
return this.listPrompts(result.nextCursor());
938+
}
939+
return Mono.empty();
940+
}).reduce(new ArrayList<>(), (allPrompts, result) -> {
941+
allPrompts.addAll(result.prompts());
942+
return allPrompts;
943+
});
944+
}
945+
879946
/**
880947
* Retrieves a specific prompt by its ID. This provides the complete prompt template
881948
* including all parameters and instructions for generating AI content.

0 commit comments

Comments
 (0)