1919import java .util .List ;
2020import java .util .Map ;
2121import java .util .Optional ;
22+ import java .util .function .BiFunction ;
2223
2324import com .fasterxml .jackson .annotation .JsonAlias ;
2425import com .fasterxml .jackson .annotation .JsonIgnoreProperties ;
2930import io .modelcontextprotocol .server .McpServerFeatures .AsyncToolSpecification ;
3031import io .modelcontextprotocol .server .McpStatelessServerFeatures ;
3132import io .modelcontextprotocol .server .McpSyncServerExchange ;
33+ import io .modelcontextprotocol .server .McpTransportContext ;
3234import io .modelcontextprotocol .spec .McpSchema ;
35+ import io .modelcontextprotocol .spec .McpSchema .CallToolRequest ;
3336import io .modelcontextprotocol .spec .McpSchema .Role ;
3437import reactor .core .publisher .Mono ;
3538import reactor .core .scheduler .Schedulers ;
@@ -152,16 +155,6 @@ public static McpServerFeatures.SyncToolSpecification toSyncToolSpecification(To
152155 * Converts a Spring AI ToolCallback to an MCP SyncToolSpecification. This enables
153156 * Spring AI functions to be exposed as MCP tools that can be discovered and invoked
154157 * by language models.
155- *
156- * <p>
157- * The conversion process:
158- * <ul>
159- * <li>Creates an MCP Tool with the function's name and input schema</li>
160- * <li>Wraps the function's execution in a SyncToolSpecification that handles the MCP
161- * protocol</li>
162- * <li>Provides error handling and result formatting according to MCP
163- * specifications</li>
164- * </ul>
165158 * @param toolCallback the Spring AI function callback to convert
166159 * @param mimeType the MIME type of the output content
167160 * @return an MCP SyncToolSpecification that wraps the function callback
@@ -170,39 +163,50 @@ public static McpServerFeatures.SyncToolSpecification toSyncToolSpecification(To
170163 public static McpServerFeatures .SyncToolSpecification toSyncToolSpecification (ToolCallback toolCallback ,
171164 MimeType mimeType ) {
172165
173- var tool = new McpSchema .Tool (toolCallback .getToolDefinition ().name (),
174- toolCallback .getToolDefinition ().description (), toolCallback .getToolDefinition ().inputSchema ());
166+ SharedSyncToolSpecification sharedSpec = toSharedSyncToolSpecification (toolCallback , mimeType );
175167
176- return new McpServerFeatures .SyncToolSpecification (tool , (exchange , request ) -> {
177- try {
178- String callResult = toolCallback .call (ModelOptionsUtils .toJsonString (request ),
179- new ToolContext (Map .of (TOOL_CONTEXT_MCP_EXCHANGE_KEY , exchange )));
180- if (mimeType != null && mimeType .toString ().startsWith ("image" )) {
181- return new McpSchema .CallToolResult (List
182- .of (new McpSchema .ImageContent (List .of (Role .ASSISTANT ), null , callResult , mimeType .toString ())),
183- false );
184- }
185- return new McpSchema .CallToolResult (List .of (new McpSchema .TextContent (callResult )), false );
186- }
187- catch (Exception e ) {
188- return new McpSchema .CallToolResult (List .of (new McpSchema .TextContent (e .getMessage ())), true );
189- }
190- });
168+ return new McpServerFeatures .SyncToolSpecification (sharedSpec .tool (), null ,
169+ (exchange , request ) -> sharedSpec .sharedHandler ().apply (exchange , request ));
191170 }
192171
172+ /**
173+ * Converts a Spring AI ToolCallback to an MCP StatelessSyncToolSpecification. This
174+ * enables Spring AI functions to be exposed as MCP tools that can be discovered and
175+ * invoked by language models.
176+ *
177+ * You can use the ToolCallback builder to create a new instance of ToolCallback using
178+ * either java.util.function.Function or Method reference.
179+ * @param toolCallback the Spring AI function callback to convert
180+ * @param mimeType the MIME type of the output content
181+ * @return an MCP StatelessSyncToolSpecification that wraps the function callback
182+ * @throws RuntimeException if there's an error during the function execution
183+ */
193184 public static McpStatelessServerFeatures .SyncToolSpecification toStatelessSyncToolSpecification (
194185 ToolCallback toolCallback , MimeType mimeType ) {
195186
187+ var sharedSpec = toSharedSyncToolSpecification (toolCallback , mimeType );
188+
189+ return new McpStatelessServerFeatures .SyncToolSpecification (sharedSpec .tool (),
190+ (exchange , request ) -> sharedSpec .sharedHandler ().apply (exchange , request ));
191+ }
192+
193+ private record SharedSyncToolSpecification (McpSchema .Tool tool ,
194+ BiFunction <Object , CallToolRequest , McpSchema .CallToolResult > sharedHandler ) {
195+ }
196+
197+ private static SharedSyncToolSpecification toSharedSyncToolSpecification (ToolCallback toolCallback ,
198+ MimeType mimeType ) {
199+
196200 var tool = McpSchema .Tool .builder ()
197201 .name (toolCallback .getToolDefinition ().name ())
198202 .description (toolCallback .getToolDefinition ().description ())
199203 .inputSchema (toolCallback .getToolDefinition ().inputSchema ())
200204 .build ();
201205
202- return new McpStatelessServerFeatures . SyncToolSpecification (tool , (mcpTransportContext , request ) -> {
206+ return new SharedSyncToolSpecification (tool , (exchangeOrContext , request ) -> {
203207 try {
204- String callResult = toolCallback .call (ModelOptionsUtils .toJsonString (request ),
205- new ToolContext (Map .of (TOOL_CONTEXT_MCP_EXCHANGE_KEY , mcpTransportContext )));
208+ String callResult = toolCallback .call (ModelOptionsUtils .toJsonString (request . arguments () ),
209+ new ToolContext (Map .of (TOOL_CONTEXT_MCP_EXCHANGE_KEY , exchangeOrContext )));
206210 if (mimeType != null && mimeType .toString ().startsWith ("image" )) {
207211 return new McpSchema .CallToolResult (List
208212 .of (new McpSchema .ImageContent (List .of (Role .ASSISTANT ), null , callResult , mimeType .toString ())),
@@ -329,8 +333,8 @@ public static McpStatelessServerFeatures.AsyncToolSpecification toStatelessAsync
329333 toolCallback , mimeType );
330334
331335 return new McpStatelessServerFeatures .AsyncToolSpecification (statelessSyncToolSpecification .tool (),
332- (context , map ) -> Mono
333- .fromCallable (() -> statelessSyncToolSpecification .callHandler ().apply (context , map ))
336+ (context , request ) -> Mono
337+ .fromCallable (() -> statelessSyncToolSpecification .callHandler ().apply (context . copy (), request ))
334338 .subscribeOn (Schedulers .boundedElastic ()));
335339 }
336340
0 commit comments