feat: add support for meta parameter in client paginated list queries (#906)

* feat: add support for meta parameter in client paginated list queries#

- resources/list
- resources/templates/list
- prompts/list
- tools/list

paginated list operations extended in this review:
- listResources(String cursor, Map<String, Object> meta)
- listResourceTemplates(String cursor, Map<String, Object> meta)
- listPrompts(String cursor, Map<String, Object> meta)

Closes #907

Co-authored-by: SHEETAL MOHITE <mohishee@amazon.com>
Signed-off-by: Daniel Garnier-Moiroux <git@garnier.wf>
This commit is contained in:
smohite04 2026-04-09 01:40:35 -07:00 • committed by GitHub
commit eaa0c691f0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 733 additions and 8 deletions

View file

@ -303,7 +303,7 @@ public class McpAsyncClient {
return Mono.empty();
}
return this.listToolsInternal(init, McpSchema.FIRST_PAGE).doOnNext(listToolsResult -> {
return this.listToolsInternal(init, McpSchema.FIRST_PAGE, null).doOnNext(listToolsResult -> {
listToolsResult.tools()
.forEach(tool -> logger.debug("Tool {} schema: {}", tool.name(), tool.outputSchema()));
if (enableCallToolSchemaCaching && listToolsResult.tools() != null) {
@ -645,16 +645,27 @@ public class McpAsyncClient {
* @return A Mono that emits the list of tools result
*/
public Mono<McpSchema.ListToolsResult> listTools(String cursor) {
return this.initializer.withInitialization("listing tools", init -> this.listToolsInternal(init, cursor));
return this.initializer.withInitialization("listing tools", init -> this.listToolsInternal(init, cursor, null));
}
private Mono<McpSchema.ListToolsResult> listToolsInternal(Initialization init, String cursor) {
/**
* Retrieves a paginated list of tools with optional metadata.
* @param cursor Optional pagination cursor from a previous list request
* @param meta Optional metadata to include in the request (_meta field)
* @return A Mono that emits the list of tools result
*/
public Mono<McpSchema.ListToolsResult> listTools(String cursor, java.util.Map<String, Object> meta) {
return this.initializer.withInitialization("listing tools", init -> this.listToolsInternal(init, cursor, meta));
}
private Mono<McpSchema.ListToolsResult> listToolsInternal(Initialization init, String cursor,
java.util.Map<String, Object> meta) {
if (init.initializeResult().capabilities().tools() == null) {
return Mono.error(new IllegalStateException("Server does not provide tools capability"));
}
return init.mcpSession()
.sendRequest(McpSchema.METHOD_TOOLS_LIST, new McpSchema.PaginatedRequest(cursor),
.sendRequest(McpSchema.METHOD_TOOLS_LIST, new McpSchema.PaginatedRequest(cursor, meta),
LIST_TOOLS_RESULT_TYPE_REF)
.doOnNext(result -> {
// Validate tool names (warn only)
@ -725,12 +736,31 @@ public class McpAsyncClient {
* @see #readResource(McpSchema.Resource)
*/
public Mono<McpSchema.ListResourcesResult> listResources(String cursor) {
return this.listResourcesInternal(cursor, null);
}
/**
* Retrieves a paginated list of resources provided by the server. Resources represent
* any kind of UTF-8 encoded data that an MCP server makes available to clients, such
* as database records, API responses, log files, and more.
* @param cursor Optional pagination cursor from a previous list request
* @param meta Optional metadata to include in the request (_meta field)
* @return A Mono that completes with the list of resources result.
* @see McpSchema.ListResourcesResult
* @see #readResource(McpSchema.Resource)
*/
public Mono<McpSchema.ListResourcesResult> listResources(String cursor, java.util.Map<String, Object> meta) {
return this.listResourcesInternal(cursor, meta);
}
private Mono<McpSchema.ListResourcesResult> listResourcesInternal(String cursor,
java.util.Map<String, Object> meta) {
return this.initializer.withInitialization("listing resources", init -> {
if (init.initializeResult().capabilities().resources() == null) {
return Mono.error(new IllegalStateException("Server does not provide the resources capability"));
}
return init.mcpSession()
.sendRequest(McpSchema.METHOD_RESOURCES_LIST, new McpSchema.PaginatedRequest(cursor),
.sendRequest(McpSchema.METHOD_RESOURCES_LIST, new McpSchema.PaginatedRequest(cursor, meta),
LIST_RESOURCES_RESULT_TYPE_REF);
});
}
@ -795,12 +825,31 @@ public class McpAsyncClient {
* @see McpSchema.ListResourceTemplatesResult
*/
public Mono<McpSchema.ListResourceTemplatesResult> listResourceTemplates(String cursor) {
return this.listResourceTemplatesInternal(cursor, null);
}
/**
* Retrieves a paginated list of resource templates provided by the server. Resource
* templates allow servers to expose parameterized resources using URI templates,
* enabling dynamic resource access based on variable parameters.
* @param cursor Optional pagination cursor from a previous list request
* @param meta Optional metadata to include in the request (_meta field)
* @return A Mono that completes with the list of resource templates result.
* @see McpSchema.ListResourceTemplatesResult
*/
public Mono<McpSchema.ListResourceTemplatesResult> listResourceTemplates(String cursor,
java.util.Map<String, Object> meta) {
return this.listResourceTemplatesInternal(cursor, meta);
}
private Mono<McpSchema.ListResourceTemplatesResult> listResourceTemplatesInternal(String cursor,
java.util.Map<String, Object> meta) {
return this.initializer.withInitialization("listing resource templates", init -> {
if (init.initializeResult().capabilities().resources() == null) {
return Mono.error(new IllegalStateException("Server does not provide the resources capability"));
}
return init.mcpSession()
.sendRequest(McpSchema.METHOD_RESOURCES_TEMPLATES_LIST, new McpSchema.PaginatedRequest(cursor),
.sendRequest(McpSchema.METHOD_RESOURCES_TEMPLATES_LIST, new McpSchema.PaginatedRequest(cursor, meta),
LIST_RESOURCE_TEMPLATES_RESULT_TYPE_REF);
});
}
@ -895,8 +944,26 @@ public class McpAsyncClient {
* @see #getPrompt(GetPromptRequest)
*/
public Mono<ListPromptsResult> listPrompts(String cursor) {
return this.initializer.withInitialization("listing prompts", init -> init.mcpSession()
.sendRequest(McpSchema.METHOD_PROMPT_LIST, new PaginatedRequest(cursor), LIST_PROMPTS_RESULT_TYPE_REF));
return this.listPromptsInternal(cursor, null);
}
/**
* Retrieves a paginated list of prompts with optional metadata.
* @param cursor Optional pagination cursor from a previous list request
* @param meta Optional metadata to include in the request (_meta field)
* @return A Mono that completes with the list of prompts result.
* @see McpSchema.ListPromptsResult
* @see #getPrompt(GetPromptRequest)
*/
public Mono<ListPromptsResult> listPrompts(String cursor, java.util.Map<String, Object> meta) {
return this.listPromptsInternal(cursor, meta);
}
private Mono<ListPromptsResult> listPromptsInternal(String cursor, java.util.Map<String, Object> meta) {
return this.initializer.withInitialization("listing prompts",
init -> init.mcpSession()
.sendRequest(McpSchema.METHOD_PROMPT_LIST, new PaginatedRequest(cursor, meta),
LIST_PROMPTS_RESULT_TYPE_REF));
}
/**

View file

@ -259,6 +259,18 @@ public class McpSyncClient implements AutoCloseable {
}
/**
* Retrieves a paginated list of tools provided by the server.
* @param cursor Optional pagination cursor from a previous list request
* @param meta Optional metadata to include in the request (_meta field)
* @return The list of tools result containing: - tools: List of available tools, each
* with a name, description, and input schema - nextCursor: Optional cursor for
* pagination if more tools are available
*/
public McpSchema.ListToolsResult listTools(String cursor, java.util.Map<String, Object> meta) {
return withProvidedContext(this.delegate.listTools(cursor, meta)).block();
}
// --------------------------
// Resources
// --------------------------
@ -282,6 +294,17 @@ public class McpSyncClient implements AutoCloseable {
}
/**
* Retrieves a paginated list of resources with optional metadata.
* @param cursor Optional pagination cursor from a previous list request
* @param meta Optional metadata to include in the request (_meta field)
* @return The list of resources result
*/
public McpSchema.ListResourcesResult listResources(String cursor, java.util.Map<String, Object> meta) {
return withProvidedContext(this.delegate.listResources(cursor, meta)).block();
}
/**
* Send a resources/read request.
* @param resource the resource to read
@ -324,6 +347,21 @@ 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.
*
* Retrieves a paginated list of resource templates provided by the server.
* @param cursor Optional pagination cursor from a previous list request
* @param meta Optional metadata to include in the request (_meta field)
* @return The list of resource templates result.
*/
public McpSchema.ListResourceTemplatesResult listResourceTemplates(String cursor,
java.util.Map<String, Object> meta) {
return withProvidedContext(this.delegate.listResourceTemplates(cursor, meta)).block();
}
/**
* Subscriptions. The protocol supports optional subscriptions to resource changes.
* Clients can subscribe to specific resources and receive notifications when they
@ -370,6 +408,17 @@ public class McpSyncClient implements AutoCloseable {
}
/**
* Retrieves a paginated list of prompts provided by the server.
* @param cursor Optional pagination cursor from a previous list request
* @param meta Optional metadata to include in the request (_meta field)
* @return The list of prompts result.
*/
public ListPromptsResult listPrompts(String cursor, java.util.Map<String, Object> meta) {
return withProvidedContext(this.delegate.listPrompts(cursor, meta)).block();
}
public GetPromptResult getPrompt(GetPromptRequest getPromptRequest) {
return withProvidedContext(this.delegate.getPrompt(getPromptRequest)).block();
}

View file

@ -154,6 +154,19 @@ public abstract class AbstractMcpSyncClientTests {
});
}
@Test
void testListToolsWithMeta() {
withClient(createMcpTransport(), mcpSyncClient -> {
mcpSyncClient.initialize();
java.util.Map<String, Object> meta = java.util.Map.of("requestId", "test-123");
ListToolsResult tools = mcpSyncClient.listTools(McpSchema.FIRST_PAGE, meta);
assertThat(tools).isNotNull().satisfies(result -> {
assertThat(result.tools()).isNotNull().isNotEmpty();
});
});
}
@Test
void testListAllTools() {
withClient(createMcpTransport(), mcpSyncClient -> {
@ -678,4 +691,43 @@ public abstract class AbstractMcpSyncClientTests {
});
}
@Test
void testListResourcesWithMeta() {
withClient(createMcpTransport(), mcpSyncClient -> {
mcpSyncClient.initialize();
java.util.Map<String, Object> meta = java.util.Map.of("requestId", "test-123");
ListResourcesResult resources = mcpSyncClient.listResources(McpSchema.FIRST_PAGE, meta);
assertThat(resources).isNotNull().satisfies(result -> {
assertThat(result.resources()).isNotNull();
});
});
}
@Test
void testListResourceTemplatesWithMeta() {
withClient(createMcpTransport(), mcpSyncClient -> {
mcpSyncClient.initialize();
java.util.Map<String, Object> meta = java.util.Map.of("requestId", "test-123");
ListResourceTemplatesResult result = mcpSyncClient.listResourceTemplates(McpSchema.FIRST_PAGE, meta);
assertThat(result).isNotNull().satisfies(r -> {
assertThat(r.resourceTemplates()).isNotNull();
});
});
}
@Test
void testListPromptsWithMeta() {
withClient(createMcpTransport(), mcpSyncClient -> {
mcpSyncClient.initialize();
java.util.Map<String, Object> meta = java.util.Map.of("requestId", "test-123");
McpSchema.ListPromptsResult result = mcpSyncClient.listPrompts(McpSchema.FIRST_PAGE, meta);
assertThat(result).isNotNull().satisfies(r -> {
assertThat(r.prompts()).isNotNull();
});
});
}
}

View file

@ -307,4 +307,561 @@ class McpAsyncClientTests {
assertThat(names).containsExactlyInAnyOrder("subtract", "add");
}
@Test
void testListToolsWithCursorAndMeta() {
McpSchema.Tool addTool = McpSchema.Tool.builder().name("add").description("calculate add").build();
McpSchema.ListToolsResult mockToolsResult = new McpSchema.ListToolsResult(List.of(addTool), null);
// Use array to capture from anonymous class
McpSchema.PaginatedRequest[] capturedRequest = new McpSchema.PaginatedRequest[1];
McpClientTransport transport = new McpClientTransport() {
Function<Mono<McpSchema.JSONRPCMessage>, Mono<McpSchema.JSONRPCMessage>> handler;
@Override
public Mono<Void> connect(
Function<Mono<McpSchema.JSONRPCMessage>, Mono<McpSchema.JSONRPCMessage>> handler) {
return Mono.deferContextual(ctx -> {
this.handler = handler;
return Mono.empty();
});
}
@Override
public Mono<Void> closeGracefully() {
return Mono.empty();
}
@Override
public Mono<Void> sendMessage(McpSchema.JSONRPCMessage message) {
if (!(message instanceof McpSchema.JSONRPCRequest request)) {
return Mono.empty();
}
McpSchema.JSONRPCResponse response;
if (McpSchema.METHOD_INITIALIZE.equals(request.method())) {
response = new McpSchema.JSONRPCResponse(McpSchema.JSONRPC_VERSION, request.id(), MOCK_INIT_RESULT,
null);
}
else if (McpSchema.METHOD_TOOLS_LIST.equals(request.method())) {
capturedRequest[0] = JSON_MAPPER.convertValue(request.params(), McpSchema.PaginatedRequest.class);
response = new McpSchema.JSONRPCResponse(McpSchema.JSONRPC_VERSION, request.id(), mockToolsResult,
null);
}
else {
return Mono.empty();
}
return handler.apply(Mono.just(response)).then();
}
@Override
public <T> T unmarshalFrom(Object data, TypeRef<T> typeRef) {
return JSON_MAPPER.convertValue(data, new TypeRef<>() {
@Override
public java.lang.reflect.Type getType() {
return typeRef.getType();
}
});
}
};
McpAsyncClient client = McpClient.async(transport).build();
Map<String, Object> meta = Map.of("customKey", "customValue");
McpSchema.ListToolsResult toolsResult = client.listTools("cursor-1", meta).block();
assertThat(toolsResult).isNotNull();
assertThat(toolsResult.tools()).hasSize(1);
assertThat(capturedRequest[0]).isNotNull();
assertThat(capturedRequest[0].cursor()).isEqualTo("cursor-1");
assertThat(capturedRequest[0].meta()).containsEntry("customKey", "customValue");
}
@Test
void testSyncListToolsWithCursorAndMeta() {
McpSchema.Tool addTool = McpSchema.Tool.builder().name("add").description("calculate add").build();
McpSchema.ListToolsResult mockToolsResult = new McpSchema.ListToolsResult(List.of(addTool), null);
McpSchema.PaginatedRequest[] capturedRequest = new McpSchema.PaginatedRequest[1];
McpClientTransport transport = new McpClientTransport() {
Function<Mono<McpSchema.JSONRPCMessage>, Mono<McpSchema.JSONRPCMessage>> handler;
@Override
public Mono<Void> connect(
Function<Mono<McpSchema.JSONRPCMessage>, Mono<McpSchema.JSONRPCMessage>> handler) {
return Mono.deferContextual(ctx -> {
this.handler = handler;
return Mono.empty();
});
}
@Override
public Mono<Void> closeGracefully() {
return Mono.empty();
}
@Override
public Mono<Void> sendMessage(McpSchema.JSONRPCMessage message) {
if (!(message instanceof McpSchema.JSONRPCRequest request)) {
return Mono.empty();
}
McpSchema.JSONRPCResponse response;
if (McpSchema.METHOD_INITIALIZE.equals(request.method())) {
response = new McpSchema.JSONRPCResponse(McpSchema.JSONRPC_VERSION, request.id(), MOCK_INIT_RESULT,
null);
}
else if (McpSchema.METHOD_TOOLS_LIST.equals(request.method())) {
capturedRequest[0] = JSON_MAPPER.convertValue(request.params(), McpSchema.PaginatedRequest.class);
response = new McpSchema.JSONRPCResponse(McpSchema.JSONRPC_VERSION, request.id(), mockToolsResult,
null);
}
else {
return Mono.empty();
}
return handler.apply(Mono.just(response)).then();
}
@Override
public <T> T unmarshalFrom(Object data, TypeRef<T> typeRef) {
return JSON_MAPPER.convertValue(data, new TypeRef<>() {
@Override
public java.lang.reflect.Type getType() {
return typeRef.getType();
}
});
}
};
McpSyncClient client = McpClient.sync(transport).build();
Map<String, Object> meta = Map.of("requestId", "test-123");
McpSchema.ListToolsResult toolsResult = client.listTools("cursor-1", meta);
assertThat(toolsResult).isNotNull();
assertThat(toolsResult.tools()).hasSize(1);
assertThat(capturedRequest[0]).isNotNull();
assertThat(capturedRequest[0].cursor()).isEqualTo("cursor-1");
assertThat(capturedRequest[0].meta()).containsEntry("requestId", "test-123");
}
@Test
void testListResourcesWithCursorAndMeta() {
McpSchema.Resource mockResource = McpSchema.Resource.builder().uri("file:///test.txt").name("test.txt").build();
McpSchema.ListResourcesResult mockResult = new McpSchema.ListResourcesResult(List.of(mockResource), null);
McpSchema.ServerCapabilities caps = McpSchema.ServerCapabilities.builder().resources(false, false).build();
McpSchema.InitializeResult initResult = new McpSchema.InitializeResult(ProtocolVersions.MCP_2024_11_05, caps,
MOCK_SERVER_INFO, null);
McpSchema.PaginatedRequest[] capturedRequest = new McpSchema.PaginatedRequest[1];
McpClientTransport transport = new McpClientTransport() {
Function<Mono<McpSchema.JSONRPCMessage>, Mono<McpSchema.JSONRPCMessage>> handler;
@Override
public Mono<Void> connect(
Function<Mono<McpSchema.JSONRPCMessage>, Mono<McpSchema.JSONRPCMessage>> handler) {
return Mono.deferContextual(ctx -> {
this.handler = handler;
return Mono.empty();
});
}
@Override
public Mono<Void> closeGracefully() {
return Mono.empty();
}
@Override
public Mono<Void> sendMessage(McpSchema.JSONRPCMessage message) {
if (!(message instanceof McpSchema.JSONRPCRequest request)) {
return Mono.empty();
}
McpSchema.JSONRPCResponse response;
if (McpSchema.METHOD_INITIALIZE.equals(request.method())) {
response = new McpSchema.JSONRPCResponse(McpSchema.JSONRPC_VERSION, request.id(), initResult, null);
}
else if (McpSchema.METHOD_RESOURCES_LIST.equals(request.method())) {
capturedRequest[0] = JSON_MAPPER.convertValue(request.params(), McpSchema.PaginatedRequest.class);
response = new McpSchema.JSONRPCResponse(McpSchema.JSONRPC_VERSION, request.id(), mockResult, null);
}
else {
return Mono.empty();
}
return handler.apply(Mono.just(response)).then();
}
@Override
public <T> T unmarshalFrom(Object data, TypeRef<T> typeRef) {
return JSON_MAPPER.convertValue(data, new TypeRef<>() {
@Override
public java.lang.reflect.Type getType() {
return typeRef.getType();
}
});
}
};
McpAsyncClient client = McpClient.async(transport).build();
Map<String, Object> meta = Map.of("customKey", "customValue");
McpSchema.ListResourcesResult result = client.listResources("cursor-1", meta).block();
assertThat(result).isNotNull();
assertThat(result.resources()).hasSize(1);
assertThat(capturedRequest[0]).isNotNull();
assertThat(capturedRequest[0].cursor()).isEqualTo("cursor-1");
assertThat(capturedRequest[0].meta()).containsEntry("customKey", "customValue");
}
@Test
void testSyncListResourcesWithCursorAndMeta() {
McpSchema.Resource mockResource = McpSchema.Resource.builder().uri("file:///test.txt").name("test.txt").build();
McpSchema.ListResourcesResult mockResult = new McpSchema.ListResourcesResult(List.of(mockResource), null);
McpSchema.ServerCapabilities caps = McpSchema.ServerCapabilities.builder().resources(false, false).build();
McpSchema.InitializeResult initResult = new McpSchema.InitializeResult(ProtocolVersions.MCP_2024_11_05, caps,
MOCK_SERVER_INFO, null);
McpSchema.PaginatedRequest[] capturedRequest = new McpSchema.PaginatedRequest[1];
McpClientTransport transport = new McpClientTransport() {
Function<Mono<McpSchema.JSONRPCMessage>, Mono<McpSchema.JSONRPCMessage>> handler;
@Override
public Mono<Void> connect(
Function<Mono<McpSchema.JSONRPCMessage>, Mono<McpSchema.JSONRPCMessage>> handler) {
return Mono.deferContextual(ctx -> {
this.handler = handler;
return Mono.empty();
});
}
@Override
public Mono<Void> closeGracefully() {
return Mono.empty();
}
@Override
public Mono<Void> sendMessage(McpSchema.JSONRPCMessage message) {
if (!(message instanceof McpSchema.JSONRPCRequest request)) {
return Mono.empty();
}
McpSchema.JSONRPCResponse response;
if (McpSchema.METHOD_INITIALIZE.equals(request.method())) {
response = new McpSchema.JSONRPCResponse(McpSchema.JSONRPC_VERSION, request.id(), initResult, null);
}
else if (McpSchema.METHOD_RESOURCES_LIST.equals(request.method())) {
capturedRequest[0] = JSON_MAPPER.convertValue(request.params(), McpSchema.PaginatedRequest.class);
response = new McpSchema.JSONRPCResponse(McpSchema.JSONRPC_VERSION, request.id(), mockResult, null);
}
else {
return Mono.empty();
}
return handler.apply(Mono.just(response)).then();
}
@Override
public <T> T unmarshalFrom(Object data, TypeRef<T> typeRef) {
return JSON_MAPPER.convertValue(data, new TypeRef<>() {
@Override
public java.lang.reflect.Type getType() {
return typeRef.getType();
}
});
}
};
McpSyncClient client = McpClient.sync(transport).build();
Map<String, Object> meta = Map.of("customKey", "customValue");
McpSchema.ListResourcesResult result = client.listResources("cursor-1", meta);
assertThat(result).isNotNull();
assertThat(result.resources()).hasSize(1);
assertThat(capturedRequest[0]).isNotNull();
assertThat(capturedRequest[0].cursor()).isEqualTo("cursor-1");
assertThat(capturedRequest[0].meta()).containsEntry("customKey", "customValue");
}
@Test
void testListResourceTemplatesWithCursorAndMeta() {
McpSchema.ResourceTemplate mockTemplate = new McpSchema.ResourceTemplate("file:///{name}", "template", null,
null, null);
McpSchema.ListResourceTemplatesResult mockResult = new McpSchema.ListResourceTemplatesResult(
List.of(mockTemplate), null);
McpSchema.ServerCapabilities caps = McpSchema.ServerCapabilities.builder().resources(false, false).build();
McpSchema.InitializeResult initResult = new McpSchema.InitializeResult(ProtocolVersions.MCP_2024_11_05, caps,
MOCK_SERVER_INFO, null);
McpSchema.PaginatedRequest[] capturedRequest = new McpSchema.PaginatedRequest[1];
McpClientTransport transport = new McpClientTransport() {
Function<Mono<McpSchema.JSONRPCMessage>, Mono<McpSchema.JSONRPCMessage>> handler;
@Override
public Mono<Void> connect(
Function<Mono<McpSchema.JSONRPCMessage>, Mono<McpSchema.JSONRPCMessage>> handler) {
return Mono.deferContextual(ctx -> {
this.handler = handler;
return Mono.empty();
});
}
@Override
public Mono<Void> closeGracefully() {
return Mono.empty();
}
@Override
public Mono<Void> sendMessage(McpSchema.JSONRPCMessage message) {
if (!(message instanceof McpSchema.JSONRPCRequest request)) {
return Mono.empty();
}
McpSchema.JSONRPCResponse response;
if (McpSchema.METHOD_INITIALIZE.equals(request.method())) {
response = new McpSchema.JSONRPCResponse(McpSchema.JSONRPC_VERSION, request.id(), initResult, null);
}
else if (McpSchema.METHOD_RESOURCES_TEMPLATES_LIST.equals(request.method())) {
capturedRequest[0] = JSON_MAPPER.convertValue(request.params(), McpSchema.PaginatedRequest.class);
response = new McpSchema.JSONRPCResponse(McpSchema.JSONRPC_VERSION, request.id(), mockResult, null);
}
else {
return Mono.empty();
}
return handler.apply(Mono.just(response)).then();
}
@Override
public <T> T unmarshalFrom(Object data, TypeRef<T> typeRef) {
return JSON_MAPPER.convertValue(data, new TypeRef<>() {
@Override
public java.lang.reflect.Type getType() {
return typeRef.getType();
}
});
}
};
McpAsyncClient client = McpClient.async(transport).build();
Map<String, Object> meta = Map.of("customKey", "customValue");
McpSchema.ListResourceTemplatesResult result = client.listResourceTemplates("cursor-1", meta).block();
assertThat(result).isNotNull();
assertThat(result.resourceTemplates()).hasSize(1);
assertThat(capturedRequest[0]).isNotNull();
assertThat(capturedRequest[0].cursor()).isEqualTo("cursor-1");
assertThat(capturedRequest[0].meta()).containsEntry("customKey", "customValue");
}
@Test
void testSyncListResourceTemplatesWithCursorAndMeta() {
McpSchema.ResourceTemplate mockTemplate = new McpSchema.ResourceTemplate("file:///{name}", "template", null,
null, null);
McpSchema.ListResourceTemplatesResult mockResult = new McpSchema.ListResourceTemplatesResult(
List.of(mockTemplate), null);
McpSchema.ServerCapabilities caps = McpSchema.ServerCapabilities.builder().resources(false, false).build();
McpSchema.InitializeResult initResult = new McpSchema.InitializeResult(ProtocolVersions.MCP_2024_11_05, caps,
MOCK_SERVER_INFO, null);
McpSchema.PaginatedRequest[] capturedRequest = new McpSchema.PaginatedRequest[1];
McpClientTransport transport = new McpClientTransport() {
Function<Mono<McpSchema.JSONRPCMessage>, Mono<McpSchema.JSONRPCMessage>> handler;
@Override
public Mono<Void> connect(
Function<Mono<McpSchema.JSONRPCMessage>, Mono<McpSchema.JSONRPCMessage>> handler) {
return Mono.deferContextual(ctx -> {
this.handler = handler;
return Mono.empty();
});
}
@Override
public Mono<Void> closeGracefully() {
return Mono.empty();
}
@Override
public Mono<Void> sendMessage(McpSchema.JSONRPCMessage message) {
if (!(message instanceof McpSchema.JSONRPCRequest request)) {
return Mono.empty();
}
McpSchema.JSONRPCResponse response;
if (McpSchema.METHOD_INITIALIZE.equals(request.method())) {
response = new McpSchema.JSONRPCResponse(McpSchema.JSONRPC_VERSION, request.id(), initResult, null);
}
else if (McpSchema.METHOD_RESOURCES_TEMPLATES_LIST.equals(request.method())) {
capturedRequest[0] = JSON_MAPPER.convertValue(request.params(), McpSchema.PaginatedRequest.class);
response = new McpSchema.JSONRPCResponse(McpSchema.JSONRPC_VERSION, request.id(), mockResult, null);
}
else {
return Mono.empty();
}
return handler.apply(Mono.just(response)).then();
}
@Override
public <T> T unmarshalFrom(Object data, TypeRef<T> typeRef) {
return JSON_MAPPER.convertValue(data, new TypeRef<>() {
@Override
public java.lang.reflect.Type getType() {
return typeRef.getType();
}
});
}
};
McpSyncClient client = McpClient.sync(transport).build();
Map<String, Object> meta = Map.of("customKey", "customValue");
McpSchema.ListResourceTemplatesResult result = client.listResourceTemplates("cursor-1", meta);
assertThat(result).isNotNull();
assertThat(result.resourceTemplates()).hasSize(1);
assertThat(capturedRequest[0]).isNotNull();
assertThat(capturedRequest[0].cursor()).isEqualTo("cursor-1");
assertThat(capturedRequest[0].meta()).containsEntry("customKey", "customValue");
}
@Test
void testListPromptsWithCursorAndMeta() {
McpSchema.Prompt mockPrompt = new McpSchema.Prompt("test-prompt", "A test prompt", List.of());
McpSchema.ListPromptsResult mockResult = new McpSchema.ListPromptsResult(List.of(mockPrompt), null);
McpSchema.ServerCapabilities caps = McpSchema.ServerCapabilities.builder().prompts(false).build();
McpSchema.InitializeResult initResult = new McpSchema.InitializeResult(ProtocolVersions.MCP_2024_11_05, caps,
MOCK_SERVER_INFO, null);
McpSchema.PaginatedRequest[] capturedRequest = new McpSchema.PaginatedRequest[1];
McpClientTransport transport = new McpClientTransport() {
Function<Mono<McpSchema.JSONRPCMessage>, Mono<McpSchema.JSONRPCMessage>> handler;
@Override
public Mono<Void> connect(
Function<Mono<McpSchema.JSONRPCMessage>, Mono<McpSchema.JSONRPCMessage>> handler) {
return Mono.deferContextual(ctx -> {
this.handler = handler;
return Mono.empty();
});
}
@Override
public Mono<Void> closeGracefully() {
return Mono.empty();
}
@Override
public Mono<Void> sendMessage(McpSchema.JSONRPCMessage message) {
if (!(message instanceof McpSchema.JSONRPCRequest request)) {
return Mono.empty();
}
McpSchema.JSONRPCResponse response;
if (McpSchema.METHOD_INITIALIZE.equals(request.method())) {
response = new McpSchema.JSONRPCResponse(McpSchema.JSONRPC_VERSION, request.id(), initResult, null);
}
else if (McpSchema.METHOD_PROMPT_LIST.equals(request.method())) {
capturedRequest[0] = JSON_MAPPER.convertValue(request.params(), McpSchema.PaginatedRequest.class);
response = new McpSchema.JSONRPCResponse(McpSchema.JSONRPC_VERSION, request.id(), mockResult, null);
}
else {
return Mono.empty();
}
return handler.apply(Mono.just(response)).then();
}
@Override
public <T> T unmarshalFrom(Object data, TypeRef<T> typeRef) {
return JSON_MAPPER.convertValue(data, new TypeRef<>() {
@Override
public java.lang.reflect.Type getType() {
return typeRef.getType();
}
});
}
};
McpAsyncClient client = McpClient.async(transport).build();
Map<String, Object> meta = Map.of("customKey", "customValue");
McpSchema.ListPromptsResult result = client.listPrompts("cursor-1", meta).block();
assertThat(result).isNotNull();
assertThat(result.prompts()).hasSize(1);
assertThat(capturedRequest[0]).isNotNull();
assertThat(capturedRequest[0].cursor()).isEqualTo("cursor-1");
assertThat(capturedRequest[0].meta()).containsEntry("customKey", "customValue");
}
@Test
void testSyncListPromptsWithCursorAndMeta() {
McpSchema.Prompt mockPrompt = new McpSchema.Prompt("test-prompt", "A test prompt", List.of());
McpSchema.ListPromptsResult mockResult = new McpSchema.ListPromptsResult(List.of(mockPrompt), null);
McpSchema.ServerCapabilities caps = McpSchema.ServerCapabilities.builder().prompts(false).build();
McpSchema.InitializeResult initResult = new McpSchema.InitializeResult(ProtocolVersions.MCP_2024_11_05, caps,
MOCK_SERVER_INFO, null);
McpSchema.PaginatedRequest[] capturedRequest = new McpSchema.PaginatedRequest[1];
McpClientTransport transport = new McpClientTransport() {
Function<Mono<McpSchema.JSONRPCMessage>, Mono<McpSchema.JSONRPCMessage>> handler;
@Override
public Mono<Void> connect(
Function<Mono<McpSchema.JSONRPCMessage>, Mono<McpSchema.JSONRPCMessage>> handler) {
return Mono.deferContextual(ctx -> {
this.handler = handler;
return Mono.empty();
});
}
@Override
public Mono<Void> closeGracefully() {
return Mono.empty();
}
@Override
public Mono<Void> sendMessage(McpSchema.JSONRPCMessage message) {
if (!(message instanceof McpSchema.JSONRPCRequest request)) {
return Mono.empty();
}
McpSchema.JSONRPCResponse response;
if (McpSchema.METHOD_INITIALIZE.equals(request.method())) {
response = new McpSchema.JSONRPCResponse(McpSchema.JSONRPC_VERSION, request.id(), initResult, null);
}
else if (McpSchema.METHOD_PROMPT_LIST.equals(request.method())) {
capturedRequest[0] = JSON_MAPPER.convertValue(request.params(), McpSchema.PaginatedRequest.class);
response = new McpSchema.JSONRPCResponse(McpSchema.JSONRPC_VERSION, request.id(), mockResult, null);
}
else {
return Mono.empty();
}
return handler.apply(Mono.just(response)).then();
}
@Override
public <T> T unmarshalFrom(Object data, TypeRef<T> typeRef) {
return JSON_MAPPER.convertValue(data, new TypeRef<>() {
@Override
public java.lang.reflect.Type getType() {
return typeRef.getType();
}
});
}
};
McpSyncClient client = McpClient.sync(transport).build();
Map<String, Object> meta = Map.of("customKey", "customValue");
McpSchema.ListPromptsResult result = client.listPrompts("cursor-1", meta);
assertThat(result).isNotNull();
assertThat(result.prompts()).hasSize(1);
assertThat(capturedRequest[0]).isNotNull();
assertThat(capturedRequest[0].cursor()).isEqualTo("cursor-1");
assertThat(capturedRequest[0].meta()).containsEntry("customKey", "customValue");
}
}