From ad9bc309b35293aad5dee2044347a13ce274a9bb Mon Sep 17 00:00:00 2001 From: Anurag Pant Date: Wed, 11 Jun 2025 17:09:28 +0000 Subject: [PATCH 1/4] fix: Support pagination in tools change handler --- .../client/McpAsyncClient.java | 14 ++++-- .../McpAsyncClientResponseHandlerTests.java | 50 ++++++++++++++----- 2 files changed, 49 insertions(+), 15 deletions(-) diff --git a/mcp/src/main/java/io/modelcontextprotocol/client/McpAsyncClient.java b/mcp/src/main/java/io/modelcontextprotocol/client/McpAsyncClient.java index 8f0433eb1..3e78a54f8 100644 --- a/mcp/src/main/java/io/modelcontextprotocol/client/McpAsyncClient.java +++ b/mcp/src/main/java/io/modelcontextprotocol/client/McpAsyncClient.java @@ -682,9 +682,17 @@ public Mono listTools(String cursor) { private NotificationHandler asyncToolsChangeNotificationHandler( List, Mono>> toolsChangeConsumers) { // TODO: params are not used yet - return params -> this.listTools() - .flatMap(listToolsResult -> Flux.fromIterable(toolsChangeConsumers) - .flatMap(consumer -> consumer.apply(listToolsResult.tools())) + return params -> this.listTools().expand(result -> { + if (result.nextCursor() != null) { + return this.listTools(result.nextCursor()); + } + return Mono.empty(); + }).reduce(new ArrayList(), (allTools, result) -> { + allTools.addAll(result.tools()); + return allTools; + }) + .flatMap(allTools -> Flux.fromIterable(toolsChangeConsumers) + .flatMap(consumer -> consumer.apply(allTools)) .onErrorResume(error -> { logger.error("Error handling tools list change notification", error); return Mono.empty(); diff --git a/mcp/src/test/java/io/modelcontextprotocol/client/McpAsyncClientResponseHandlerTests.java b/mcp/src/test/java/io/modelcontextprotocol/client/McpAsyncClientResponseHandlerTests.java index e6cde8e3b..a79bdf6c9 100644 --- a/mcp/src/test/java/io/modelcontextprotocol/client/McpAsyncClientResponseHandlerTests.java +++ b/mcp/src/test/java/io/modelcontextprotocol/client/McpAsyncClientResponseHandlerTests.java @@ -17,6 +17,7 @@ import io.modelcontextprotocol.spec.McpSchema; import io.modelcontextprotocol.spec.McpSchema.ClientCapabilities; import io.modelcontextprotocol.spec.McpSchema.InitializeResult; +import io.modelcontextprotocol.spec.McpSchema.PaginatedRequest; import io.modelcontextprotocol.spec.McpSchema.Root; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; @@ -109,27 +110,52 @@ void testToolsChangeNotificationHandling() throws JsonProcessingException { // Create a mock tools list that the server will return Map inputSchema = Map.of("type", "object", "properties", Map.of(), "required", List.of()); - McpSchema.Tool mockTool = new McpSchema.Tool("test-tool", "Test Tool Description", + McpSchema.Tool mockTool = new McpSchema.Tool("test-tool-1", "Test Tool 1 Description", new ObjectMapper().writeValueAsString(inputSchema)); - McpSchema.ListToolsResult mockToolsResult = new McpSchema.ListToolsResult(List.of(mockTool), null); + + // Create page 1 response with nextPageToken + String nextPageToken = "page2Token"; + McpSchema.ListToolsResult mockToolsResult1 = new McpSchema.ListToolsResult(List.of(mockTool), nextPageToken); // Simulate server sending tools/list_changed notification McpSchema.JSONRPCNotification notification = new McpSchema.JSONRPCNotification(McpSchema.JSONRPC_VERSION, McpSchema.METHOD_NOTIFICATION_TOOLS_LIST_CHANGED, null); transport.simulateIncomingMessage(notification); - // Simulate server response to tools/list request - McpSchema.JSONRPCRequest toolsListRequest = transport.getLastSentMessageAsRequest(); - assertThat(toolsListRequest.method()).isEqualTo(McpSchema.METHOD_TOOLS_LIST); + // Simulate server response to first tools/list request + McpSchema.JSONRPCRequest toolsListRequest1 = transport.getLastSentMessageAsRequest(); + assertThat(toolsListRequest1.method()).isEqualTo(McpSchema.METHOD_TOOLS_LIST); + + McpSchema.JSONRPCResponse toolsListResponse1 = new McpSchema.JSONRPCResponse(McpSchema.JSONRPC_VERSION, + toolsListRequest1.id(), mockToolsResult1, null); + transport.simulateIncomingMessage(toolsListResponse1); + + // Create mock tools for page 2 + McpSchema.Tool mockTool2 = new McpSchema.Tool("test-tool-2", "Test Tool 2 Description", + new ObjectMapper().writeValueAsString(inputSchema)); + + // Create page 2 response with no nextPageToken (last page) + McpSchema.ListToolsResult mockToolsResult2 = new McpSchema.ListToolsResult(List.of(mockTool2), null); + + // Simulate server response to second tools/list request with page token + McpSchema.JSONRPCRequest toolsListRequest2 = transport.getLastSentMessageAsRequest(); + assertThat(toolsListRequest2.method()).isEqualTo(McpSchema.METHOD_TOOLS_LIST); + + // Verify the page token was included in the request + PaginatedRequest params = (PaginatedRequest) toolsListRequest2.params(); + assertThat(params).isNotNull(); + assertThat(params.cursor()).isEqualTo(nextPageToken); - McpSchema.JSONRPCResponse toolsListResponse = new McpSchema.JSONRPCResponse(McpSchema.JSONRPC_VERSION, - toolsListRequest.id(), mockToolsResult, null); - transport.simulateIncomingMessage(toolsListResponse); + McpSchema.JSONRPCResponse toolsListResponse2 = new McpSchema.JSONRPCResponse(McpSchema.JSONRPC_VERSION, + toolsListRequest2.id(), mockToolsResult2, null); + transport.simulateIncomingMessage(toolsListResponse2); - // Verify the consumer received the expected tools - assertThat(receivedTools).hasSize(1); - assertThat(receivedTools.get(0).name()).isEqualTo("test-tool"); - assertThat(receivedTools.get(0).description()).isEqualTo("Test Tool Description"); + // Verify the consumer received all expected tools from both pages + assertThat(receivedTools).hasSize(2); + assertThat(receivedTools.get(0).name()).isEqualTo("test-tool-1"); + assertThat(receivedTools.get(0).description()).isEqualTo("Test Tool 1 Description"); + assertThat(receivedTools.get(1).name()).isEqualTo("test-tool-2"); + assertThat(receivedTools.get(1).description()).isEqualTo("Test Tool 2 Description"); asyncMcpClient.closeGracefully(); } From 86a957935b6c5cd20823a1fcc94e3bc4cffa349a Mon Sep 17 00:00:00 2001 From: Anurag Pant Date: Wed, 18 Jun 2025 07:31:42 -0700 Subject: [PATCH 2/4] Add client side list handlers to handle pagination Add client side list handlers to handle pagination for prompts, resources and resource templates. --- .../client/AbstractMcpAsyncClientTests.java | 68 +++++++++++++ .../client/AbstractMcpSyncClientTests.java | 48 ++++++++++ .../client/McpAsyncClient.java | 95 ++++++++++++++++--- .../client/McpSyncClient.java | 82 ++++++++++++---- .../client/AbstractMcpAsyncClientTests.java | 92 +++++++++++++++--- .../client/AbstractMcpSyncClientTests.java | 49 ++++++++++ 6 files changed, 389 insertions(+), 45 deletions(-) diff --git a/mcp-test/src/main/java/io/modelcontextprotocol/client/AbstractMcpAsyncClientTests.java b/mcp-test/src/main/java/io/modelcontextprotocol/client/AbstractMcpAsyncClientTests.java index d08bf7065..e107e9b71 100644 --- a/mcp-test/src/main/java/io/modelcontextprotocol/client/AbstractMcpAsyncClientTests.java +++ b/mcp-test/src/main/java/io/modelcontextprotocol/client/AbstractMcpAsyncClientTests.java @@ -162,6 +162,23 @@ void testListTools() { }); } + @Test + void testListAllTools() { + withClient(createMcpTransport(), mcpAsyncClient -> { + StepVerifier.create(mcpAsyncClient.initialize().then(mcpAsyncClient.listAllTools())) + .consumeNextWith(tools -> { + assertThat(tools).isNotNull().satisfies(result -> { + assertThat(result).isNotEmpty(); + + Tool firstTool = result.get(0); + assertThat(firstTool.name()).isNotNull(); + assertThat(firstTool.description()).isNotNull(); + }); + }) + .verifyComplete(); + }); + } + @Test void testPingWithoutInitialization() { verifyCallSucceedsWithImplicitInitialization(client -> client.ping(), "pinging the server"); @@ -293,6 +310,23 @@ void testListResources() { }); } + @Test + void testListAllResources() { + withClient(createMcpTransport(), mcpAsyncClient -> { + StepVerifier.create(mcpAsyncClient.initialize().then(mcpAsyncClient.listAllResources())) + .consumeNextWith(resources -> { + assertThat(resources).isNotNull().satisfies(result -> { + assertThat(result).isNotEmpty(); + + Resource firstResource = result.get(0); + assertThat(firstResource.uri()).isNotNull(); + assertThat(firstResource.name()).isNotNull(); + }); + }) + .verifyComplete(); + }); + } + @Test void testMcpAsyncClientState() { withClient(createMcpTransport(), mcpAsyncClient -> { @@ -324,6 +358,23 @@ void testListPrompts() { }); } + @Test + void testListAllPrompts() { + withClient(createMcpTransport(), mcpAsyncClient -> { + StepVerifier.create(mcpAsyncClient.initialize().then(mcpAsyncClient.listAllPrompts())) + .consumeNextWith(prompts -> { + assertThat(prompts).isNotNull().satisfies(result -> { + assertThat(result).isNotEmpty(); + + Prompt firstPrompt = result.get(0); + assertThat(firstPrompt.name()).isNotNull(); + assertThat(firstPrompt.description()).isNotNull(); + }); + }) + .verifyComplete(); + }); + } + @Test void testGetPromptWithoutInitialization() { GetPromptRequest request = new GetPromptRequest("simple_prompt", Map.of()); @@ -439,6 +490,23 @@ void testListResourceTemplates() { }); } + @Test + void testListAllResourceTemplates() { + withClient(createMcpTransport(), mcpAsyncClient -> { + StepVerifier.create(mcpAsyncClient.initialize().then(mcpAsyncClient.listAllResourceTemplates())) + .consumeNextWith(resourceTemplates -> { + assertThat(resourceTemplates).isNotNull().satisfies(result -> { + assertThat(result).isNotEmpty(); + + McpSchema.ResourceTemplate firstResourceTemplate = result.get(0); + assertThat(firstResourceTemplate.name()).isNotNull(); + assertThat(firstResourceTemplate.description()).isNotNull(); + }); + }) + .verifyComplete(); + }); + } + // @Test void testResourceSubscription() { withClient(createMcpTransport(), mcpAsyncClient -> { diff --git a/mcp-test/src/main/java/io/modelcontextprotocol/client/AbstractMcpSyncClientTests.java b/mcp-test/src/main/java/io/modelcontextprotocol/client/AbstractMcpSyncClientTests.java index d94929662..cbddc222c 100644 --- a/mcp-test/src/main/java/io/modelcontextprotocol/client/AbstractMcpSyncClientTests.java +++ b/mcp-test/src/main/java/io/modelcontextprotocol/client/AbstractMcpSyncClientTests.java @@ -161,6 +161,22 @@ void testListTools() { }); } + @Test + void testListAllTools() { + withClient(createMcpTransport(), mcpSyncClient -> { + mcpSyncClient.initialize(); + List tools = mcpSyncClient.listAllTools(); + + assertThat(tools).isNotNull().satisfies(result -> { + assertThat(result).isNotEmpty(); + + Tool firstTool = result.get(0); + assertThat(firstTool.name()).isNotNull(); + assertThat(firstTool.description()).isNotNull(); + }); + }); + } + @Test void testCallToolsWithoutInitialization() { verifyCallSucceedsWithImplicitInitialization( @@ -320,6 +336,22 @@ void testListResources() { }); } + @Test + void testListAllResources() { + withClient(createMcpTransport(), mcpSyncClient -> { + mcpSyncClient.initialize(); + List resources = mcpSyncClient.listAllResources(); + + assertThat(resources).isNotNull().satisfies(result -> { + assertThat(result).isNotEmpty(); + + Resource firstResource = result.get(0); + assertThat(firstResource.uri()).isNotNull(); + assertThat(firstResource.name()).isNotNull(); + }); + }); + } + @Test void testClientSessionState() { withClient(createMcpTransport(), mcpSyncClient -> { @@ -418,6 +450,22 @@ void testListResourceTemplates() { }); } + @Test + void testListAllResourceTemplates() { + withClient(createMcpTransport(), mcpSyncClient -> { + mcpSyncClient.initialize(); + List resourceTemplates = mcpSyncClient.listAllResourceTemplates(); + + assertThat(resourceTemplates).isNotNull().satisfies(result -> { + assertThat(result).isNotEmpty(); + + McpSchema.ResourceTemplate firstResourceTemplate = result.get(0); + assertThat(firstResourceTemplate.name()).isNotNull(); + assertThat(firstResourceTemplate.description()).isNotNull(); + }); + }); + } + // @Test void testResourceSubscription() { withClient(createMcpTransport(), mcpSyncClient -> { diff --git a/mcp/src/main/java/io/modelcontextprotocol/client/McpAsyncClient.java b/mcp/src/main/java/io/modelcontextprotocol/client/McpAsyncClient.java index 3e78a54f8..d9fed34e5 100644 --- a/mcp/src/main/java/io/modelcontextprotocol/client/McpAsyncClient.java +++ b/mcp/src/main/java/io/modelcontextprotocol/client/McpAsyncClient.java @@ -13,7 +13,11 @@ import java.util.function.Function; import java.util.function.Supplier; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import com.fasterxml.jackson.core.type.TypeReference; + import io.modelcontextprotocol.spec.McpClientSession; import io.modelcontextprotocol.spec.McpClientSession.NotificationHandler; import io.modelcontextprotocol.spec.McpClientSession.RequestHandler; @@ -35,8 +39,6 @@ import io.modelcontextprotocol.spec.McpTransportSessionNotFoundException; import io.modelcontextprotocol.util.Assert; import io.modelcontextprotocol.util.Utils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import reactor.core.publisher.Sinks; @@ -656,7 +658,7 @@ public Mono callTool(McpSchema.CallToolRequest callToo } /** - * Retrieves the list of all tools provided by the server. + * Retrieves the first page of tools provided by the server. * @return A Mono that emits the list of tools result. */ public Mono listTools() { @@ -679,18 +681,26 @@ public Mono listTools(String cursor) { }); } - private NotificationHandler asyncToolsChangeNotificationHandler( - List, Mono>> toolsChangeConsumers) { - // TODO: params are not used yet - return params -> this.listTools().expand(result -> { + /** + * Retrieves list of all tools provided by the server after handling pagination. + * @return A Mono that emits a list of all tools + */ + public Mono> listAllTools() { + return this.listTools().expand(result -> { if (result.nextCursor() != null) { return this.listTools(result.nextCursor()); } return Mono.empty(); - }).reduce(new ArrayList(), (allTools, result) -> { + }).reduce(new ArrayList<>(), (allTools, result) -> { allTools.addAll(result.tools()); return allTools; - }) + }); + } + + private NotificationHandler asyncToolsChangeNotificationHandler( + List, Mono>> toolsChangeConsumers) { + // TODO: params are not used yet + return params -> this.listAllTools() .flatMap(allTools -> Flux.fromIterable(toolsChangeConsumers) .flatMap(consumer -> consumer.apply(allTools)) .onErrorResume(error -> { @@ -714,9 +724,9 @@ private NotificationHandler asyncToolsChangeNotificationHandler( }; /** - * Retrieves the list of all resources provided by the server. Resources represent any - * kind of UTF-8 encoded data that an MCP server makes available to clients, such as - * database records, API responses, log files, and more. + * Retrieves the first page of resources provided by the server. Resources represent + * any kind of UTF-8 encoded data that an MCP server makes available to clients, such + * as database records, API responses, log files, and more. * @return A Mono that completes with the list of resources result. * @see McpSchema.ListResourcesResult * @see #readResource(McpSchema.Resource) @@ -745,6 +755,26 @@ public Mono listResources(String cursor) { }); } + /** + * Retrieves the list of all resources provided by the server. Resources represent any + * kind of UTF-8 encoded data that an MCP server makes available to clients, such as + * database records, API responses, log files, and more. + * @return A Mono that completes with list of all resources. + * @see McpSchema.Resource + * @see #readResource(McpSchema.Resource) + */ + public Mono> listAllResources() { + return this.listResources().expand(result -> { + if (result.nextCursor() != null) { + return this.listResources(result.nextCursor()); + } + return Mono.empty(); + }).reduce(new ArrayList<>(), (allResources, result) -> { + allResources.addAll(result.resources()); + return allResources; + }); + } + /** * Reads the content of a specific resource identified by the provided Resource * object. This method fetches the actual data that the resource represents. @@ -777,7 +807,7 @@ public Mono readResource(McpSchema.ReadResourceReq } /** - * Retrieves the list of all resource templates provided by the server. Resource + * Retrieves the first page of resource templates provided by the server. Resource * templates allow servers to expose parameterized resources using URI templates, * enabling dynamic resource access based on variable parameters. * @return A Mono that completes with the list of resource templates result. @@ -806,6 +836,25 @@ public Mono listResourceTemplates(String }); } + /** + * Retrieves the list of all resource templates provided by the server. Resource + * templates allow servers to expose parameterized resources using URI templates, + * enabling dynamic resource access based on variable parameters. + * @return A Mono that completes with the list of all resource templates. + * @see McpSchema.ResourceTemplate + */ + public Mono> listAllResourceTemplates() { + return this.listResourceTemplates().expand(result -> { + if (result.nextCursor() != null) { + return this.listResourceTemplates(result.nextCursor()); + } + return Mono.empty(); + }).reduce(new ArrayList<>(), (allResourceTemplates, result) -> { + allResourceTemplates.addAll(result.resourceTemplates()); + return allResourceTemplates; + }); + } + /** * Subscribes to changes in a specific resource. When the resource changes on the * server, the client will receive notifications through the resources change @@ -855,7 +904,7 @@ private NotificationHandler asyncResourcesChangeNotificationHandler( }; /** - * Retrieves the list of all prompts provided by the server. + * Retrieves the first page of prompts provided by the server. * @return A Mono that completes with the list of prompts result. * @see McpSchema.ListPromptsResult * @see #getPrompt(GetPromptRequest) @@ -876,6 +925,24 @@ public Mono listPrompts(String cursor) { .sendRequest(McpSchema.METHOD_PROMPT_LIST, new PaginatedRequest(cursor), LIST_PROMPTS_RESULT_TYPE_REF)); } + /** + * Retrieves the list of all prompts provided by the server. + * @return A Mono that completes with the list of all prompts. + * @see McpSchema.Prompt + * @see #getPrompt(GetPromptRequest) + */ + public Mono> listAllPrompts() { + return this.listPrompts().expand(result -> { + if (result.nextCursor() != null) { + return this.listPrompts(result.nextCursor()); + } + return Mono.empty(); + }).reduce(new ArrayList<>(), (allPrompts, result) -> { + allPrompts.addAll(result.prompts()); + return allPrompts; + }); + } + /** * Retrieves a specific prompt by its ID. This provides the complete prompt template * including all parameters and instructions for generating AI content. diff --git a/mcp/src/main/java/io/modelcontextprotocol/client/McpSyncClient.java b/mcp/src/main/java/io/modelcontextprotocol/client/McpSyncClient.java index a8fb979e1..5fc08ca3e 100644 --- a/mcp/src/main/java/io/modelcontextprotocol/client/McpSyncClient.java +++ b/mcp/src/main/java/io/modelcontextprotocol/client/McpSyncClient.java @@ -5,6 +5,10 @@ package io.modelcontextprotocol.client; import java.time.Duration; +import java.util.List; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import io.modelcontextprotocol.spec.McpSchema; import io.modelcontextprotocol.spec.McpSchema.ClientCapabilities; @@ -12,8 +16,6 @@ import io.modelcontextprotocol.spec.McpSchema.GetPromptResult; import io.modelcontextprotocol.spec.McpSchema.ListPromptsResult; import io.modelcontextprotocol.util.Assert; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** * A synchronous client implementation for the Model Context Protocol (MCP) that wraps an @@ -219,7 +221,7 @@ public McpSchema.CallToolResult callTool(McpSchema.CallToolRequest callToolReque } /** - * Retrieves the list of all tools provided by the server. + * Retrieves the first page of tools provided by the server. * @return The list of tools result containing: - tools: List of available tools, each * with a name, description, and input schema - nextCursor: Optional cursor for * pagination if more tools are available @@ -239,25 +241,41 @@ public McpSchema.ListToolsResult listTools(String cursor) { return this.delegate.listTools(cursor).block(); } + /** + * Retrieves the list of all tools provided by the server. + * @return The list of all tools + */ + public List listAllTools() { + return this.delegate.listAllTools().block(); + } + // -------------------------- // Resources // -------------------------- /** - * Send a resources/list request. - * @param cursor the cursor - * @return the list of resources result. + * Retrieves the first page of resources provided by the server. + * @return The list of resources result + */ + public McpSchema.ListResourcesResult listResources() { + return this.delegate.listResources().block(); + } + + /** + * Retrieves a paginated list of resources provided by the server. + * @param cursor Optional pagination cursor from a previous list request + * @return The list of resources result */ public McpSchema.ListResourcesResult listResources(String cursor) { return this.delegate.listResources(cursor).block(); } /** - * Send a resources/list request. - * @return the list of resources result. + * Retrieves the list of all resources provided by the server. + * @return The list of all resources */ - public McpSchema.ListResourcesResult listResources() { - return this.delegate.listResources().block(); + public List listAllResources() { + return this.delegate.listAllResources().block(); } /** @@ -278,24 +296,32 @@ public McpSchema.ReadResourceResult readResource(McpSchema.ReadResourceRequest r return this.delegate.readResource(readResourceRequest).block(); } + /** + * Retrieves the first page of resource templates provided by the server. + * @return The list of resource templates result. + */ + public McpSchema.ListResourceTemplatesResult listResourceTemplates() { + return this.delegate.listResourceTemplates().block(); + } + /** * Resource templates allow servers to expose parameterized resources using URI * templates. Arguments may be auto-completed through the completion API. * - * Request a list of resource templates the server has. - * @param cursor the cursor - * @return the list of resource templates result. + * Retrieves a paginated list of resource templates provided by the server. + * @param cursor Optional pagination cursor from a previous list request + * @return The list of resource templates result. */ public McpSchema.ListResourceTemplatesResult listResourceTemplates(String cursor) { return this.delegate.listResourceTemplates(cursor).block(); } /** - * Request a list of resource templates the server has. - * @return the list of resource templates result. + * Retrieves the list of all resources provided by the server. + * @return The list of all resource templates */ - public McpSchema.ListResourceTemplatesResult listResourceTemplates() { - return this.delegate.listResourceTemplates().block(); + public List listAllResourceTemplates() { + return this.delegate.listAllResourceTemplates().block(); } /** @@ -323,12 +349,30 @@ public void unsubscribeResource(McpSchema.UnsubscribeRequest unsubscribeRequest) // -------------------------- // Prompts // -------------------------- + + /** + * Retrieves the first page of prompts provided by the server. + * @return The list of prompts result. + */ + public ListPromptsResult listPrompts() { + return this.delegate.listPrompts().block(); + } + + /** + * Retrieves a paginated list of prompts provided by the server. + * @param cursor Optional pagination cursor from a previous list request + * @return The list of prompts result. + */ public ListPromptsResult listPrompts(String cursor) { return this.delegate.listPrompts(cursor).block(); } - public ListPromptsResult listPrompts() { - return this.delegate.listPrompts().block(); + /** + * Retrieves the list of all prompts provided by the server. + * @return The list of all prompts + */ + public List listAllPrompts() { + return this.delegate.listAllPrompts().block(); } public GetPromptResult getPrompt(GetPromptRequest getPromptRequest) { diff --git a/mcp/src/test/java/io/modelcontextprotocol/client/AbstractMcpAsyncClientTests.java b/mcp/src/test/java/io/modelcontextprotocol/client/AbstractMcpAsyncClientTests.java index af130bec0..99090274a 100644 --- a/mcp/src/test/java/io/modelcontextprotocol/client/AbstractMcpAsyncClientTests.java +++ b/mcp/src/test/java/io/modelcontextprotocol/client/AbstractMcpAsyncClientTests.java @@ -13,6 +13,18 @@ import java.util.function.Consumer; import java.util.function.Function; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.Assertions.fail; +import org.junit.jupiter.api.AfterEach; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + import io.modelcontextprotocol.spec.McpClientTransport; import io.modelcontextprotocol.spec.McpError; import io.modelcontextprotocol.spec.McpSchema; @@ -30,22 +42,10 @@ import io.modelcontextprotocol.spec.McpSchema.Tool; import io.modelcontextprotocol.spec.McpSchema.UnsubscribeRequest; import io.modelcontextprotocol.spec.McpTransport; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.ValueSource; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import reactor.test.StepVerifier; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatCode; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.assertj.core.api.Assertions.fail; -import static org.junit.jupiter.api.Assertions.assertInstanceOf; - /** * Test suite for the {@link McpAsyncClient} that can be used with different * {@link McpTransport} implementations. @@ -163,6 +163,23 @@ void testListTools() { }); } + @Test + void testListAllTools() { + withClient(createMcpTransport(), mcpAsyncClient -> { + StepVerifier.create(mcpAsyncClient.initialize().then(mcpAsyncClient.listAllTools())) + .consumeNextWith(tools -> { + assertThat(tools).isNotNull().satisfies(result -> { + assertThat(result).isNotEmpty(); + + Tool firstTool = result.get(0); + assertThat(firstTool.name()).isNotNull(); + assertThat(firstTool.description()).isNotNull(); + }); + }) + .verifyComplete(); + }); + } + @Test void testPingWithoutInitialization() { verifyCallSucceedsWithImplicitInitialization(client -> client.ping(), "pinging the server"); @@ -294,6 +311,23 @@ void testListResources() { }); } + @Test + void testListAllResources() { + withClient(createMcpTransport(), mcpAsyncClient -> { + StepVerifier.create(mcpAsyncClient.initialize().then(mcpAsyncClient.listAllResources())) + .consumeNextWith(resources -> { + assertThat(resources).isNotNull().satisfies(result -> { + assertThat(result).isNotEmpty(); + + Resource firstResource = result.get(0); + assertThat(firstResource.uri()).isNotNull(); + assertThat(firstResource.name()).isNotNull(); + }); + }) + .verifyComplete(); + }); + } + @Test void testMcpAsyncClientState() { withClient(createMcpTransport(), mcpAsyncClient -> { @@ -325,6 +359,23 @@ void testListPrompts() { }); } + @Test + void testListAllPrompts() { + withClient(createMcpTransport(), mcpAsyncClient -> { + StepVerifier.create(mcpAsyncClient.initialize().then(mcpAsyncClient.listAllPrompts())) + .consumeNextWith(prompts -> { + assertThat(prompts).isNotNull().satisfies(result -> { + assertThat(result).isNotEmpty(); + + Prompt firstPrompt = result.get(0); + assertThat(firstPrompt.name()).isNotNull(); + assertThat(firstPrompt.description()).isNotNull(); + }); + }) + .verifyComplete(); + }); + } + @Test void testGetPromptWithoutInitialization() { GetPromptRequest request = new GetPromptRequest("simple_prompt", Map.of()); @@ -440,6 +491,23 @@ void testListResourceTemplates() { }); } + @Test + void testListAllResourceTemplates() { + withClient(createMcpTransport(), mcpAsyncClient -> { + StepVerifier.create(mcpAsyncClient.initialize().then(mcpAsyncClient.listAllResourceTemplates())) + .consumeNextWith(resourceTemplates -> { + assertThat(resourceTemplates).isNotNull().satisfies(result -> { + assertThat(result).isNotEmpty(); + + McpSchema.ResourceTemplate firstResourceTemplate = result.get(0); + assertThat(firstResourceTemplate.name()).isNotNull(); + assertThat(firstResourceTemplate.description()).isNotNull(); + }); + }) + .verifyComplete(); + }); + } + // @Test void testResourceSubscription() { withClient(createMcpTransport(), mcpAsyncClient -> { diff --git a/mcp/src/test/java/io/modelcontextprotocol/client/AbstractMcpSyncClientTests.java b/mcp/src/test/java/io/modelcontextprotocol/client/AbstractMcpSyncClientTests.java index 10ecadec9..bc700a3c7 100644 --- a/mcp/src/test/java/io/modelcontextprotocol/client/AbstractMcpSyncClientTests.java +++ b/mcp/src/test/java/io/modelcontextprotocol/client/AbstractMcpSyncClientTests.java @@ -42,6 +42,7 @@ import static org.assertj.core.api.Assertions.assertThatCode; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.assertj.core.api.Assertions.fail; +import static org.junit.Assert.assertThat; import static org.junit.jupiter.api.Assertions.assertInstanceOf; /** @@ -164,6 +165,22 @@ void testListTools() { }); } + @Test + void testListAllTools() { + withClient(createMcpTransport(), mcpSyncClient -> { + mcpSyncClient.initialize(); + List tools = mcpSyncClient.listAllTools(); + + assertThat(tools).isNotNull().satisfies(result -> { + assertThat(result).isNotEmpty(); + + Tool firstTool = result.get(0); + assertThat(firstTool.name()).isNotNull(); + assertThat(firstTool.description()).isNotNull(); + }); + }); + } + @Test void testCallToolsWithoutInitialization() { verifyCallSucceedsWithImplicitInitialization( @@ -323,6 +340,22 @@ void testListResources() { }); } + @Test + void testListAllResources() { + withClient(createMcpTransport(), mcpSyncClient -> { + mcpSyncClient.initialize(); + List resources = mcpSyncClient.listAllResources(); + + assertThat(resources).isNotNull().satisfies(result -> { + assertThat(result).isNotEmpty(); + + Resource firstResource = result.get(0); + assertThat(firstResource.uri()).isNotNull(); + assertThat(firstResource.name()).isNotNull(); + }); + }); + } + @Test void testClientSessionState() { withClient(createMcpTransport(), mcpSyncClient -> { @@ -421,6 +454,22 @@ void testListResourceTemplates() { }); } + @Test + void testListAllResourceTemplates() { + withClient(createMcpTransport(), mcpSyncClient -> { + mcpSyncClient.initialize(); + List resourceTemplates = mcpSyncClient.listAllResourceTemplates(); + + assertThat(resourceTemplates).isNotNull().satisfies(result -> { + assertThat(result).isNotEmpty(); + + McpSchema.ResourceTemplate firstResourceTemplate = result.get(0); + assertThat(firstResourceTemplate.name()).isNotNull(); + assertThat(firstResourceTemplate.description()).isNotNull(); + }); + }); + } + // @Test void testResourceSubscription() { withClient(createMcpTransport(), mcpSyncClient -> { From d02d500a3a298c8d277148d1453f7a0dead7691c Mon Sep 17 00:00:00 2001 From: Anurag Pant Date: Mon, 23 Jun 2025 16:07:14 -0700 Subject: [PATCH 3/4] Repurposed existing list handlers to deal with pagination --- .../client/AbstractMcpAsyncClientTests.java | 72 ++++----- .../client/AbstractMcpSyncClientTests.java | 42 +++--- .../client/McpAsyncClient.java | 141 +++++++----------- .../client/McpSyncClient.java | 50 ++----- .../modelcontextprotocol/spec/McpSchema.java | 2 + .../client/AbstractMcpAsyncClientTests.java | 79 +++++----- .../client/AbstractMcpSyncClientTests.java | 42 +++--- 7 files changed, 175 insertions(+), 253 deletions(-) diff --git a/mcp-test/src/main/java/io/modelcontextprotocol/client/AbstractMcpAsyncClientTests.java b/mcp-test/src/main/java/io/modelcontextprotocol/client/AbstractMcpAsyncClientTests.java index 9e9844a56..4f7d5678b 100644 --- a/mcp-test/src/main/java/io/modelcontextprotocol/client/AbstractMcpAsyncClientTests.java +++ b/mcp-test/src/main/java/io/modelcontextprotocol/client/AbstractMcpAsyncClientTests.java @@ -149,13 +149,13 @@ void testConstructorWithInvalidArguments() { @Test void testListToolsWithoutInitialization() { - verifyCallSucceedsWithImplicitInitialization(client -> client.listTools(null), "listing tools"); + verifyCallSucceedsWithImplicitInitialization(client -> client.listTools(McpSchema.FIRST_PAGE), "listing tools"); } @Test void testListTools() { withClient(createMcpTransport(), mcpAsyncClient -> { - StepVerifier.create(mcpAsyncClient.initialize().then(mcpAsyncClient.listTools(null))) + StepVerifier.create(mcpAsyncClient.initialize().then(mcpAsyncClient.listTools(McpSchema.FIRST_PAGE))) .consumeNextWith(result -> { assertThat(result.tools()).isNotNull().isNotEmpty(); @@ -170,15 +170,13 @@ void testListTools() { @Test void testListAllTools() { withClient(createMcpTransport(), mcpAsyncClient -> { - StepVerifier.create(mcpAsyncClient.initialize().then(mcpAsyncClient.listAllTools())) - .consumeNextWith(tools -> { - assertThat(tools).isNotNull().satisfies(result -> { - assertThat(result).isNotEmpty(); - - Tool firstTool = result.get(0); - assertThat(firstTool.name()).isNotNull(); - assertThat(firstTool.description()).isNotNull(); - }); + StepVerifier.create(mcpAsyncClient.initialize().then(mcpAsyncClient.listTools())) + .consumeNextWith(result -> { + assertThat(result.tools()).isNotNull().isNotEmpty(); + + Tool firstTool = result.tools().get(0); + assertThat(firstTool.name()).isNotNull(); + assertThat(firstTool.description()).isNotNull(); }) .verifyComplete(); }); @@ -293,13 +291,14 @@ void testCallToolWithMessageAnnotations(String messageType) { @Test void testListResourcesWithoutInitialization() { - verifyCallSucceedsWithImplicitInitialization(client -> client.listResources(null), "listing resources"); + verifyCallSucceedsWithImplicitInitialization(client -> client.listResources(McpSchema.FIRST_PAGE), + "listing resources"); } @Test void testListResources() { withClient(createMcpTransport(), mcpAsyncClient -> { - StepVerifier.create(mcpAsyncClient.initialize().then(mcpAsyncClient.listResources(null))) + StepVerifier.create(mcpAsyncClient.initialize().then(mcpAsyncClient.listResources(McpSchema.FIRST_PAGE))) .consumeNextWith(resources -> { assertThat(resources).isNotNull().satisfies(result -> { assertThat(result.resources()).isNotNull(); @@ -318,14 +317,16 @@ void testListResources() { @Test void testListAllResources() { withClient(createMcpTransport(), mcpAsyncClient -> { - StepVerifier.create(mcpAsyncClient.initialize().then(mcpAsyncClient.listAllResources())) + StepVerifier.create(mcpAsyncClient.initialize().then(mcpAsyncClient.listResources())) .consumeNextWith(resources -> { assertThat(resources).isNotNull().satisfies(result -> { - assertThat(result).isNotEmpty(); + assertThat(result.resources()).isNotNull(); - Resource firstResource = result.get(0); - assertThat(firstResource.uri()).isNotNull(); - assertThat(firstResource.name()).isNotNull(); + if (!result.resources().isEmpty()) { + Resource firstResource = result.resources().get(0); + assertThat(firstResource.uri()).isNotNull(); + assertThat(firstResource.name()).isNotNull(); + } }); }) .verifyComplete(); @@ -341,13 +342,14 @@ void testMcpAsyncClientState() { @Test void testListPromptsWithoutInitialization() { - verifyCallSucceedsWithImplicitInitialization(client -> client.listPrompts(null), "listing " + "prompts"); + verifyCallSucceedsWithImplicitInitialization(client -> client.listPrompts(McpSchema.FIRST_PAGE), + "listing " + "prompts"); } @Test void testListPrompts() { withClient(createMcpTransport(), mcpAsyncClient -> { - StepVerifier.create(mcpAsyncClient.initialize().then(mcpAsyncClient.listPrompts(null))) + StepVerifier.create(mcpAsyncClient.initialize().then(mcpAsyncClient.listPrompts(McpSchema.FIRST_PAGE))) .consumeNextWith(prompts -> { assertThat(prompts).isNotNull().satisfies(result -> { assertThat(result.prompts()).isNotNull(); @@ -366,14 +368,16 @@ void testListPrompts() { @Test void testListAllPrompts() { withClient(createMcpTransport(), mcpAsyncClient -> { - StepVerifier.create(mcpAsyncClient.initialize().then(mcpAsyncClient.listAllPrompts())) + StepVerifier.create(mcpAsyncClient.initialize().then(mcpAsyncClient.listPrompts())) .consumeNextWith(prompts -> { assertThat(prompts).isNotNull().satisfies(result -> { - assertThat(result).isNotEmpty(); + assertThat(result.prompts()).isNotNull(); - Prompt firstPrompt = result.get(0); - assertThat(firstPrompt.name()).isNotNull(); - assertThat(firstPrompt.description()).isNotNull(); + if (!result.prompts().isEmpty()) { + Prompt firstPrompt = result.prompts().get(0); + assertThat(firstPrompt.name()).isNotNull(); + assertThat(firstPrompt.description()).isNotNull(); + } }); }) .verifyComplete(); @@ -520,14 +524,15 @@ else if (content instanceof BlobResourceContents blobContent) { @Test void testListResourceTemplatesWithoutInitialization() { - verifyCallSucceedsWithImplicitInitialization(client -> client.listResourceTemplates(), + verifyCallSucceedsWithImplicitInitialization(client -> client.listResourceTemplates(McpSchema.FIRST_PAGE), "listing resource templates"); } @Test void testListResourceTemplates() { withClient(createMcpTransport(), mcpAsyncClient -> { - StepVerifier.create(mcpAsyncClient.initialize().then(mcpAsyncClient.listResourceTemplates())) + StepVerifier + .create(mcpAsyncClient.initialize().then(mcpAsyncClient.listResourceTemplates(McpSchema.FIRST_PAGE))) .consumeNextWith(result -> { assertThat(result).isNotNull(); assertThat(result.resourceTemplates()).isNotNull(); @@ -539,15 +544,10 @@ void testListResourceTemplates() { @Test void testListAllResourceTemplates() { withClient(createMcpTransport(), mcpAsyncClient -> { - StepVerifier.create(mcpAsyncClient.initialize().then(mcpAsyncClient.listAllResourceTemplates())) - .consumeNextWith(resourceTemplates -> { - assertThat(resourceTemplates).isNotNull().satisfies(result -> { - assertThat(result).isNotEmpty(); - - McpSchema.ResourceTemplate firstResourceTemplate = result.get(0); - assertThat(firstResourceTemplate.name()).isNotNull(); - assertThat(firstResourceTemplate.description()).isNotNull(); - }); + StepVerifier.create(mcpAsyncClient.initialize().then(mcpAsyncClient.listResourceTemplates())) + .consumeNextWith(result -> { + assertThat(result).isNotNull(); + assertThat(result.resourceTemplates()).isNotNull(); }) .verifyComplete(); }); diff --git a/mcp-test/src/main/java/io/modelcontextprotocol/client/AbstractMcpSyncClientTests.java b/mcp-test/src/main/java/io/modelcontextprotocol/client/AbstractMcpSyncClientTests.java index 7e6a9f3be..7736c233c 100644 --- a/mcp-test/src/main/java/io/modelcontextprotocol/client/AbstractMcpSyncClientTests.java +++ b/mcp-test/src/main/java/io/modelcontextprotocol/client/AbstractMcpSyncClientTests.java @@ -151,14 +151,14 @@ void testConstructorWithInvalidArguments() { @Test void testListToolsWithoutInitialization() { - verifyCallSucceedsWithImplicitInitialization(client -> client.listTools(null), "listing tools"); + verifyCallSucceedsWithImplicitInitialization(client -> client.listTools(McpSchema.FIRST_PAGE), "listing tools"); } @Test void testListTools() { withClient(createMcpTransport(), mcpSyncClient -> { mcpSyncClient.initialize(); - ListToolsResult tools = mcpSyncClient.listTools(null); + ListToolsResult tools = mcpSyncClient.listTools(McpSchema.FIRST_PAGE); assertThat(tools).isNotNull().satisfies(result -> { assertThat(result.tools()).isNotNull().isNotEmpty(); @@ -174,12 +174,12 @@ void testListTools() { void testListAllTools() { withClient(createMcpTransport(), mcpSyncClient -> { mcpSyncClient.initialize(); - List tools = mcpSyncClient.listAllTools(); + ListToolsResult tools = mcpSyncClient.listTools(); assertThat(tools).isNotNull().satisfies(result -> { - assertThat(result).isNotEmpty(); + assertThat(result.tools()).isNotNull().isNotEmpty(); - Tool firstTool = result.get(0); + Tool firstTool = result.tools().get(0); assertThat(firstTool.name()).isNotNull(); assertThat(firstTool.description()).isNotNull(); }); @@ -324,14 +324,15 @@ void testRootsListChanged() { @Test void testListResourcesWithoutInitialization() { - verifyCallSucceedsWithImplicitInitialization(client -> client.listResources(null), "listing resources"); + verifyCallSucceedsWithImplicitInitialization(client -> client.listResources(McpSchema.FIRST_PAGE), + "listing resources"); } @Test void testListResources() { withClient(createMcpTransport(), mcpSyncClient -> { mcpSyncClient.initialize(); - ListResourcesResult resources = mcpSyncClient.listResources(null); + ListResourcesResult resources = mcpSyncClient.listResources(McpSchema.FIRST_PAGE); assertThat(resources).isNotNull().satisfies(result -> { assertThat(result.resources()).isNotNull(); @@ -349,14 +350,16 @@ void testListResources() { void testListAllResources() { withClient(createMcpTransport(), mcpSyncClient -> { mcpSyncClient.initialize(); - List resources = mcpSyncClient.listAllResources(); + ListResourcesResult resources = mcpSyncClient.listResources(); assertThat(resources).isNotNull().satisfies(result -> { - assertThat(result).isNotEmpty(); + assertThat(result.resources()).isNotNull(); - Resource firstResource = result.get(0); - assertThat(firstResource.uri()).isNotNull(); - assertThat(firstResource.name()).isNotNull(); + if (!result.resources().isEmpty()) { + Resource firstResource = result.resources().get(0); + assertThat(firstResource.uri()).isNotNull(); + assertThat(firstResource.name()).isNotNull(); + } }); }); } @@ -498,7 +501,7 @@ else if (content instanceof BlobResourceContents blobContent) { @Test void testListResourceTemplatesWithoutInitialization() { - verifyCallSucceedsWithImplicitInitialization(client -> client.listResourceTemplates(null), + verifyCallSucceedsWithImplicitInitialization(client -> client.listResourceTemplates(McpSchema.FIRST_PAGE), "listing resource templates"); } @@ -506,7 +509,7 @@ void testListResourceTemplatesWithoutInitialization() { void testListResourceTemplates() { withClient(createMcpTransport(), mcpSyncClient -> { mcpSyncClient.initialize(); - ListResourceTemplatesResult result = mcpSyncClient.listResourceTemplates(null); + ListResourceTemplatesResult result = mcpSyncClient.listResourceTemplates(McpSchema.FIRST_PAGE); assertThat(result).isNotNull(); assertThat(result.resourceTemplates()).isNotNull(); @@ -517,15 +520,10 @@ void testListResourceTemplates() { void testListAllResourceTemplates() { withClient(createMcpTransport(), mcpSyncClient -> { mcpSyncClient.initialize(); - List resourceTemplates = mcpSyncClient.listAllResourceTemplates(); - - assertThat(resourceTemplates).isNotNull().satisfies(result -> { - assertThat(result).isNotEmpty(); + ListResourceTemplatesResult result = mcpSyncClient.listResourceTemplates(); - McpSchema.ResourceTemplate firstResourceTemplate = result.get(0); - assertThat(firstResourceTemplate.name()).isNotNull(); - assertThat(firstResourceTemplate.description()).isNotNull(); - }); + assertThat(result).isNotNull(); + assertThat(result.resourceTemplates()).isNotNull(); }); } diff --git a/mcp/src/main/java/io/modelcontextprotocol/client/McpAsyncClient.java b/mcp/src/main/java/io/modelcontextprotocol/client/McpAsyncClient.java index d9fed34e5..35bc02f51 100644 --- a/mcp/src/main/java/io/modelcontextprotocol/client/McpAsyncClient.java +++ b/mcp/src/main/java/io/modelcontextprotocol/client/McpAsyncClient.java @@ -658,11 +658,19 @@ public Mono callTool(McpSchema.CallToolRequest callToo } /** - * Retrieves the first page of tools provided by the server. - * @return A Mono that emits the list of tools result. + * Retrieves list of all tools provided by the server after handling pagination. + * @return A Mono that emits a list of all tools result */ public Mono listTools() { - return this.listTools(null); + return this.listTools(McpSchema.FIRST_PAGE).expand(result -> { + if (result.nextCursor() != null) { + return this.listTools(result.nextCursor()); + } + return Mono.empty(); + }).reduce(new McpSchema.ListToolsResult(new ArrayList<>(), null), (allToolsResult, result) -> { + allToolsResult.tools().addAll(result.tools()); + return allToolsResult; + }); } /** @@ -681,28 +689,12 @@ public Mono listTools(String cursor) { }); } - /** - * Retrieves list of all tools provided by the server after handling pagination. - * @return A Mono that emits a list of all tools - */ - public Mono> listAllTools() { - return this.listTools().expand(result -> { - if (result.nextCursor() != null) { - return this.listTools(result.nextCursor()); - } - return Mono.empty(); - }).reduce(new ArrayList<>(), (allTools, result) -> { - allTools.addAll(result.tools()); - return allTools; - }); - } - private NotificationHandler asyncToolsChangeNotificationHandler( List, Mono>> toolsChangeConsumers) { // TODO: params are not used yet - return params -> this.listAllTools() - .flatMap(allTools -> Flux.fromIterable(toolsChangeConsumers) - .flatMap(consumer -> consumer.apply(allTools)) + return params -> this.listTools() + .flatMap(listToolsResult -> Flux.fromIterable(toolsChangeConsumers) + .flatMap(consumer -> consumer.apply(listToolsResult.tools())) .onErrorResume(error -> { logger.error("Error handling tools list change notification", error); return Mono.empty(); @@ -724,15 +716,23 @@ private NotificationHandler asyncToolsChangeNotificationHandler( }; /** - * Retrieves the first page of resources provided by the server. Resources represent - * any kind of UTF-8 encoded data that an MCP server makes available to clients, such - * as database records, API responses, log files, and more. - * @return A Mono that completes with the list of resources result. + * Retrieves the list of all resources provided by the server. Resources represent any + * kind of UTF-8 encoded data that an MCP server makes available to clients, such as + * database records, API responses, log files, and more. + * @return A Mono that completes with list of all resources result * @see McpSchema.ListResourcesResult * @see #readResource(McpSchema.Resource) */ public Mono listResources() { - return this.listResources(null); + return this.listResources(McpSchema.FIRST_PAGE).expand(result -> { + if (result.nextCursor() != null) { + return this.listResources(result.nextCursor()); + } + return Mono.empty(); + }).reduce(new McpSchema.ListResourcesResult(new ArrayList<>(), null), (allResourcesResult, result) -> { + allResourcesResult.resources().addAll(result.resources()); + return allResourcesResult; + }); } /** @@ -755,26 +755,6 @@ public Mono listResources(String cursor) { }); } - /** - * Retrieves the list of all resources provided by the server. Resources represent any - * kind of UTF-8 encoded data that an MCP server makes available to clients, such as - * database records, API responses, log files, and more. - * @return A Mono that completes with list of all resources. - * @see McpSchema.Resource - * @see #readResource(McpSchema.Resource) - */ - public Mono> listAllResources() { - return this.listResources().expand(result -> { - if (result.nextCursor() != null) { - return this.listResources(result.nextCursor()); - } - return Mono.empty(); - }).reduce(new ArrayList<>(), (allResources, result) -> { - allResources.addAll(result.resources()); - return allResources; - }); - } - /** * Reads the content of a specific resource identified by the provided Resource * object. This method fetches the actual data that the resource represents. @@ -807,14 +787,24 @@ public Mono readResource(McpSchema.ReadResourceReq } /** - * Retrieves the first page of resource templates provided by the server. Resource + * Retrieves the list of all resource templates provided by the server. Resource * templates allow servers to expose parameterized resources using URI templates, * enabling dynamic resource access based on variable parameters. - * @return A Mono that completes with the list of resource templates result. + * @return A Mono that completes with the list of all resource templates result * @see McpSchema.ListResourceTemplatesResult */ public Mono listResourceTemplates() { - return this.listResourceTemplates(null); + return this.listResourceTemplates(McpSchema.FIRST_PAGE).expand(result -> { + if (result.nextCursor() != null) { + return this.listResourceTemplates(result.nextCursor()); + } + return Mono.empty(); + }) + .reduce(new McpSchema.ListResourceTemplatesResult(new ArrayList<>(), null), + (allResourceTemplatesResult, result) -> { + allResourceTemplatesResult.resourceTemplates().addAll(result.resourceTemplates()); + return allResourceTemplatesResult; + }); } /** @@ -836,25 +826,6 @@ public Mono listResourceTemplates(String }); } - /** - * Retrieves the list of all resource templates provided by the server. Resource - * templates allow servers to expose parameterized resources using URI templates, - * enabling dynamic resource access based on variable parameters. - * @return A Mono that completes with the list of all resource templates. - * @see McpSchema.ResourceTemplate - */ - public Mono> listAllResourceTemplates() { - return this.listResourceTemplates().expand(result -> { - if (result.nextCursor() != null) { - return this.listResourceTemplates(result.nextCursor()); - } - return Mono.empty(); - }).reduce(new ArrayList<>(), (allResourceTemplates, result) -> { - allResourceTemplates.addAll(result.resourceTemplates()); - return allResourceTemplates; - }); - } - /** * Subscribes to changes in a specific resource. When the resource changes on the * server, the client will receive notifications through the resources change @@ -904,13 +875,21 @@ private NotificationHandler asyncResourcesChangeNotificationHandler( }; /** - * Retrieves the first page of prompts provided by the server. - * @return A Mono that completes with the list of prompts result. + * Retrieves the list of all prompts provided by the server. + * @return A Mono that completes with the list of all prompts result. * @see McpSchema.ListPromptsResult * @see #getPrompt(GetPromptRequest) */ public Mono listPrompts() { - return this.listPrompts(null); + return this.listPrompts(McpSchema.FIRST_PAGE).expand(result -> { + if (result.nextCursor() != null) { + return this.listPrompts(result.nextCursor()); + } + return Mono.empty(); + }).reduce(new ListPromptsResult(new ArrayList<>(), null), (allPromptsResult, result) -> { + allPromptsResult.prompts().addAll(result.prompts()); + return allPromptsResult; + }); } /** @@ -925,24 +904,6 @@ public Mono listPrompts(String cursor) { .sendRequest(McpSchema.METHOD_PROMPT_LIST, new PaginatedRequest(cursor), LIST_PROMPTS_RESULT_TYPE_REF)); } - /** - * Retrieves the list of all prompts provided by the server. - * @return A Mono that completes with the list of all prompts. - * @see McpSchema.Prompt - * @see #getPrompt(GetPromptRequest) - */ - public Mono> listAllPrompts() { - return this.listPrompts().expand(result -> { - if (result.nextCursor() != null) { - return this.listPrompts(result.nextCursor()); - } - return Mono.empty(); - }).reduce(new ArrayList<>(), (allPrompts, result) -> { - allPrompts.addAll(result.prompts()); - return allPrompts; - }); - } - /** * Retrieves a specific prompt by its ID. This provides the complete prompt template * including all parameters and instructions for generating AI content. diff --git a/mcp/src/main/java/io/modelcontextprotocol/client/McpSyncClient.java b/mcp/src/main/java/io/modelcontextprotocol/client/McpSyncClient.java index 5fc08ca3e..27b020f05 100644 --- a/mcp/src/main/java/io/modelcontextprotocol/client/McpSyncClient.java +++ b/mcp/src/main/java/io/modelcontextprotocol/client/McpSyncClient.java @@ -221,9 +221,9 @@ public McpSchema.CallToolResult callTool(McpSchema.CallToolRequest callToolReque } /** - * Retrieves the first page of tools provided by the server. - * @return The list of tools result containing: - tools: List of available tools, each - * with a name, description, and input schema - nextCursor: Optional cursor for + * Retrieves the list of all tools provided by the server. + * @return The list of all tools result containing: - tools: List of available tools, + * each with a name, description, and input schema - nextCursor: Optional cursor for * pagination if more tools are available */ public McpSchema.ListToolsResult listTools() { @@ -241,21 +241,13 @@ public McpSchema.ListToolsResult listTools(String cursor) { return this.delegate.listTools(cursor).block(); } - /** - * Retrieves the list of all tools provided by the server. - * @return The list of all tools - */ - public List listAllTools() { - return this.delegate.listAllTools().block(); - } - // -------------------------- // Resources // -------------------------- /** - * Retrieves the first page of resources provided by the server. - * @return The list of resources result + * Retrieves the list of all resources provided by the server. + * @return The list of all resources result */ public McpSchema.ListResourcesResult listResources() { return this.delegate.listResources().block(); @@ -270,14 +262,6 @@ public McpSchema.ListResourcesResult listResources(String cursor) { return this.delegate.listResources(cursor).block(); } - /** - * Retrieves the list of all resources provided by the server. - * @return The list of all resources - */ - public List listAllResources() { - return this.delegate.listAllResources().block(); - } - /** * Send a resources/read request. * @param resource the resource to read @@ -297,8 +281,8 @@ public McpSchema.ReadResourceResult readResource(McpSchema.ReadResourceRequest r } /** - * Retrieves the first page of resource templates provided by the server. - * @return The list of resource templates result. + * Retrieves the list of all resource templates provided by the server. + * @return The list of all resource templates result. */ public McpSchema.ListResourceTemplatesResult listResourceTemplates() { return this.delegate.listResourceTemplates().block(); @@ -316,14 +300,6 @@ public McpSchema.ListResourceTemplatesResult listResourceTemplates(String cursor return this.delegate.listResourceTemplates(cursor).block(); } - /** - * Retrieves the list of all resources provided by the server. - * @return The list of all resource templates - */ - public List listAllResourceTemplates() { - return this.delegate.listAllResourceTemplates().block(); - } - /** * Subscriptions. The protocol supports optional subscriptions to resource changes. * Clients can subscribe to specific resources and receive notifications when they @@ -351,8 +327,8 @@ public void unsubscribeResource(McpSchema.UnsubscribeRequest unsubscribeRequest) // -------------------------- /** - * Retrieves the first page of prompts provided by the server. - * @return The list of prompts result. + * Retrieves the list of all prompts provided by the server. + * @return The list of all prompts result. */ public ListPromptsResult listPrompts() { return this.delegate.listPrompts().block(); @@ -367,14 +343,6 @@ public ListPromptsResult listPrompts(String cursor) { return this.delegate.listPrompts(cursor).block(); } - /** - * Retrieves the list of all prompts provided by the server. - * @return The list of all prompts - */ - public List listAllPrompts() { - return this.delegate.listAllPrompts().block(); - } - public GetPromptResult getPrompt(GetPromptRequest getPromptRequest) { return this.delegate.getPrompt(getPromptRequest).block(); } diff --git a/mcp/src/main/java/io/modelcontextprotocol/spec/McpSchema.java b/mcp/src/main/java/io/modelcontextprotocol/spec/McpSchema.java index a78600af1..f1e78e2d3 100644 --- a/mcp/src/main/java/io/modelcontextprotocol/spec/McpSchema.java +++ b/mcp/src/main/java/io/modelcontextprotocol/spec/McpSchema.java @@ -41,6 +41,8 @@ private McpSchema() { public static final String JSONRPC_VERSION = "2.0"; + public static final String FIRST_PAGE = null; + // --------------------------- // Method Names // --------------------------- diff --git a/mcp/src/test/java/io/modelcontextprotocol/client/AbstractMcpAsyncClientTests.java b/mcp/src/test/java/io/modelcontextprotocol/client/AbstractMcpAsyncClientTests.java index 4fa6b516d..9be6e553c 100644 --- a/mcp/src/test/java/io/modelcontextprotocol/client/AbstractMcpAsyncClientTests.java +++ b/mcp/src/test/java/io/modelcontextprotocol/client/AbstractMcpAsyncClientTests.java @@ -20,12 +20,6 @@ import java.util.function.Consumer; import java.util.function.Function; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatCode; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.assertj.core.api.Assertions.fail; -import static org.junit.jupiter.api.Assertions.assertInstanceOf; - import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -156,13 +150,13 @@ void testConstructorWithInvalidArguments() { @Test void testListToolsWithoutInitialization() { - verifyCallSucceedsWithImplicitInitialization(client -> client.listTools(null), "listing tools"); + verifyCallSucceedsWithImplicitInitialization(client -> client.listTools(McpSchema.FIRST_PAGE), "listing tools"); } @Test void testListTools() { withClient(createMcpTransport(), mcpAsyncClient -> { - StepVerifier.create(mcpAsyncClient.initialize().then(mcpAsyncClient.listTools(null))) + StepVerifier.create(mcpAsyncClient.initialize().then(mcpAsyncClient.listTools(McpSchema.FIRST_PAGE))) .consumeNextWith(result -> { assertThat(result.tools()).isNotNull().isNotEmpty(); @@ -177,15 +171,13 @@ void testListTools() { @Test void testListAllTools() { withClient(createMcpTransport(), mcpAsyncClient -> { - StepVerifier.create(mcpAsyncClient.initialize().then(mcpAsyncClient.listAllTools())) - .consumeNextWith(tools -> { - assertThat(tools).isNotNull().satisfies(result -> { - assertThat(result).isNotEmpty(); - - Tool firstTool = result.get(0); - assertThat(firstTool.name()).isNotNull(); - assertThat(firstTool.description()).isNotNull(); - }); + StepVerifier.create(mcpAsyncClient.initialize().then(mcpAsyncClient.listTools())) + .consumeNextWith(result -> { + assertThat(result.tools()).isNotNull().isNotEmpty(); + + Tool firstTool = result.tools().get(0); + assertThat(firstTool.name()).isNotNull(); + assertThat(firstTool.description()).isNotNull(); }) .verifyComplete(); }); @@ -300,13 +292,14 @@ void testCallToolWithMessageAnnotations(String messageType) { @Test void testListResourcesWithoutInitialization() { - verifyCallSucceedsWithImplicitInitialization(client -> client.listResources(null), "listing resources"); + verifyCallSucceedsWithImplicitInitialization(client -> client.listResources(McpSchema.FIRST_PAGE), + "listing resources"); } @Test void testListResources() { withClient(createMcpTransport(), mcpAsyncClient -> { - StepVerifier.create(mcpAsyncClient.initialize().then(mcpAsyncClient.listResources(null))) + StepVerifier.create(mcpAsyncClient.initialize().then(mcpAsyncClient.listResources(McpSchema.FIRST_PAGE))) .consumeNextWith(resources -> { assertThat(resources).isNotNull().satisfies(result -> { assertThat(result.resources()).isNotNull(); @@ -325,14 +318,16 @@ void testListResources() { @Test void testListAllResources() { withClient(createMcpTransport(), mcpAsyncClient -> { - StepVerifier.create(mcpAsyncClient.initialize().then(mcpAsyncClient.listAllResources())) + StepVerifier.create(mcpAsyncClient.initialize().then(mcpAsyncClient.listResources())) .consumeNextWith(resources -> { assertThat(resources).isNotNull().satisfies(result -> { - assertThat(result).isNotEmpty(); + assertThat(result.resources()).isNotNull(); - Resource firstResource = result.get(0); - assertThat(firstResource.uri()).isNotNull(); - assertThat(firstResource.name()).isNotNull(); + if (!result.resources().isEmpty()) { + Resource firstResource = result.resources().get(0); + assertThat(firstResource.uri()).isNotNull(); + assertThat(firstResource.name()).isNotNull(); + } }); }) .verifyComplete(); @@ -348,13 +343,14 @@ void testMcpAsyncClientState() { @Test void testListPromptsWithoutInitialization() { - verifyCallSucceedsWithImplicitInitialization(client -> client.listPrompts(null), "listing " + "prompts"); + verifyCallSucceedsWithImplicitInitialization(client -> client.listPrompts(McpSchema.FIRST_PAGE), + "listing " + "prompts"); } @Test void testListPrompts() { withClient(createMcpTransport(), mcpAsyncClient -> { - StepVerifier.create(mcpAsyncClient.initialize().then(mcpAsyncClient.listPrompts(null))) + StepVerifier.create(mcpAsyncClient.initialize().then(mcpAsyncClient.listPrompts(McpSchema.FIRST_PAGE))) .consumeNextWith(prompts -> { assertThat(prompts).isNotNull().satisfies(result -> { assertThat(result.prompts()).isNotNull(); @@ -373,14 +369,16 @@ void testListPrompts() { @Test void testListAllPrompts() { withClient(createMcpTransport(), mcpAsyncClient -> { - StepVerifier.create(mcpAsyncClient.initialize().then(mcpAsyncClient.listAllPrompts())) + StepVerifier.create(mcpAsyncClient.initialize().then(mcpAsyncClient.listPrompts())) .consumeNextWith(prompts -> { assertThat(prompts).isNotNull().satisfies(result -> { - assertThat(result).isNotEmpty(); + assertThat(result.prompts()).isNotNull(); - Prompt firstPrompt = result.get(0); - assertThat(firstPrompt.name()).isNotNull(); - assertThat(firstPrompt.description()).isNotNull(); + if (!result.prompts().isEmpty()) { + Prompt firstPrompt = result.prompts().get(0); + assertThat(firstPrompt.name()).isNotNull(); + assertThat(firstPrompt.description()).isNotNull(); + } }); }) .verifyComplete(); @@ -527,14 +525,15 @@ else if (content instanceof BlobResourceContents blobContent) { @Test void testListResourceTemplatesWithoutInitialization() { - verifyCallSucceedsWithImplicitInitialization(client -> client.listResourceTemplates(), + verifyCallSucceedsWithImplicitInitialization(client -> client.listResourceTemplates(McpSchema.FIRST_PAGE), "listing resource templates"); } @Test void testListResourceTemplates() { withClient(createMcpTransport(), mcpAsyncClient -> { - StepVerifier.create(mcpAsyncClient.initialize().then(mcpAsyncClient.listResourceTemplates())) + StepVerifier + .create(mcpAsyncClient.initialize().then(mcpAsyncClient.listResourceTemplates(McpSchema.FIRST_PAGE))) .consumeNextWith(result -> { assertThat(result).isNotNull(); assertThat(result.resourceTemplates()).isNotNull(); @@ -546,15 +545,10 @@ void testListResourceTemplates() { @Test void testListAllResourceTemplates() { withClient(createMcpTransport(), mcpAsyncClient -> { - StepVerifier.create(mcpAsyncClient.initialize().then(mcpAsyncClient.listAllResourceTemplates())) - .consumeNextWith(resourceTemplates -> { - assertThat(resourceTemplates).isNotNull().satisfies(result -> { - assertThat(result).isNotEmpty(); - - McpSchema.ResourceTemplate firstResourceTemplate = result.get(0); - assertThat(firstResourceTemplate.name()).isNotNull(); - assertThat(firstResourceTemplate.description()).isNotNull(); - }); + StepVerifier.create(mcpAsyncClient.initialize().then(mcpAsyncClient.listResourceTemplates())) + .consumeNextWith(result -> { + assertThat(result).isNotNull(); + assertThat(result.resourceTemplates()).isNotNull(); }) .verifyComplete(); }); @@ -653,6 +647,7 @@ void testInitializeWithAllCapabilities() { // --------------------------------------- // Logging Tests // --------------------------------------- + @Test void testLoggingLevelsWithoutInitialization() { verifyNotificationSucceedsWithImplicitInitialization( diff --git a/mcp/src/test/java/io/modelcontextprotocol/client/AbstractMcpSyncClientTests.java b/mcp/src/test/java/io/modelcontextprotocol/client/AbstractMcpSyncClientTests.java index d7ede6b4e..6cb694678 100644 --- a/mcp/src/test/java/io/modelcontextprotocol/client/AbstractMcpSyncClientTests.java +++ b/mcp/src/test/java/io/modelcontextprotocol/client/AbstractMcpSyncClientTests.java @@ -152,14 +152,14 @@ void testConstructorWithInvalidArguments() { @Test void testListToolsWithoutInitialization() { - verifyCallSucceedsWithImplicitInitialization(client -> client.listTools(null), "listing tools"); + verifyCallSucceedsWithImplicitInitialization(client -> client.listTools(McpSchema.FIRST_PAGE), "listing tools"); } @Test void testListTools() { withClient(createMcpTransport(), mcpSyncClient -> { mcpSyncClient.initialize(); - ListToolsResult tools = mcpSyncClient.listTools(null); + ListToolsResult tools = mcpSyncClient.listTools(McpSchema.FIRST_PAGE); assertThat(tools).isNotNull().satisfies(result -> { assertThat(result.tools()).isNotNull().isNotEmpty(); @@ -175,12 +175,12 @@ void testListTools() { void testListAllTools() { withClient(createMcpTransport(), mcpSyncClient -> { mcpSyncClient.initialize(); - List tools = mcpSyncClient.listAllTools(); + ListToolsResult tools = mcpSyncClient.listTools(); assertThat(tools).isNotNull().satisfies(result -> { - assertThat(result).isNotEmpty(); + assertThat(result.tools()).isNotNull().isNotEmpty(); - Tool firstTool = result.get(0); + Tool firstTool = result.tools().get(0); assertThat(firstTool.name()).isNotNull(); assertThat(firstTool.description()).isNotNull(); }); @@ -325,14 +325,15 @@ void testRootsListChanged() { @Test void testListResourcesWithoutInitialization() { - verifyCallSucceedsWithImplicitInitialization(client -> client.listResources(null), "listing resources"); + verifyCallSucceedsWithImplicitInitialization(client -> client.listResources(McpSchema.FIRST_PAGE), + "listing resources"); } @Test void testListResources() { withClient(createMcpTransport(), mcpSyncClient -> { mcpSyncClient.initialize(); - ListResourcesResult resources = mcpSyncClient.listResources(null); + ListResourcesResult resources = mcpSyncClient.listResources(McpSchema.FIRST_PAGE); assertThat(resources).isNotNull().satisfies(result -> { assertThat(result.resources()).isNotNull(); @@ -350,14 +351,16 @@ void testListResources() { void testListAllResources() { withClient(createMcpTransport(), mcpSyncClient -> { mcpSyncClient.initialize(); - List resources = mcpSyncClient.listAllResources(); + ListResourcesResult resources = mcpSyncClient.listResources(); assertThat(resources).isNotNull().satisfies(result -> { - assertThat(result).isNotEmpty(); + assertThat(result.resources()).isNotNull(); - Resource firstResource = result.get(0); - assertThat(firstResource.uri()).isNotNull(); - assertThat(firstResource.name()).isNotNull(); + if (!result.resources().isEmpty()) { + Resource firstResource = result.resources().get(0); + assertThat(firstResource.uri()).isNotNull(); + assertThat(firstResource.name()).isNotNull(); + } }); }); } @@ -499,7 +502,7 @@ else if (content instanceof BlobResourceContents blobContent) { @Test void testListResourceTemplatesWithoutInitialization() { - verifyCallSucceedsWithImplicitInitialization(client -> client.listResourceTemplates(null), + verifyCallSucceedsWithImplicitInitialization(client -> client.listResourceTemplates(McpSchema.FIRST_PAGE), "listing resource templates"); } @@ -507,7 +510,7 @@ void testListResourceTemplatesWithoutInitialization() { void testListResourceTemplates() { withClient(createMcpTransport(), mcpSyncClient -> { mcpSyncClient.initialize(); - ListResourceTemplatesResult result = mcpSyncClient.listResourceTemplates(null); + ListResourceTemplatesResult result = mcpSyncClient.listResourceTemplates(McpSchema.FIRST_PAGE); assertThat(result).isNotNull(); assertThat(result.resourceTemplates()).isNotNull(); @@ -518,15 +521,10 @@ void testListResourceTemplates() { void testListAllResourceTemplates() { withClient(createMcpTransport(), mcpSyncClient -> { mcpSyncClient.initialize(); - List resourceTemplates = mcpSyncClient.listAllResourceTemplates(); - - assertThat(resourceTemplates).isNotNull().satisfies(result -> { - assertThat(result).isNotEmpty(); + ListResourceTemplatesResult result = mcpSyncClient.listResourceTemplates(); - McpSchema.ResourceTemplate firstResourceTemplate = result.get(0); - assertThat(firstResourceTemplate.name()).isNotNull(); - assertThat(firstResourceTemplate.description()).isNotNull(); - }); + assertThat(result).isNotNull(); + assertThat(result.resourceTemplates()).isNotNull(); }); } From ec6fc45962d5a1029f7f0e245c80c582b5990e81 Mon Sep 17 00:00:00 2001 From: Anurag Pant Date: Mon, 23 Jun 2025 16:22:39 -0700 Subject: [PATCH 4/4] Minor comment fix --- .../java/io/modelcontextprotocol/client/McpAsyncClient.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mcp/src/main/java/io/modelcontextprotocol/client/McpAsyncClient.java b/mcp/src/main/java/io/modelcontextprotocol/client/McpAsyncClient.java index 35bc02f51..8630c2bd6 100644 --- a/mcp/src/main/java/io/modelcontextprotocol/client/McpAsyncClient.java +++ b/mcp/src/main/java/io/modelcontextprotocol/client/McpAsyncClient.java @@ -658,8 +658,8 @@ public Mono callTool(McpSchema.CallToolRequest callToo } /** - * Retrieves list of all tools provided by the server after handling pagination. - * @return A Mono that emits a list of all tools result + * Retrieves the list of all tools provided by the server. + * @return A Mono that emits the list of all tools result */ public Mono listTools() { return this.listTools(McpSchema.FIRST_PAGE).expand(result -> { @@ -719,7 +719,7 @@ private NotificationHandler asyncToolsChangeNotificationHandler( * Retrieves the list of all resources provided by the server. Resources represent any * kind of UTF-8 encoded data that an MCP server makes available to clients, such as * database records, API responses, log files, and more. - * @return A Mono that completes with list of all resources result + * @return A Mono that completes with the list of all resources result * @see McpSchema.ListResourcesResult * @see #readResource(McpSchema.Resource) */