19
19
import java .util .List ;
20
20
import java .util .Map ;
21
21
import java .util .Optional ;
22
+ import java .util .function .BiFunction ;
22
23
23
24
import com .fasterxml .jackson .annotation .JsonAlias ;
24
25
import com .fasterxml .jackson .annotation .JsonIgnoreProperties ;
29
30
import io .modelcontextprotocol .server .McpServerFeatures .AsyncToolSpecification ;
30
31
import io .modelcontextprotocol .server .McpStatelessServerFeatures ;
31
32
import io .modelcontextprotocol .server .McpSyncServerExchange ;
33
+ import io .modelcontextprotocol .server .McpTransportContext ;
32
34
import io .modelcontextprotocol .spec .McpSchema ;
35
+ import io .modelcontextprotocol .spec .McpSchema .CallToolRequest ;
33
36
import io .modelcontextprotocol .spec .McpSchema .Role ;
34
37
import reactor .core .publisher .Mono ;
35
38
import reactor .core .scheduler .Schedulers ;
@@ -152,16 +155,6 @@ public static McpServerFeatures.SyncToolSpecification toSyncToolSpecification(To
152
155
* Converts a Spring AI ToolCallback to an MCP SyncToolSpecification. This enables
153
156
* Spring AI functions to be exposed as MCP tools that can be discovered and invoked
154
157
* 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>
165
158
* @param toolCallback the Spring AI function callback to convert
166
159
* @param mimeType the MIME type of the output content
167
160
* @return an MCP SyncToolSpecification that wraps the function callback
@@ -170,39 +163,50 @@ public static McpServerFeatures.SyncToolSpecification toSyncToolSpecification(To
170
163
public static McpServerFeatures .SyncToolSpecification toSyncToolSpecification (ToolCallback toolCallback ,
171
164
MimeType mimeType ) {
172
165
173
- var tool = new McpSchema .Tool (toolCallback .getToolDefinition ().name (),
174
- toolCallback .getToolDefinition ().description (), toolCallback .getToolDefinition ().inputSchema ());
166
+ SharedSyncToolSpecification sharedSpec = toSharedSyncToolSpecification (toolCallback , mimeType );
175
167
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 ));
191
170
}
192
171
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
+ */
193
184
public static McpStatelessServerFeatures .SyncToolSpecification toStatelessSyncToolSpecification (
194
185
ToolCallback toolCallback , MimeType mimeType ) {
195
186
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
+
196
200
var tool = McpSchema .Tool .builder ()
197
201
.name (toolCallback .getToolDefinition ().name ())
198
202
.description (toolCallback .getToolDefinition ().description ())
199
203
.inputSchema (toolCallback .getToolDefinition ().inputSchema ())
200
204
.build ();
201
205
202
- return new McpStatelessServerFeatures . SyncToolSpecification (tool , (mcpTransportContext , request ) -> {
206
+ return new SharedSyncToolSpecification (tool , (exchangeOrContext , request ) -> {
203
207
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 )));
206
210
if (mimeType != null && mimeType .toString ().startsWith ("image" )) {
207
211
return new McpSchema .CallToolResult (List
208
212
.of (new McpSchema .ImageContent (List .of (Role .ASSISTANT ), null , callResult , mimeType .toString ())),
@@ -329,8 +333,8 @@ public static McpStatelessServerFeatures.AsyncToolSpecification toStatelessAsync
329
333
toolCallback , mimeType );
330
334
331
335
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 ))
334
338
.subscribeOn (Schedulers .boundedElastic ()));
335
339
}
336
340
0 commit comments