Skip to content

Commit 37c342c

Browse files
committed
feat: refactor the tool specifications
- Deprecate AsyncToolSpecification in favor of AsyncToolCallSpecification - Deprecate SyncToolSpecification in favor of SyncToolCallSpecification - Tool handlers now receive CallToolRequest instead of raw arguments to enable access to additional metadata parameters like progressToken - Add toolCall() and toolCalls() builder methods alongside deprecated variants - Update addTool() methods to support new tool call specifications - Maintain backward compatibility through deprecation annotations - Add tests for new tool call functionality This change allows tool handlers to process additional metadata parameters from the full CallToolRequest object, enabling features like progress tracking. Signed-off-by: Christian Tzolov <christian.tzolov@broadcom.com>
1 parent d9006ec commit 37c342c

File tree

8 files changed

+457
-93
lines changed

8 files changed

+457
-93
lines changed

mcp-test/src/main/java/io/modelcontextprotocol/server/AbstractMcpAsyncServerTests.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ void testImmediateClose() {
102102
""";
103103

104104
@Test
105+
@Deprecated
105106
void testAddTool() {
106107
Tool newTool = new McpSchema.Tool("new-tool", "New test tool", emptyJsonSchema);
107108
var mcpAsyncServer = McpServer.async(createMcpTransportProvider())
@@ -116,6 +117,21 @@ void testAddTool() {
116117
assertThatCode(() -> mcpAsyncServer.closeGracefully().block(Duration.ofSeconds(10))).doesNotThrowAnyException();
117118
}
118119

120+
@Test
121+
void testAddToolCall() {
122+
Tool newTool = new McpSchema.Tool("new-tool", "New test tool", emptyJsonSchema);
123+
var mcpAsyncServer = McpServer.async(createMcpTransportProvider())
124+
.serverInfo("test-server", "1.0.0")
125+
.capabilities(ServerCapabilities.builder().tools(true).build())
126+
.build();
127+
128+
StepVerifier.create(mcpAsyncServer.addTool(new McpServerFeatures.AsyncToolCallSpecification(newTool,
129+
(exchange, request) -> Mono.just(new CallToolResult(List.of(), false)))))
130+
.verifyComplete();
131+
132+
assertThatCode(() -> mcpAsyncServer.closeGracefully().block(Duration.ofSeconds(10))).doesNotThrowAnyException();
133+
}
134+
119135
@Test
120136
void testAddDuplicateTool() {
121137
Tool duplicateTool = new McpSchema.Tool(TEST_TOOL_NAME, "Duplicate tool", emptyJsonSchema);

mcp-test/src/main/java/io/modelcontextprotocol/server/AbstractMcpSyncServerTests.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ void testGetAsyncServer() {
109109
""";
110110

111111
@Test
112+
@Deprecated
112113
void testAddTool() {
113114
var mcpSyncServer = McpServer.sync(createMcpTransportProvider())
114115
.serverInfo("test-server", "1.0.0")
@@ -123,6 +124,21 @@ void testAddTool() {
123124
assertThatCode(() -> mcpSyncServer.closeGracefully()).doesNotThrowAnyException();
124125
}
125126

127+
@Test
128+
void testAddToolCall() {
129+
var mcpSyncServer = McpServer.sync(createMcpTransportProvider())
130+
.serverInfo("test-server", "1.0.0")
131+
.capabilities(ServerCapabilities.builder().tools(true).build())
132+
.build();
133+
134+
Tool newTool = new McpSchema.Tool("new-tool", "New test tool", emptyJsonSchema);
135+
assertThatCode(() -> mcpSyncServer.addTool(new McpServerFeatures.SyncToolCallSpecification(newTool,
136+
(exchange, request) -> new CallToolResult(List.of(), false))))
137+
.doesNotThrowAnyException();
138+
139+
assertThatCode(() -> mcpSyncServer.closeGracefully()).doesNotThrowAnyException();
140+
}
141+
126142
@Test
127143
void testAddDuplicateTool() {
128144
Tool duplicateTool = new McpSchema.Tool(TEST_TOOL_NAME, "Duplicate tool", emptyJsonSchema);

mcp/src/main/java/io/modelcontextprotocol/server/McpAsyncServer.java

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ public class McpAsyncServer {
9292

9393
private final String instructions;
9494

95-
private final CopyOnWriteArrayList<McpServerFeatures.AsyncToolSpecification> tools = new CopyOnWriteArrayList<>();
95+
private final CopyOnWriteArrayList<McpServerFeatures.AsyncToolCallSpecification> tools = new CopyOnWriteArrayList<>();
9696

9797
private final CopyOnWriteArrayList<McpSchema.ResourceTemplate> resourceTemplates = new CopyOnWriteArrayList<>();
9898

@@ -271,15 +271,27 @@ private McpServerSession.NotificationHandler asyncRootsListChangedNotificationHa
271271
* Add a new tool specification at runtime.
272272
* @param toolSpecification The tool specification to add
273273
* @return Mono that completes when clients have been notified of the change
274+
* @deprecated Use {@link #addTool(McpServerFeatures.AsyncToolCallSpecification)}
275+
* instead.
274276
*/
277+
@Deprecated
275278
public Mono<Void> addTool(McpServerFeatures.AsyncToolSpecification toolSpecification) {
276-
if (toolSpecification == null) {
279+
return addTool(toolSpecification.toToolCall());
280+
}
281+
282+
/**
283+
* Add a new tool call specification at runtime.
284+
* @param toolCallSpecification The tool specification to add
285+
* @return Mono that completes when clients have been notified of the change
286+
*/
287+
public Mono<Void> addTool(McpServerFeatures.AsyncToolCallSpecification toolCallSpecification) {
288+
if (toolCallSpecification == null) {
277289
return Mono.error(new McpError("Tool specification must not be null"));
278290
}
279-
if (toolSpecification.tool() == null) {
291+
if (toolCallSpecification.tool() == null) {
280292
return Mono.error(new McpError("Tool must not be null"));
281293
}
282-
if (toolSpecification.call() == null) {
294+
if (toolCallSpecification.call() == null) {
283295
return Mono.error(new McpError("Tool call handler must not be null"));
284296
}
285297
if (this.serverCapabilities.tools() == null) {
@@ -288,13 +300,13 @@ public Mono<Void> addTool(McpServerFeatures.AsyncToolSpecification toolSpecifica
288300

289301
return Mono.defer(() -> {
290302
// Check for duplicate tool names
291-
if (this.tools.stream().anyMatch(th -> th.tool().name().equals(toolSpecification.tool().name()))) {
303+
if (this.tools.stream().anyMatch(th -> th.tool().name().equals(toolCallSpecification.tool().name()))) {
292304
return Mono
293-
.error(new McpError("Tool with name '" + toolSpecification.tool().name() + "' already exists"));
305+
.error(new McpError("Tool with name '" + toolCallSpecification.tool().name() + "' already exists"));
294306
}
295307

296-
this.tools.add(toolSpecification);
297-
logger.debug("Added tool handler: {}", toolSpecification.tool().name());
308+
this.tools.add(toolCallSpecification);
309+
logger.debug("Added tool handler: {}", toolCallSpecification.tool().name());
298310

299311
if (this.serverCapabilities.tools().listChanged()) {
300312
return notifyToolsListChanged();
@@ -340,7 +352,7 @@ public Mono<Void> notifyToolsListChanged() {
340352

341353
private McpServerSession.RequestHandler<McpSchema.ListToolsResult> toolsListRequestHandler() {
342354
return (exchange, params) -> {
343-
List<Tool> tools = this.tools.stream().map(McpServerFeatures.AsyncToolSpecification::tool).toList();
355+
List<Tool> tools = this.tools.stream().map(McpServerFeatures.AsyncToolCallSpecification::tool).toList();
344356

345357
return Mono.just(new McpSchema.ListToolsResult(tools, null));
346358
};
@@ -352,15 +364,15 @@ private McpServerSession.RequestHandler<CallToolResult> toolsCallRequestHandler(
352364
new TypeReference<McpSchema.CallToolRequest>() {
353365
});
354366

355-
Optional<McpServerFeatures.AsyncToolSpecification> toolSpecification = this.tools.stream()
367+
Optional<McpServerFeatures.AsyncToolCallSpecification> toolSpecification = this.tools.stream()
356368
.filter(tr -> callToolRequest.name().equals(tr.tool().name()))
357369
.findAny();
358370

359371
if (toolSpecification.isEmpty()) {
360372
return Mono.error(new McpError("Tool not found: " + callToolRequest.name()));
361373
}
362374

363-
return toolSpecification.map(tool -> tool.call().apply(exchange, callToolRequest.arguments()))
375+
return toolSpecification.map(tool -> tool.call().apply(exchange, callToolRequest))
364376
.orElse(Mono.error(new McpError("Tool not found: " + callToolRequest.name())));
365377
};
366378
}

mcp/src/main/java/io/modelcontextprotocol/server/McpServer.java

Lines changed: 134 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ class AsyncSpecification {
175175
* Each tool is uniquely identified by a name and includes metadata describing its
176176
* schema.
177177
*/
178-
private final List<McpServerFeatures.AsyncToolSpecification> tools = new ArrayList<>();
178+
private final List<McpServerFeatures.AsyncToolCallSpecification> tools = new ArrayList<>();
179179

180180
/**
181181
* The Model Context Protocol (MCP) provides a standardized way for servers to
@@ -321,13 +321,40 @@ public AsyncSpecification capabilities(McpSchema.ServerCapabilities serverCapabi
321321
* map of arguments passed to the tool.
322322
* @return This builder instance for method chaining
323323
* @throws IllegalArgumentException if tool or handler is null
324+
* @deprecated Use {@link #toolCall(McpSchema.Tool, BiFunction)} instead for tool
325+
* calls that require a request object.
324326
*/
327+
@Deprecated
325328
public AsyncSpecification tool(McpSchema.Tool tool,
326329
BiFunction<McpAsyncServerExchange, Map<String, Object>, Mono<CallToolResult>> handler) {
327330
Assert.notNull(tool, "Tool must not be null");
328331
Assert.notNull(handler, "Handler must not be null");
329332

330-
this.tools.add(new McpServerFeatures.AsyncToolSpecification(tool, handler));
333+
this.tools.add(new McpServerFeatures.AsyncToolSpecification(tool, handler).toToolCall());
334+
335+
return this;
336+
}
337+
338+
/**
339+
* Adds a single tool with its implementation handler to the server. This is a
340+
* convenience method for registering individual tools without creating a
341+
* {@link McpServerFeatures.AsyncToolCallSpecification} explicitly.
342+
* @param tool The tool definition including name, description, and schema. Must
343+
* not be null.
344+
* @param handler The function that implements the tool's logic. Must not be null.
345+
* The function's first argument is an {@link McpAsyncServerExchange} upon which
346+
* the server can interact with the connected client. The second argument is the
347+
* {@link McpSchema.CallToolRequest} object containing the tool call
348+
* @return This builder instance for method chaining
349+
* @throws IllegalArgumentException if tool or handler is null
350+
*/
351+
public AsyncSpecification toolCall(McpSchema.Tool tool,
352+
BiFunction<McpAsyncServerExchange, McpSchema.CallToolRequest, Mono<CallToolResult>> handler) {
353+
354+
Assert.notNull(tool, "Tool must not be null");
355+
Assert.notNull(handler, "Handler must not be null");
356+
357+
this.tools.add(new McpServerFeatures.AsyncToolCallSpecification(tool, handler));
331358

332359
return this;
333360
}
@@ -341,10 +368,29 @@ public AsyncSpecification tool(McpSchema.Tool tool,
341368
* @return This builder instance for method chaining
342369
* @throws IllegalArgumentException if toolSpecifications is null
343370
* @see #tools(McpServerFeatures.AsyncToolSpecification...)
371+
* @deprecated Use {@link #toolCalls(List)} instead for adding multiple tool
372+
* calls.
344373
*/
374+
@Deprecated
345375
public AsyncSpecification tools(List<McpServerFeatures.AsyncToolSpecification> toolSpecifications) {
346376
Assert.notNull(toolSpecifications, "Tool handlers list must not be null");
347-
this.tools.addAll(toolSpecifications);
377+
this.tools.addAll(toolSpecifications.stream().map(s -> s.toToolCall()).toList());
378+
return this;
379+
}
380+
381+
/**
382+
* Adds multiple tools with their handlers to the server using a List. This method
383+
* is useful when tools are dynamically generated or loaded from a configuration
384+
* source.
385+
* @param toolCallSpecifications The list of tool specifications to add. Must not
386+
* be null.
387+
* @return This builder instance for method chaining
388+
* @throws IllegalArgumentException if toolSpecifications is null
389+
* @see #tools(McpServerFeatures.AsyncToolCallSpecification...)
390+
*/
391+
public AsyncSpecification toolCalls(List<McpServerFeatures.AsyncToolCallSpecification> toolCallSpecifications) {
392+
Assert.notNull(toolCallSpecifications, "Tool handlers list must not be null");
393+
this.tools.addAll(toolCallSpecifications);
348394
return this;
349395
}
350396

@@ -363,11 +409,28 @@ public AsyncSpecification tools(List<McpServerFeatures.AsyncToolSpecification> t
363409
* @param toolSpecifications The tool specifications to add. Must not be null.
364410
* @return This builder instance for method chaining
365411
* @throws IllegalArgumentException if toolSpecifications is null
366-
* @see #tools(List)
412+
* @deprecated Use
413+
* {@link #toolCalls(McpServerFeatures.AsyncToolCallSpecification...)} instead
367414
*/
415+
@Deprecated
368416
public AsyncSpecification tools(McpServerFeatures.AsyncToolSpecification... toolSpecifications) {
369417
Assert.notNull(toolSpecifications, "Tool handlers list must not be null");
370418
for (McpServerFeatures.AsyncToolSpecification tool : toolSpecifications) {
419+
this.tools.add(tool.toToolCall());
420+
}
421+
return this;
422+
}
423+
424+
/**
425+
* Adds multiple tools with their handlers to the server using varargs. This
426+
* method provides a convenient way to register multiple tools inline.
427+
* @param toolSpecifications The tool specifications to add. Must not be null.
428+
* @return This builder instance for method chaining
429+
* @throws IllegalArgumentException if toolSpecifications is null
430+
*/
431+
public AsyncSpecification toolCalls(McpServerFeatures.AsyncToolCallSpecification... toolCallSpecifications) {
432+
Assert.notNull(toolCallSpecifications, "Tool handlers list must not be null");
433+
for (McpServerFeatures.AsyncToolCallSpecification tool : toolCallSpecifications) {
371434
this.tools.add(tool);
372435
}
373436
return this;
@@ -667,7 +730,7 @@ class SyncSpecification {
667730
* Each tool is uniquely identified by a name and includes metadata describing its
668731
* schema.
669732
*/
670-
private final List<McpServerFeatures.SyncToolSpecification> tools = new ArrayList<>();
733+
private final List<McpServerFeatures.SyncToolCallSpecification> tools = new ArrayList<>();
671734

672735
/**
673736
* The Model Context Protocol (MCP) provides a standardized way for servers to
@@ -812,13 +875,39 @@ public SyncSpecification capabilities(McpSchema.ServerCapabilities serverCapabil
812875
* list of arguments passed to the tool.
813876
* @return This builder instance for method chaining
814877
* @throws IllegalArgumentException if tool or handler is null
878+
* @deprecated Use {@link #toolCall(McpSchema.Tool, BiFunction)} instead for tool
879+
* calls that require a request object.
815880
*/
881+
@Deprecated
816882
public SyncSpecification tool(McpSchema.Tool tool,
817883
BiFunction<McpSyncServerExchange, Map<String, Object>, McpSchema.CallToolResult> handler) {
818884
Assert.notNull(tool, "Tool must not be null");
819885
Assert.notNull(handler, "Handler must not be null");
820886

821-
this.tools.add(new McpServerFeatures.SyncToolSpecification(tool, handler));
887+
this.tools.add(new McpServerFeatures.SyncToolSpecification(tool, handler).toToolCall());
888+
889+
return this;
890+
}
891+
892+
/**
893+
* Adds a single tool with its implementation handler to the server. This is a
894+
* convenience method for registering individual tools without creating a
895+
* {@link McpServerFeatures.SyncToolSpecification} explicitly.
896+
* @param tool The tool definition including name, description, and schema. Must
897+
* not be null.
898+
* @param handler The function that implements the tool's logic. Must not be null.
899+
* The function's first argument is an {@link McpSyncServerExchange} upon which
900+
* the server can interact with the connected client. The second argument is the
901+
* list of arguments passed to the tool.
902+
* @return This builder instance for method chaining
903+
* @throws IllegalArgumentException if tool or handler is null
904+
*/
905+
public SyncSpecification toolCall(McpSchema.Tool tool,
906+
BiFunction<McpSyncServerExchange, McpSchema.CallToolRequest, McpSchema.CallToolResult> handler) {
907+
Assert.notNull(tool, "Tool must not be null");
908+
Assert.notNull(handler, "Handler must not be null");
909+
910+
this.tools.add(new McpServerFeatures.SyncToolCallSpecification(tool, handler));
822911

823912
return this;
824913
}
@@ -832,10 +921,28 @@ public SyncSpecification tool(McpSchema.Tool tool,
832921
* @return This builder instance for method chaining
833922
* @throws IllegalArgumentException if toolSpecifications is null
834923
* @see #tools(McpServerFeatures.SyncToolSpecification...)
924+
* @deprecated Use {@link #toolCalls(List)} instead for adding multiple tool
925+
* calls.
835926
*/
927+
@Deprecated
836928
public SyncSpecification tools(List<McpServerFeatures.SyncToolSpecification> toolSpecifications) {
837929
Assert.notNull(toolSpecifications, "Tool handlers list must not be null");
838-
this.tools.addAll(toolSpecifications);
930+
this.tools.addAll(toolSpecifications.stream().map(s -> s.toToolCall()).toList());
931+
return this;
932+
}
933+
934+
/**
935+
* Adds multiple tools with their handlers to the server using a List. This method
936+
* is useful when tools are dynamically generated or loaded from a configuration
937+
* source.
938+
* @param toolSpecifications The list of tool specifications to add. Must not be
939+
* null.
940+
* @return This builder instance for method chaining
941+
* @throws IllegalArgumentException if toolSpecifications is null
942+
*/
943+
public SyncSpecification toolCalls(List<McpServerFeatures.SyncToolCallSpecification> toolCallSpecifications) {
944+
Assert.notNull(toolCallSpecifications, "Tool handlers list must not be null");
945+
this.tools.addAll(toolCallSpecifications);
839946
return this;
840947
}
841948

@@ -855,10 +962,30 @@ public SyncSpecification tools(List<McpServerFeatures.SyncToolSpecification> too
855962
* @return This builder instance for method chaining
856963
* @throws IllegalArgumentException if toolSpecifications is null
857964
* @see #tools(List)
965+
* @deprecated Use
966+
* {@link #toolCalls(McpServerFeatures.SyncToolCallSpecification...)} instead for
967+
* tool calls that require a request object.
858968
*/
969+
@Deprecated
859970
public SyncSpecification tools(McpServerFeatures.SyncToolSpecification... toolSpecifications) {
860971
Assert.notNull(toolSpecifications, "Tool handlers list must not be null");
861972
for (McpServerFeatures.SyncToolSpecification tool : toolSpecifications) {
973+
this.tools.add(tool.toToolCall());
974+
}
975+
return this;
976+
}
977+
978+
/**
979+
* Adds multiple tools with their handlers to the server using varargs. This
980+
* method provides a convenient way to register multiple tools inline.
981+
* @param toolSpecifications The tool specifications to add. Must not be null.
982+
* @return This builder instance for method chaining
983+
* @throws IllegalArgumentException if toolSpecifications is null
984+
* @see #tools(List)
985+
*/
986+
public SyncSpecification toolCalls(McpServerFeatures.SyncToolCallSpecification... toolCallSpecifications) {
987+
Assert.notNull(toolCallSpecifications, "Tool handlers list must not be null");
988+
for (McpServerFeatures.SyncToolCallSpecification tool : toolCallSpecifications) {
862989
this.tools.add(tool);
863990
}
864991
return this;

0 commit comments

Comments
 (0)