2222import java .util .function .BiFunction ;
2323import java .util .stream .Stream ;
2424
25- import com .fasterxml .jackson .annotation .JsonAlias ;
26- import com .fasterxml .jackson .annotation .JsonIgnoreProperties ;
2725import io .micrometer .common .util .StringUtils ;
2826import io .modelcontextprotocol .client .McpAsyncClient ;
2927import io .modelcontextprotocol .client .McpSyncClient ;
3937import org .springframework .ai .chat .model .ToolContext ;
4038import org .springframework .ai .model .ModelOptionsUtils ;
4139import org .springframework .ai .tool .ToolCallback ;
42- import org .springframework .lang . Nullable ;
40+ import org .springframework .ai . tool . method . MethodToolCallback ;
4341import org .springframework .util .CollectionUtils ;
4442import org .springframework .util .MimeType ;
4543
@@ -196,12 +194,13 @@ public static McpServerFeatures.SyncToolSpecification toSyncToolSpecification(To
196194 public static McpServerFeatures .SyncToolSpecification toSyncToolSpecification (ToolCallback toolCallback ,
197195 MimeType mimeType ) {
198196
199- SharedSyncToolSpecification sharedSpec = toSharedSyncToolSpecification (toolCallback , mimeType );
197+ SharedAsyncToolSpecification sharedSpec = toSharedAsyncToolSpecification (toolCallback , mimeType );
200198
201199 return new McpServerFeatures .SyncToolSpecification (sharedSpec .tool (),
202200 (exchange , map ) -> sharedSpec .sharedHandler ()
203- .apply (exchange , new CallToolRequest (sharedSpec .tool ().name (), map )),
204- (exchange , request ) -> sharedSpec .sharedHandler ().apply (exchange , request ));
201+ .apply (exchange , new CallToolRequest (sharedSpec .tool ().name (), map ))
202+ .block (),
203+ (exchange , request ) -> sharedSpec .sharedHandler ().apply (exchange , request ).block ());
205204 }
206205
207206 /**
@@ -219,15 +218,15 @@ public static McpServerFeatures.SyncToolSpecification toSyncToolSpecification(To
219218 public static McpStatelessServerFeatures .SyncToolSpecification toStatelessSyncToolSpecification (
220219 ToolCallback toolCallback , MimeType mimeType ) {
221220
222- var sharedSpec = toSharedSyncToolSpecification (toolCallback , mimeType );
221+ var sharedSpec = toSharedAsyncToolSpecification (toolCallback , mimeType );
223222
224223 return McpStatelessServerFeatures .SyncToolSpecification .builder ()
225224 .tool (sharedSpec .tool ())
226- .callHandler ((exchange , request ) -> sharedSpec .sharedHandler ().apply (exchange , request ))
225+ .callHandler ((exchange , request ) -> sharedSpec .sharedHandler ().apply (exchange , request ). block () )
227226 .build ();
228227 }
229228
230- private static SharedSyncToolSpecification toSharedSyncToolSpecification (ToolCallback toolCallback ,
229+ private static SharedAsyncToolSpecification toSharedAsyncToolSpecification (ToolCallback toolCallback ,
231230 MimeType mimeType ) {
232231
233232 var tool = McpSchema .Tool .builder ()
@@ -237,20 +236,31 @@ private static SharedSyncToolSpecification toSharedSyncToolSpecification(ToolCal
237236 McpSchema .JsonSchema .class ))
238237 .build ();
239238
240- return new SharedSyncToolSpecification (tool , (exchangeOrContext , request ) -> {
241- try {
242- String callResult = toolCallback .call (ModelOptionsUtils .toJsonString (request .arguments ()),
243- new ToolContext (Map .of (TOOL_CONTEXT_MCP_EXCHANGE_KEY , exchangeOrContext )));
239+ return new SharedAsyncToolSpecification (tool , (exchangeOrContext , request ) -> {
240+ final String toolRequest = ModelOptionsUtils .toJsonString (request .arguments ());
241+ final ToolContext toolContext = new ToolContext (Map .of (TOOL_CONTEXT_MCP_EXCHANGE_KEY , exchangeOrContext ));
242+ final Mono <String > callResult ;
243+ if (toolCallback instanceof MethodToolCallback reactiveMethodToolCallback ) {
244+ callResult = reactiveMethodToolCallback .callReactive (toolRequest , toolContext );
245+ }
246+ else {
247+ callResult = Mono .fromCallable (() -> toolCallback .call (toolRequest , toolContext ));
248+ }
249+ return callResult .map (result -> {
244250 if (mimeType != null && mimeType .toString ().startsWith ("image" )) {
245251 McpSchema .Annotations annotations = new McpSchema .Annotations (List .of (Role .ASSISTANT ), null );
246- return new McpSchema .CallToolResult (
247- List .of (new McpSchema .ImageContent (annotations , callResult , mimeType .toString ())), false );
252+ return McpSchema .CallToolResult .builder ()
253+ .addContent (new McpSchema .ImageContent (annotations , result , mimeType .toString ()))
254+ .isError (false )
255+ .build ();
248256 }
249- return new McpSchema .CallToolResult (List .of (new McpSchema .TextContent (callResult )), false );
250- }
251- catch (Exception e ) {
252- return new McpSchema .CallToolResult (List .of (new McpSchema .TextContent (e .getMessage ())), true );
253- }
257+ return McpSchema .CallToolResult .builder ().addTextContent (result ).isError (false ).build ();
258+ })
259+ .onErrorResume (Exception .class ,
260+ error -> Mono .fromSupplier (() -> McpSchema .CallToolResult .builder ()
261+ .addTextContent (error .getMessage ())
262+ .isError (true )
263+ .build ()));
254264 });
255265 }
256266
@@ -331,7 +341,6 @@ public static McpServerFeatures.AsyncToolSpecification toAsyncToolSpecification(
331341 * This method enables Spring AI tools to be exposed as asynchronous MCP tools that
332342 * can be discovered and invoked by language models. The conversion process:
333343 * <ul>
334- * <li>First converts the callback to a synchronous specification</li>
335344 * <li>Wraps the synchronous execution in a reactive Mono</li>
336345 * <li>Configures execution on a bounded elastic scheduler for non-blocking
337346 * operation</li>
@@ -352,26 +361,24 @@ public static McpServerFeatures.AsyncToolSpecification toAsyncToolSpecification(
352361 public static McpServerFeatures .AsyncToolSpecification toAsyncToolSpecification (ToolCallback toolCallback ,
353362 MimeType mimeType ) {
354363
355- McpServerFeatures . SyncToolSpecification syncToolSpecification = toSyncToolSpecification (toolCallback , mimeType );
364+ SharedAsyncToolSpecification asyncToolSpecification = toSharedAsyncToolSpecification (toolCallback , mimeType );
356365
357366 return McpServerFeatures .AsyncToolSpecification .builder ()
358- .tool (syncToolSpecification .tool ())
359- .callHandler ((exchange , request ) -> Mono
360- .fromCallable (
361- () -> syncToolSpecification .callHandler ().apply (new McpSyncServerExchange (exchange ), request ))
367+ .tool (asyncToolSpecification .tool ())
368+ .callHandler ((exchange , request ) -> asyncToolSpecification .sharedHandler ()
369+ .apply (new McpSyncServerExchange (exchange ), request )
362370 .subscribeOn (Schedulers .boundedElastic ()))
363371 .build ();
364372 }
365373
366374 public static McpStatelessServerFeatures .AsyncToolSpecification toStatelessAsyncToolSpecification (
367375 ToolCallback toolCallback , MimeType mimeType ) {
368376
369- McpStatelessServerFeatures .SyncToolSpecification statelessSyncToolSpecification = toStatelessSyncToolSpecification (
370- toolCallback , mimeType );
377+ SharedAsyncToolSpecification asyncToolSpecification = toSharedAsyncToolSpecification (toolCallback , mimeType );
371378
372- return new McpStatelessServerFeatures .AsyncToolSpecification (statelessSyncToolSpecification .tool (),
373- (context , request ) -> Mono
374- .fromCallable (() -> statelessSyncToolSpecification . callHandler (). apply (context , request ) )
379+ return new McpStatelessServerFeatures .AsyncToolSpecification (asyncToolSpecification .tool (),
380+ (context , request ) -> asyncToolSpecification . sharedHandler ()
381+ .apply (context , request )
375382 .subscribeOn (Schedulers .boundedElastic ()));
376383 }
377384
@@ -441,13 +448,8 @@ public static List<ToolCallback> getToolCallbacksFromAsyncClients(List<McpAsyncC
441448 return List .of ((AsyncMcpToolCallbackProvider .builder ().mcpClients (asyncMcpClients ).build ().getToolCallbacks ()));
442449 }
443450
444- @ JsonIgnoreProperties (ignoreUnknown = true )
445- // @formatter:off
446- private record Base64Wrapper (@ JsonAlias ("mimetype" ) @ Nullable MimeType mimeType , @ JsonAlias ({
447- "base64" , "b64" , "imageData" }) @ Nullable String data ) {
451+ private record SharedAsyncToolSpecification (McpSchema .Tool tool ,
452+ BiFunction <Object , CallToolRequest , Mono <McpSchema .CallToolResult >> sharedHandler ) {
448453 }
449454
450- private record SharedSyncToolSpecification (McpSchema .Tool tool ,
451- BiFunction <Object , CallToolRequest , McpSchema .CallToolResult > sharedHandler ) {
452- }
453455}
0 commit comments