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 d08bf706..e107e9b7 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 d9492966..cbddc222 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 8f0433eb..d9fed34e 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,12 +681,28 @@ 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.listTools() - .flatMap(listToolsResult -> Flux.fromIterable(toolsChangeConsumers) - .flatMap(consumer -> consumer.apply(listToolsResult.tools())) + return params -> this.listAllTools() + .flatMap(allTools -> Flux.fromIterable(toolsChangeConsumers) + .flatMap(consumer -> consumer.apply(allTools)) .onErrorResume(error -> { logger.error("Error handling tools list change notification", error); return Mono.empty(); @@ -706,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) @@ -737,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. @@ -769,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. @@ -798,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 @@ -847,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) @@ -868,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 a8fb979e..5fc08ca3 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 af130bec..99090274 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 10ecadec..bc700a3c 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 -> { diff --git a/mcp/src/test/java/io/modelcontextprotocol/client/McpAsyncClientResponseHandlerTests.java b/mcp/src/test/java/io/modelcontextprotocol/client/McpAsyncClientResponseHandlerTests.java index e6cde8e3..a79bdf6c 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(); }