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 4c77e5985..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,28 @@ 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(); + + Tool firstTool = result.tools().get(0); + assertThat(firstTool.name()).isNotNull(); + assertThat(firstTool.description()).isNotNull(); + }) + .verifyComplete(); + }); + } + + @Test + void testListAllTools() { + withClient(createMcpTransport(), mcpAsyncClient -> { + StepVerifier.create(mcpAsyncClient.initialize().then(mcpAsyncClient.listTools())) .consumeNextWith(result -> { assertThat(result.tools()).isNotNull().isNotEmpty(); @@ -276,13 +291,33 @@ 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(); + + if (!result.resources().isEmpty()) { + Resource firstResource = result.resources().get(0); + assertThat(firstResource.uri()).isNotNull(); + assertThat(firstResource.name()).isNotNull(); + } + }); + }) + .verifyComplete(); + }); + } + + @Test + void testListAllResources() { + withClient(createMcpTransport(), mcpAsyncClient -> { + StepVerifier.create(mcpAsyncClient.initialize().then(mcpAsyncClient.listResources())) .consumeNextWith(resources -> { assertThat(resources).isNotNull().satisfies(result -> { assertThat(result.resources()).isNotNull(); @@ -307,13 +342,33 @@ 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(); + + if (!result.prompts().isEmpty()) { + Prompt firstPrompt = result.prompts().get(0); + assertThat(firstPrompt.name()).isNotNull(); + assertThat(firstPrompt.description()).isNotNull(); + } + }); + }) + .verifyComplete(); + }); + } + + @Test + void testListAllPrompts() { + withClient(createMcpTransport(), mcpAsyncClient -> { + StepVerifier.create(mcpAsyncClient.initialize().then(mcpAsyncClient.listPrompts())) .consumeNextWith(prompts -> { assertThat(prompts).isNotNull().satisfies(result -> { assertThat(result.prompts()).isNotNull(); @@ -469,12 +524,25 @@ 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(McpSchema.FIRST_PAGE))) + .consumeNextWith(result -> { + assertThat(result).isNotNull(); + assertThat(result.resourceTemplates()).isNotNull(); + }) + .verifyComplete(); + }); + } + + @Test + void testListAllResourceTemplates() { withClient(createMcpTransport(), mcpAsyncClient -> { StepVerifier.create(mcpAsyncClient.initialize().then(mcpAsyncClient.listResourceTemplates())) .consumeNextWith(result -> { 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 80c152505..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,30 @@ 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(); + + Tool firstTool = result.tools().get(0); + assertThat(firstTool.name()).isNotNull(); + assertThat(firstTool.description()).isNotNull(); + }); + }); + } + + @Test + void testListAllTools() { + withClient(createMcpTransport(), mcpSyncClient -> { + mcpSyncClient.initialize(); + ListToolsResult tools = mcpSyncClient.listTools(); assertThat(tools).isNotNull().satisfies(result -> { assertThat(result.tools()).isNotNull().isNotEmpty(); @@ -308,14 +324,33 @@ 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(); + + if (!result.resources().isEmpty()) { + Resource firstResource = result.resources().get(0); + assertThat(firstResource.uri()).isNotNull(); + assertThat(firstResource.name()).isNotNull(); + } + }); + }); + } + + @Test + void testListAllResources() { + withClient(createMcpTransport(), mcpSyncClient -> { + mcpSyncClient.initialize(); + ListResourcesResult resources = mcpSyncClient.listResources(); assertThat(resources).isNotNull().satisfies(result -> { assertThat(result.resources()).isNotNull(); @@ -466,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"); } @@ -474,7 +509,18 @@ 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(); + }); + } + + @Test + void testListAllResourceTemplates() { + withClient(createMcpTransport(), mcpSyncClient -> { + mcpSyncClient.initialize(); + ListResourceTemplatesResult result = mcpSyncClient.listResourceTemplates(); 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 8f0433eb1..8630c2bd6 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; @@ -657,10 +659,18 @@ public Mono callTool(McpSchema.CallToolRequest callToo /** * Retrieves the list of all tools provided by the server. - * @return A Mono that emits the list of tools result. + * @return A Mono that emits the 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; + }); } /** @@ -709,12 +719,20 @@ 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 the list of resources result. + * @return A Mono that completes with the 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; + }); } /** @@ -772,11 +790,21 @@ public Mono readResource(McpSchema.ReadResourceReq * 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; + }); } /** @@ -848,12 +876,20 @@ private NotificationHandler asyncResourcesChangeNotificationHandler( /** * Retrieves the list of all prompts provided by the server. - * @return A Mono that completes with the list of prompts result. + * @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; + }); } /** diff --git a/mcp/src/main/java/io/modelcontextprotocol/client/McpSyncClient.java b/mcp/src/main/java/io/modelcontextprotocol/client/McpSyncClient.java index a8fb979e1..27b020f05 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 @@ -220,8 +222,8 @@ public McpSchema.CallToolResult callTool(McpSchema.CallToolRequest callToolReque /** * Retrieves the list of all 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 + * @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() { @@ -244,20 +246,20 @@ public McpSchema.ListToolsResult listTools(String cursor) { // -------------------------- /** - * Send a resources/list request. - * @param cursor the cursor - * @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(String cursor) { - return this.delegate.listResources(cursor).block(); + public McpSchema.ListResourcesResult listResources() { + return this.delegate.listResources().block(); } /** - * Send a resources/list request. - * @return the list of resources result. + * 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() { - return this.delegate.listResources().block(); + public McpSchema.ListResourcesResult listResources(String cursor) { + return this.delegate.listResources(cursor).block(); } /** @@ -278,26 +280,26 @@ public McpSchema.ReadResourceResult readResource(McpSchema.ReadResourceRequest r return this.delegate.readResource(readResourceRequest).block(); } + /** + * 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(); + } + /** * 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. - */ - public McpSchema.ListResourceTemplatesResult listResourceTemplates() { - return this.delegate.listResourceTemplates().block(); - } - /** * Subscriptions. The protocol supports optional subscriptions to resource changes. * Clients can subscribe to specific resources and receive notifications when they @@ -323,14 +325,24 @@ public void unsubscribeResource(McpSchema.UnsubscribeRequest unsubscribeRequest) // -------------------------- // Prompts // -------------------------- - 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 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 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 8472ec3e9..9be6e553c 100644 --- a/mcp/src/test/java/io/modelcontextprotocol/client/AbstractMcpAsyncClientTests.java +++ b/mcp/src/test/java/io/modelcontextprotocol/client/AbstractMcpAsyncClientTests.java @@ -150,13 +150,28 @@ 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(); + + Tool firstTool = result.tools().get(0); + assertThat(firstTool.name()).isNotNull(); + assertThat(firstTool.description()).isNotNull(); + }) + .verifyComplete(); + }); + } + + @Test + void testListAllTools() { + withClient(createMcpTransport(), mcpAsyncClient -> { + StepVerifier.create(mcpAsyncClient.initialize().then(mcpAsyncClient.listTools())) .consumeNextWith(result -> { assertThat(result.tools()).isNotNull().isNotEmpty(); @@ -277,13 +292,33 @@ 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(); + + if (!result.resources().isEmpty()) { + Resource firstResource = result.resources().get(0); + assertThat(firstResource.uri()).isNotNull(); + assertThat(firstResource.name()).isNotNull(); + } + }); + }) + .verifyComplete(); + }); + } + + @Test + void testListAllResources() { + withClient(createMcpTransport(), mcpAsyncClient -> { + StepVerifier.create(mcpAsyncClient.initialize().then(mcpAsyncClient.listResources())) .consumeNextWith(resources -> { assertThat(resources).isNotNull().satisfies(result -> { assertThat(result.resources()).isNotNull(); @@ -308,13 +343,33 @@ 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(); + + if (!result.prompts().isEmpty()) { + Prompt firstPrompt = result.prompts().get(0); + assertThat(firstPrompt.name()).isNotNull(); + assertThat(firstPrompt.description()).isNotNull(); + } + }); + }) + .verifyComplete(); + }); + } + + @Test + void testListAllPrompts() { + withClient(createMcpTransport(), mcpAsyncClient -> { + StepVerifier.create(mcpAsyncClient.initialize().then(mcpAsyncClient.listPrompts())) .consumeNextWith(prompts -> { assertThat(prompts).isNotNull().satisfies(result -> { assertThat(result.prompts()).isNotNull(); @@ -470,12 +525,25 @@ 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(McpSchema.FIRST_PAGE))) + .consumeNextWith(result -> { + assertThat(result).isNotNull(); + assertThat(result.resourceTemplates()).isNotNull(); + }) + .verifyComplete(); + }); + } + + @Test + void testListAllResourceTemplates() { withClient(createMcpTransport(), mcpAsyncClient -> { StepVerifier.create(mcpAsyncClient.initialize().then(mcpAsyncClient.listResourceTemplates())) .consumeNextWith(result -> { @@ -579,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 138514959..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,30 @@ 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(); + + Tool firstTool = result.tools().get(0); + assertThat(firstTool.name()).isNotNull(); + assertThat(firstTool.description()).isNotNull(); + }); + }); + } + + @Test + void testListAllTools() { + withClient(createMcpTransport(), mcpSyncClient -> { + mcpSyncClient.initialize(); + ListToolsResult tools = mcpSyncClient.listTools(); assertThat(tools).isNotNull().satisfies(result -> { assertThat(result.tools()).isNotNull().isNotEmpty(); @@ -309,14 +325,33 @@ 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(); + + if (!result.resources().isEmpty()) { + Resource firstResource = result.resources().get(0); + assertThat(firstResource.uri()).isNotNull(); + assertThat(firstResource.name()).isNotNull(); + } + }); + }); + } + + @Test + void testListAllResources() { + withClient(createMcpTransport(), mcpSyncClient -> { + mcpSyncClient.initialize(); + ListResourcesResult resources = mcpSyncClient.listResources(); assertThat(resources).isNotNull().satisfies(result -> { assertThat(result.resources()).isNotNull(); @@ -467,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"); } @@ -475,7 +510,18 @@ 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(); + }); + } + + @Test + void testListAllResourceTemplates() { + withClient(createMcpTransport(), mcpSyncClient -> { + mcpSyncClient.initialize(); + ListResourceTemplatesResult result = mcpSyncClient.listResourceTemplates(); assertThat(result).isNotNull(); assertThat(result.resourceTemplates()).isNotNull(); 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(); }