|
22 | 22 | import io.modelcontextprotocol.server.McpServerFeatures;
|
23 | 23 | import io.modelcontextprotocol.server.McpServerFeatures.AsyncToolRegistration;
|
24 | 24 | import io.modelcontextprotocol.spec.McpSchema;
|
| 25 | +import io.modelcontextprotocol.spec.McpSchema.Role; |
25 | 26 | import reactor.core.publisher.Mono;
|
26 | 27 | import reactor.core.scheduler.Schedulers;
|
27 | 28 |
|
28 | 29 | import org.springframework.ai.model.ModelOptionsUtils;
|
29 | 30 | import org.springframework.ai.tool.ToolCallback;
|
30 | 31 | import org.springframework.util.CollectionUtils;
|
| 32 | +import org.springframework.util.MimeType; |
31 | 33 |
|
32 | 34 | /**
|
33 | 35 | * Utility class that provides helper methods for working with Model Context Protocol
|
@@ -105,12 +107,44 @@ public static List<McpServerFeatures.SyncToolRegistration> toSyncToolRegistratio
|
105 | 107 | * @throws RuntimeException if there's an error during the function execution
|
106 | 108 | */
|
107 | 109 | public static McpServerFeatures.SyncToolRegistration toSyncToolRegistration(ToolCallback toolCallback) {
|
| 110 | + return toSyncToolRegistration(toolCallback, null); |
| 111 | + } |
| 112 | + |
| 113 | + /** |
| 114 | + * Converts a Spring AI FunctionCallback to an MCP SyncToolRegistration. This enables |
| 115 | + * Spring AI functions to be exposed as MCP tools that can be discovered and invoked |
| 116 | + * by language models. |
| 117 | + * |
| 118 | + * <p> |
| 119 | + * The conversion process: |
| 120 | + * <ul> |
| 121 | + * <li>Creates an MCP Tool with the function's name and input schema</li> |
| 122 | + * <li>Wraps the function's execution in a SyncToolRegistration that handles the MCP |
| 123 | + * protocol</li> |
| 124 | + * <li>Provides error handling and result formatting according to MCP |
| 125 | + * specifications</li> |
| 126 | + * </ul> |
| 127 | + * |
| 128 | + * You can use the FunctionCallback builder to create a new instance of |
| 129 | + * FunctionCallback using either java.util.function.Function or Method reference. |
| 130 | + * @param toolCallback the Spring AI function callback to convert |
| 131 | + * @param mimeType the MIME type of the output content |
| 132 | + * @return an MCP SyncToolRegistration that wraps the function callback |
| 133 | + * @throws RuntimeException if there's an error during the function execution |
| 134 | + */ |
| 135 | + public static McpServerFeatures.SyncToolRegistration toSyncToolRegistration(ToolCallback toolCallback, |
| 136 | + MimeType mimeType) { |
| 137 | + |
108 | 138 | var tool = new McpSchema.Tool(toolCallback.getToolDefinition().name(),
|
109 | 139 | toolCallback.getToolDefinition().description(), toolCallback.getToolDefinition().inputSchema());
|
110 | 140 |
|
111 | 141 | return new McpServerFeatures.SyncToolRegistration(tool, request -> {
|
112 | 142 | try {
|
113 | 143 | String callResult = toolCallback.call(ModelOptionsUtils.toJsonString(request));
|
| 144 | + if (mimeType != null && mimeType.toString().startsWith("image")) { |
| 145 | + return new McpSchema.CallToolResult(List.of(new McpSchema.ImageContent(List.of(Role.ASSISTANT), |
| 146 | + null, "image", callResult, mimeType.toString())), false); |
| 147 | + } |
114 | 148 | return new McpSchema.CallToolResult(List.of(new McpSchema.TextContent(callResult)), false);
|
115 | 149 | }
|
116 | 150 | catch (Exception e) {
|
@@ -174,8 +208,39 @@ public static List<McpServerFeatures.AsyncToolRegistration> toAsyncToolRegistrat
|
174 | 208 | * @see Schedulers#boundedElastic()
|
175 | 209 | */
|
176 | 210 | public static McpServerFeatures.AsyncToolRegistration toAsyncToolRegistration(ToolCallback toolCallback) {
|
| 211 | + return toAsyncToolRegistration(toolCallback, null); |
| 212 | + } |
| 213 | + |
| 214 | + /** |
| 215 | + * Converts a Spring AI tool callback to an MCP asynchronous tool registration. |
| 216 | + * <p> |
| 217 | + * This method enables Spring AI tools to be exposed as asynchronous MCP tools that |
| 218 | + * can be discovered and invoked by language models. The conversion process: |
| 219 | + * <ul> |
| 220 | + * <li>First converts the callback to a synchronous registration</li> |
| 221 | + * <li>Wraps the synchronous execution in a reactive Mono</li> |
| 222 | + * <li>Configures execution on a bounded elastic scheduler for non-blocking |
| 223 | + * operation</li> |
| 224 | + * </ul> |
| 225 | + * <p> |
| 226 | + * The resulting async registration will: |
| 227 | + * <ul> |
| 228 | + * <li>Execute the tool without blocking the calling thread</li> |
| 229 | + * <li>Handle errors and results asynchronously</li> |
| 230 | + * <li>Provide backpressure through Project Reactor</li> |
| 231 | + * </ul> |
| 232 | + * @param toolCallback the Spring AI tool callback to convert |
| 233 | + * @param mimeType the MIME type of the output content |
| 234 | + * @return an MCP asynchronous tool registration that wraps the tool callback |
| 235 | + * @see McpServerFeatures.AsyncToolRegistration |
| 236 | + * @see Mono |
| 237 | + * @see Schedulers#boundedElastic() |
| 238 | + */ |
| 239 | + public static McpServerFeatures.AsyncToolRegistration toAsyncToolRegistration(ToolCallback toolCallback, |
| 240 | + MimeType mimeType) { |
| 241 | + |
| 242 | + McpServerFeatures.SyncToolRegistration syncToolRegistration = toSyncToolRegistration(toolCallback, mimeType); |
177 | 243 |
|
178 |
| - McpServerFeatures.SyncToolRegistration syncToolRegistration = toSyncToolRegistration(toolCallback); |
179 | 244 | return new AsyncToolRegistration(syncToolRegistration.tool(),
|
180 | 245 | map -> Mono.fromCallable(() -> syncToolRegistration.call().apply(map))
|
181 | 246 | .subscribeOn(Schedulers.boundedElastic()));
|
|
0 commit comments