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();
}