Skip to content

Commit de11591

Browse files
author
SHEETAL MOHITE
committed
feat: add support for meta parameter in listTools
1 parent 8fd9903 commit de11591

4 files changed

Lines changed: 177 additions & 4 deletions

File tree

mcp-core/src/main/java/io/modelcontextprotocol/client/McpAsyncClient.java

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -303,7 +303,7 @@ public class McpAsyncClient {
303303
return Mono.empty();
304304
}
305305

306-
return this.listToolsInternal(init, McpSchema.FIRST_PAGE).doOnNext(listToolsResult -> {
306+
return this.listToolsInternal(init, McpSchema.FIRST_PAGE, null).doOnNext(listToolsResult -> {
307307
listToolsResult.tools()
308308
.forEach(tool -> logger.debug("Tool {} schema: {}", tool.name(), tool.outputSchema()));
309309
if (enableCallToolSchemaCaching && listToolsResult.tools() != null) {
@@ -645,16 +645,27 @@ public Mono<McpSchema.ListToolsResult> listTools() {
645645
* @return A Mono that emits the list of tools result
646646
*/
647647
public Mono<McpSchema.ListToolsResult> listTools(String cursor) {
648-
return this.initializer.withInitialization("listing tools", init -> this.listToolsInternal(init, cursor));
648+
return this.initializer.withInitialization("listing tools", init -> this.listToolsInternal(init, cursor, null));
649649
}
650650

651-
private Mono<McpSchema.ListToolsResult> listToolsInternal(Initialization init, String cursor) {
651+
/**
652+
* Retrieves a paginated list of tools with optional metadata.
653+
* @param cursor Optional pagination cursor from a previous list request
654+
* @param meta Optional metadata to include in the request (_meta field)
655+
* @return A Mono that emits the list of tools result
656+
*/
657+
public Mono<McpSchema.ListToolsResult> listTools(String cursor, java.util.Map<String, Object> meta) {
658+
return this.initializer.withInitialization("listing tools", init -> this.listToolsInternal(init, cursor, meta));
659+
}
660+
661+
private Mono<McpSchema.ListToolsResult> listToolsInternal(Initialization init, String cursor,
662+
java.util.Map<String, Object> meta) {
652663

653664
if (init.initializeResult().capabilities().tools() == null) {
654665
return Mono.error(new IllegalStateException("Server does not provide tools capability"));
655666
}
656667
return init.mcpSession()
657-
.sendRequest(McpSchema.METHOD_TOOLS_LIST, new McpSchema.PaginatedRequest(cursor),
668+
.sendRequest(McpSchema.METHOD_TOOLS_LIST, new McpSchema.PaginatedRequest(cursor, meta),
658669
LIST_TOOLS_RESULT_TYPE_REF)
659670
.doOnNext(result -> {
660671
// Validate tool names (warn only)

mcp-core/src/main/java/io/modelcontextprotocol/client/McpSyncClient.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,16 @@ public McpSchema.ListToolsResult listTools(String cursor) {
259259

260260
}
261261

262+
/**
263+
* Retrieves a paginated list of tools with optional metadata.
264+
* @param cursor Optional pagination cursor from a previous list request
265+
* @param meta Optional metadata to include in the request (_meta field)
266+
* @return The list of tools result
267+
*/
268+
public McpSchema.ListToolsResult listTools(String cursor, java.util.Map<String, Object> meta) {
269+
return withProvidedContext(this.delegate.listTools(cursor, meta)).block();
270+
}
271+
262272
// --------------------------
263273
// Resources
264274
// --------------------------

mcp-test/src/main/java/io/modelcontextprotocol/client/AbstractMcpSyncClientTests.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,19 @@ void testListTools() {
154154
});
155155
}
156156

157+
@Test
158+
void testListToolsWithMeta() {
159+
withClient(createMcpTransport(), mcpSyncClient -> {
160+
mcpSyncClient.initialize();
161+
java.util.Map<String, Object> meta = java.util.Map.of("requestId", "test-123");
162+
ListToolsResult tools = mcpSyncClient.listTools(McpSchema.FIRST_PAGE, meta);
163+
164+
assertThat(tools).isNotNull().satisfies(result -> {
165+
assertThat(result.tools()).isNotNull().isNotEmpty();
166+
});
167+
});
168+
}
169+
157170
@Test
158171
void testListAllTools() {
159172
withClient(createMcpTransport(), mcpSyncClient -> {

mcp-test/src/test/java/io/modelcontextprotocol/client/McpAsyncClientTests.java

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -307,4 +307,143 @@ public java.lang.reflect.Type getType() {
307307
assertThat(names).containsExactlyInAnyOrder("subtract", "add");
308308
}
309309

310+
@Test
311+
void testListToolsWithCursorAndMeta() {
312+
McpSchema.Tool addTool = McpSchema.Tool.builder().name("add").description("calculate add").build();
313+
McpSchema.ListToolsResult mockToolsResult = new McpSchema.ListToolsResult(List.of(addTool), null);
314+
315+
// Use array to capture from anonymous class
316+
McpSchema.PaginatedRequest[] capturedRequest = new McpSchema.PaginatedRequest[1];
317+
318+
McpClientTransport transport = new McpClientTransport() {
319+
Function<Mono<McpSchema.JSONRPCMessage>, Mono<McpSchema.JSONRPCMessage>> handler;
320+
321+
@Override
322+
public Mono<Void> connect(
323+
Function<Mono<McpSchema.JSONRPCMessage>, Mono<McpSchema.JSONRPCMessage>> handler) {
324+
return Mono.deferContextual(ctx -> {
325+
this.handler = handler;
326+
return Mono.empty();
327+
});
328+
}
329+
330+
@Override
331+
public Mono<Void> closeGracefully() {
332+
return Mono.empty();
333+
}
334+
335+
@Override
336+
public Mono<Void> sendMessage(McpSchema.JSONRPCMessage message) {
337+
if (!(message instanceof McpSchema.JSONRPCRequest request)) {
338+
return Mono.empty();
339+
}
340+
341+
McpSchema.JSONRPCResponse response;
342+
if (McpSchema.METHOD_INITIALIZE.equals(request.method())) {
343+
response = new McpSchema.JSONRPCResponse(McpSchema.JSONRPC_VERSION, request.id(), MOCK_INIT_RESULT,
344+
null);
345+
}
346+
else if (McpSchema.METHOD_TOOLS_LIST.equals(request.method())) {
347+
capturedRequest[0] = JSON_MAPPER.convertValue(request.params(), McpSchema.PaginatedRequest.class);
348+
response = new McpSchema.JSONRPCResponse(McpSchema.JSONRPC_VERSION, request.id(), mockToolsResult,
349+
null);
350+
}
351+
else {
352+
return Mono.empty();
353+
}
354+
355+
return handler.apply(Mono.just(response)).then();
356+
}
357+
358+
@Override
359+
public <T> T unmarshalFrom(Object data, TypeRef<T> typeRef) {
360+
return JSON_MAPPER.convertValue(data, new TypeRef<>() {
361+
@Override
362+
public java.lang.reflect.Type getType() {
363+
return typeRef.getType();
364+
}
365+
});
366+
}
367+
};
368+
369+
McpAsyncClient client = McpClient.async(transport).build();
370+
371+
Map<String, Object> meta = Map.of("customKey", "customValue");
372+
McpSchema.ListToolsResult toolsResult = client.listTools("cursor-1", meta).block();
373+
assertThat(toolsResult).isNotNull();
374+
assertThat(toolsResult.tools()).hasSize(1);
375+
assertThat(capturedRequest[0]).isNotNull();
376+
assertThat(capturedRequest[0].cursor()).isEqualTo("cursor-1");
377+
assertThat(capturedRequest[0].meta()).containsEntry("customKey", "customValue");
378+
}
379+
380+
@Test
381+
void testSyncListToolsWithCursorAndMeta() {
382+
McpSchema.Tool addTool = McpSchema.Tool.builder().name("add").description("calculate add").build();
383+
McpSchema.ListToolsResult mockToolsResult = new McpSchema.ListToolsResult(List.of(addTool), null);
384+
385+
McpSchema.PaginatedRequest[] capturedRequest = new McpSchema.PaginatedRequest[1];
386+
387+
McpClientTransport transport = new McpClientTransport() {
388+
Function<Mono<McpSchema.JSONRPCMessage>, Mono<McpSchema.JSONRPCMessage>> handler;
389+
390+
@Override
391+
public Mono<Void> connect(
392+
Function<Mono<McpSchema.JSONRPCMessage>, Mono<McpSchema.JSONRPCMessage>> handler) {
393+
return Mono.deferContextual(ctx -> {
394+
this.handler = handler;
395+
return Mono.empty();
396+
});
397+
}
398+
399+
@Override
400+
public Mono<Void> closeGracefully() {
401+
return Mono.empty();
402+
}
403+
404+
@Override
405+
public Mono<Void> sendMessage(McpSchema.JSONRPCMessage message) {
406+
if (!(message instanceof McpSchema.JSONRPCRequest request)) {
407+
return Mono.empty();
408+
}
409+
410+
McpSchema.JSONRPCResponse response;
411+
if (McpSchema.METHOD_INITIALIZE.equals(request.method())) {
412+
response = new McpSchema.JSONRPCResponse(McpSchema.JSONRPC_VERSION, request.id(), MOCK_INIT_RESULT,
413+
null);
414+
}
415+
else if (McpSchema.METHOD_TOOLS_LIST.equals(request.method())) {
416+
capturedRequest[0] = JSON_MAPPER.convertValue(request.params(), McpSchema.PaginatedRequest.class);
417+
response = new McpSchema.JSONRPCResponse(McpSchema.JSONRPC_VERSION, request.id(), mockToolsResult,
418+
null);
419+
}
420+
else {
421+
return Mono.empty();
422+
}
423+
424+
return handler.apply(Mono.just(response)).then();
425+
}
426+
427+
@Override
428+
public <T> T unmarshalFrom(Object data, TypeRef<T> typeRef) {
429+
return JSON_MAPPER.convertValue(data, new TypeRef<>() {
430+
@Override
431+
public java.lang.reflect.Type getType() {
432+
return typeRef.getType();
433+
}
434+
});
435+
}
436+
};
437+
438+
McpSyncClient client = McpClient.sync(transport).build();
439+
440+
Map<String, Object> meta = Map.of("requestId", "test-123");
441+
McpSchema.ListToolsResult toolsResult = client.listTools("cursor-1", meta);
442+
assertThat(toolsResult).isNotNull();
443+
assertThat(toolsResult.tools()).hasSize(1);
444+
assertThat(capturedRequest[0]).isNotNull();
445+
assertThat(capturedRequest[0].cursor()).isEqualTo("cursor-1");
446+
assertThat(capturedRequest[0].meta()).containsEntry("requestId", "test-123");
447+
}
448+
310449
}

0 commit comments

Comments
 (0)