diff --git a/mcp-spring/mcp-spring-webflux/src/main/java/io/modelcontextprotocol/client/transport/WebClientStreamableHttpTransport.java b/mcp-spring/mcp-spring-webflux/src/main/java/io/modelcontextprotocol/client/transport/WebClientStreamableHttpTransport.java index e0e1094..dd7c653 100644 --- a/mcp-spring/mcp-spring-webflux/src/main/java/io/modelcontextprotocol/client/transport/WebClientStreamableHttpTransport.java +++ b/mcp-spring/mcp-spring-webflux/src/main/java/io/modelcontextprotocol/client/transport/WebClientStreamableHttpTransport.java @@ -1,16 +1,12 @@ package io.modelcontextprotocol.client.transport; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.ObjectMapper; -import io.modelcontextprotocol.spec.DefaultMcpTransportSession; -import io.modelcontextprotocol.spec.DefaultMcpTransportStream; -import io.modelcontextprotocol.spec.McpClientTransport; -import io.modelcontextprotocol.spec.McpError; -import io.modelcontextprotocol.spec.McpSchema; -import io.modelcontextprotocol.spec.McpTransportSessionNotFoundException; -import io.modelcontextprotocol.spec.McpTransportSession; -import io.modelcontextprotocol.spec.McpTransportStream; -import io.modelcontextprotocol.util.Assert; +import java.io.IOException; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Consumer; +import java.util.function.Function; + import org.reactivestreams.Publisher; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -21,20 +17,25 @@ import org.springframework.http.codec.ServerSentEvent; import org.springframework.web.reactive.function.client.ClientResponse; import org.springframework.web.reactive.function.client.WebClient; import org.springframework.web.reactive.function.client.WebClientResponseException; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; + +import io.modelcontextprotocol.spec.DefaultMcpTransportSession; +import io.modelcontextprotocol.spec.DefaultMcpTransportStream; +import io.modelcontextprotocol.spec.McpClientTransport; +import io.modelcontextprotocol.spec.McpError; +import io.modelcontextprotocol.spec.McpSchema; +import io.modelcontextprotocol.spec.McpTransportSession; +import io.modelcontextprotocol.spec.McpTransportSessionNotFoundException; +import io.modelcontextprotocol.spec.McpTransportStream; +import io.modelcontextprotocol.util.Assert; import reactor.core.Disposable; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import reactor.util.function.Tuple2; import reactor.util.function.Tuples; -import java.io.IOException; -import java.util.List; -import java.util.Optional; -import java.util.concurrent.atomic.AtomicReference; -import java.util.function.Consumer; -import java.util.function.Function; -import java.util.function.Supplier; - /** * An implementation of the Streamable HTTP protocol as defined by the * 2025-03-26 version of the MCP specification. diff --git a/mcp-test/src/main/java/io/modelcontextprotocol/client/AbstractMcpAsyncClientResiliencyTests.java b/mcp-test/src/main/java/io/modelcontextprotocol/client/AbstractMcpAsyncClientResiliencyTests.java index 85d6a88..6748eb7 100644 --- a/mcp-test/src/main/java/io/modelcontextprotocol/client/AbstractMcpAsyncClientResiliencyTests.java +++ b/mcp-test/src/main/java/io/modelcontextprotocol/client/AbstractMcpAsyncClientResiliencyTests.java @@ -1,3 +1,6 @@ +/* + * Copyright 2024-2024 the original author or authors. + */ package io.modelcontextprotocol.client; import eu.rekawek.toxiproxy.Proxy; 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 4c77e59..4f7d567 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 @@ public abstract class AbstractMcpAsyncClientTests { @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 @@ public abstract class AbstractMcpAsyncClientTests { @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 @@ public abstract class AbstractMcpAsyncClientTests { @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 @@ public abstract class AbstractMcpAsyncClientTests { @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 80c1525..7736c23 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 @@ public abstract class AbstractMcpSyncClientTests { @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 @@ public abstract class AbstractMcpSyncClientTests { @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 @@ public abstract class AbstractMcpSyncClientTests { @Test void testListResourceTemplatesWithoutInitialization() { - verifyCallSucceedsWithImplicitInitialization(client -> client.listResourceTemplates(null), + verifyCallSucceedsWithImplicitInitialization(client -> client.listResourceTemplates(McpSchema.FIRST_PAGE), "listing resource templates"); } @@ -474,7 +509,18 @@ public abstract class AbstractMcpSyncClientTests { 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 8f0433e..8630c2b 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.concurrent.atomic.AtomicReference; 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.McpSchema.Root; 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 class McpAsyncClient { /** * 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 @@ public class McpAsyncClient { * 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 class McpAsyncClient { * 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 @@ public class McpAsyncClient { /** * 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 a8fb979..27b020f 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.GetPromptRequest; 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 class McpSyncClient implements AutoCloseable { /** * 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,22 +246,22 @@ public class McpSyncClient implements AutoCloseable { // -------------------------- /** - * Send a resources/list request. - * @param cursor the cursor - * @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 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/read request. * @param resource the resource to read @@ -279,25 +281,25 @@ public class McpSyncClient implements AutoCloseable { } /** - * 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. - */ - 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 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. + * + * 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(); + } + /** * 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 class McpSyncClient implements AutoCloseable { // -------------------------- // 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 942b148..e330e4f 100644 --- a/mcp/src/main/java/io/modelcontextprotocol/spec/McpSchema.java +++ b/mcp/src/main/java/io/modelcontextprotocol/spec/McpSchema.java @@ -44,6 +44,8 @@ public final class 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 8472ec3..9be6e55 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 @@ public abstract class AbstractMcpAsyncClientTests { @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 @@ public abstract class AbstractMcpAsyncClientTests { @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 @@ public abstract class AbstractMcpAsyncClientTests { @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 @@ public abstract class AbstractMcpAsyncClientTests { @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 @@ public abstract class AbstractMcpAsyncClientTests { // --------------------------------------- // 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 1385149..6cb6946 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 @@ public abstract class AbstractMcpSyncClientTests { @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 @@ public abstract class AbstractMcpSyncClientTests { @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 @@ public abstract class AbstractMcpSyncClientTests { @Test void testListResourceTemplatesWithoutInitialization() { - verifyCallSucceedsWithImplicitInitialization(client -> client.listResourceTemplates(null), + verifyCallSucceedsWithImplicitInitialization(client -> client.listResourceTemplates(McpSchema.FIRST_PAGE), "listing resource templates"); } @@ -475,7 +510,18 @@ public abstract class AbstractMcpSyncClientTests { 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 e6cde8e..a79bdf6 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.McpError; 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 @@ class McpAsyncClientResponseHandlerTests { // 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 toolsListResponse = new McpSchema.JSONRPCResponse(McpSchema.JSONRPC_VERSION, - toolsListRequest.id(), mockToolsResult, null); - transport.simulateIncomingMessage(toolsListResponse); + McpSchema.JSONRPCResponse toolsListResponse1 = new McpSchema.JSONRPCResponse(McpSchema.JSONRPC_VERSION, + toolsListRequest1.id(), mockToolsResult1, null); + transport.simulateIncomingMessage(toolsListResponse1); - // 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"); + // 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 toolsListResponse2 = new McpSchema.JSONRPCResponse(McpSchema.JSONRPC_VERSION, + toolsListRequest2.id(), mockToolsResult2, null); + transport.simulateIncomingMessage(toolsListResponse2); + + // 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(); }