diff --git a/mcp/common/src/main/java/org/springframework/ai/mcp/McpToolUtils.java b/mcp/common/src/main/java/org/springframework/ai/mcp/McpToolUtils.java index 2aeff595d00..acf29f49025 100644 --- a/mcp/common/src/main/java/org/springframework/ai/mcp/McpToolUtils.java +++ b/mcp/common/src/main/java/org/springframework/ai/mcp/McpToolUtils.java @@ -22,8 +22,6 @@ import java.util.function.BiFunction; import java.util.stream.Stream; -import com.fasterxml.jackson.annotation.JsonAlias; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import io.micrometer.common.util.StringUtils; import io.modelcontextprotocol.client.McpAsyncClient; import io.modelcontextprotocol.client.McpSyncClient; @@ -39,7 +37,7 @@ import org.springframework.ai.chat.model.ToolContext; import org.springframework.ai.model.ModelOptionsUtils; import org.springframework.ai.tool.ToolCallback; -import org.springframework.lang.Nullable; +import org.springframework.ai.tool.method.MethodToolCallback; import org.springframework.util.CollectionUtils; import org.springframework.util.MimeType; @@ -196,12 +194,13 @@ public static McpServerFeatures.SyncToolSpecification toSyncToolSpecification(To public static McpServerFeatures.SyncToolSpecification toSyncToolSpecification(ToolCallback toolCallback, MimeType mimeType) { - SharedSyncToolSpecification sharedSpec = toSharedSyncToolSpecification(toolCallback, mimeType); + SharedAsyncToolSpecification sharedSpec = toSharedAsyncToolSpecification(toolCallback, mimeType); return new McpServerFeatures.SyncToolSpecification(sharedSpec.tool(), (exchange, map) -> sharedSpec.sharedHandler() - .apply(exchange, new CallToolRequest(sharedSpec.tool().name(), map)), - (exchange, request) -> sharedSpec.sharedHandler().apply(exchange, request)); + .apply(exchange, new CallToolRequest(sharedSpec.tool().name(), map)) + .block(), + (exchange, request) -> sharedSpec.sharedHandler().apply(exchange, request).block()); } /** @@ -219,15 +218,15 @@ public static McpServerFeatures.SyncToolSpecification toSyncToolSpecification(To public static McpStatelessServerFeatures.SyncToolSpecification toStatelessSyncToolSpecification( ToolCallback toolCallback, MimeType mimeType) { - var sharedSpec = toSharedSyncToolSpecification(toolCallback, mimeType); + var sharedSpec = toSharedAsyncToolSpecification(toolCallback, mimeType); return McpStatelessServerFeatures.SyncToolSpecification.builder() .tool(sharedSpec.tool()) - .callHandler((exchange, request) -> sharedSpec.sharedHandler().apply(exchange, request)) + .callHandler((exchange, request) -> sharedSpec.sharedHandler().apply(exchange, request).block()) .build(); } - private static SharedSyncToolSpecification toSharedSyncToolSpecification(ToolCallback toolCallback, + private static SharedAsyncToolSpecification toSharedAsyncToolSpecification(ToolCallback toolCallback, MimeType mimeType) { var tool = McpSchema.Tool.builder() @@ -237,20 +236,31 @@ private static SharedSyncToolSpecification toSharedSyncToolSpecification(ToolCal McpSchema.JsonSchema.class)) .build(); - return new SharedSyncToolSpecification(tool, (exchangeOrContext, request) -> { - try { - String callResult = toolCallback.call(ModelOptionsUtils.toJsonString(request.arguments()), - new ToolContext(Map.of(TOOL_CONTEXT_MCP_EXCHANGE_KEY, exchangeOrContext))); + return new SharedAsyncToolSpecification(tool, (exchangeOrContext, request) -> { + final String toolRequest = ModelOptionsUtils.toJsonString(request.arguments()); + final ToolContext toolContext = new ToolContext(Map.of(TOOL_CONTEXT_MCP_EXCHANGE_KEY, exchangeOrContext)); + final Mono callResult; + if (toolCallback instanceof MethodToolCallback reactiveMethodToolCallback) { + callResult = reactiveMethodToolCallback.callReactive(toolRequest, toolContext); + } + else { + callResult = Mono.fromCallable(() -> toolCallback.call(toolRequest, toolContext)); + } + return callResult.map(result -> { if (mimeType != null && mimeType.toString().startsWith("image")) { McpSchema.Annotations annotations = new McpSchema.Annotations(List.of(Role.ASSISTANT), null); - return new McpSchema.CallToolResult( - List.of(new McpSchema.ImageContent(annotations, callResult, mimeType.toString())), false); + return McpSchema.CallToolResult.builder() + .addContent(new McpSchema.ImageContent(annotations, result, mimeType.toString())) + .isError(false) + .build(); } - return new McpSchema.CallToolResult(List.of(new McpSchema.TextContent(callResult)), false); - } - catch (Exception e) { - return new McpSchema.CallToolResult(List.of(new McpSchema.TextContent(e.getMessage())), true); - } + return McpSchema.CallToolResult.builder().addTextContent(result).isError(false).build(); + }) + .onErrorResume(Exception.class, + error -> Mono.fromSupplier(() -> McpSchema.CallToolResult.builder() + .addTextContent(error.getMessage()) + .isError(true) + .build())); }); } @@ -331,7 +341,6 @@ public static McpServerFeatures.AsyncToolSpecification toAsyncToolSpecification( * This method enables Spring AI tools to be exposed as asynchronous MCP tools that * can be discovered and invoked by language models. The conversion process: *