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