71 private static final Logger LOG = LoggerFactory.getLogger(
Ollama.class);
73 private final String host;
84 @Setter
private long requestTimeoutSeconds = 10;
87 @Setter
private int imageURLReadTimeoutSeconds = 10;
90 @Setter
private int imageURLConnectTimeoutSeconds = 10;
98 @Setter
private int maxChatToolCallRetries = 3;
109 @SuppressWarnings({
"FieldMayBeFinal",
"FieldCanBeLocal"})
110 private int numberOfRetriesForModelPull = 0;
118 @Setter
private boolean metricsEnabled =
false;
127 this.host =
"http://localhost:11434";
136 if (host.endsWith(
"/")) {
137 this.host = host.substring(0, host.length() - 1);
141 LOG.info(
"Ollama4j client initialized. Connected to Ollama server at: {}", this.host);
151 this.auth =
new BasicAuth(username, password);
170 long startTime = System.currentTimeMillis();
171 String url =
"/api/tags";
175 HttpClient httpClient = HttpClient.newHttpClient();
176 HttpRequest httpRequest;
177 HttpResponse<String> response;
179 getRequestBuilderDefault(
new URI(this.host + url))
188 response = httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofString());
189 statusCode = response.statusCode();
190 return statusCode == 200;
191 }
catch (InterruptedException ie) {
192 Thread.currentThread().interrupt();
194 }
catch (Exception e) {
218 long startTime = System.currentTimeMillis();
219 String url =
"/api/ps";
223 HttpClient httpClient = HttpClient.newHttpClient();
224 HttpRequest httpRequest =
null;
227 getRequestBuilderDefault(
new URI(this.host + url))
236 }
catch (URISyntaxException e) {
239 HttpResponse<String> response =
null;
240 response = httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofString());
241 statusCode = response.statusCode();
242 String responseString = response.body();
243 if (statusCode == 200) {
249 }
catch (InterruptedException ie) {
250 Thread.currentThread().interrupt();
252 }
catch (Exception e) {
276 long startTime = System.currentTimeMillis();
277 String url =
"/api/tags";
281 HttpClient httpClient = HttpClient.newHttpClient();
282 HttpRequest httpRequest =
283 getRequestBuilderDefault(
new URI(this.host + url))
292 HttpResponse<String> response =
293 httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofString());
294 statusCode = response.statusCode();
295 String responseString = response.body();
296 if (statusCode == 200) {
303 }
catch (InterruptedException ie) {
304 Thread.currentThread().interrupt();
306 }
catch (Exception e) {
332 private void handlePullRetry(
333 String modelName,
int currentRetry,
int maxRetries,
long baseDelayMillis)
334 throws InterruptedException {
335 int attempt = currentRetry + 1;
336 if (attempt < maxRetries) {
337 long backoffMillis = baseDelayMillis * (1L << currentRetry);
339 "Failed to pull model {}, retrying in {}s... (attempt {}/{})",
341 backoffMillis / 1000,
345 Thread.sleep(backoffMillis);
346 }
catch (InterruptedException ie) {
347 Thread.currentThread().interrupt();
352 "Failed to pull model {} after {} attempts, no more retries.",
365 private void doPullModel(String modelName, ModelPullListener listener)
throws OllamaException {
366 long startTime = System.currentTimeMillis();
367 String url =
"/api/pull";
372 HttpRequest request =
373 getRequestBuilderDefault(
new URI(this.host + url))
374 .POST(HttpRequest.BodyPublishers.ofString(jsonData))
382 HttpClient client = HttpClient.newHttpClient();
383 HttpResponse<InputStream> response =
384 client.send(request, HttpResponse.BodyHandlers.ofInputStream());
385 statusCode = response.statusCode();
386 InputStream responseBodyStream = response.body();
387 String responseString =
"";
388 boolean success =
false;
390 try (BufferedReader reader =
392 new InputStreamReader(responseBodyStream, StandardCharsets.UTF_8))) {
394 while ((line = reader.readLine()) !=
null) {
398 processModelPullResponse(modelPullResponse, modelName, listener)
403 LOG.error(
"Model pull failed or returned invalid status.");
404 throw new OllamaException(
"Model pull failed or returned invalid status.");
406 if (statusCode != 200) {
409 }
catch (InterruptedException ie) {
410 Thread.currentThread().interrupt();
411 throw new OllamaException(
"Thread was interrupted during model pull.", ie);
412 }
catch (Exception e) {
439 @SuppressWarnings(
"RedundantIfStatement")
440 private
boolean processModelPullResponse(
441 ModelPullResponse modelPullResponse, String modelName, ModelPullListener listener)
443 if (modelPullResponse ==
null) {
444 LOG.error(
"Received null response for model pull.");
447 if (modelPullListener !=
null) {
448 modelPullListener.onStatusUpdate(modelName, modelPullResponse);
450 if (listener !=
null && listener != modelPullListener) {
451 listener.onStatusUpdate(modelName, modelPullResponse);
453 String error = modelPullResponse.getError();
454 if (error !=
null && !error.trim().isEmpty()) {
455 throw new OllamaException(
"Model pull failed: " + error);
457 String status = modelPullResponse.getStatus();
458 if (status !=
null) {
459 LOG.debug(
"{}: {}", modelName, status);
460 if (
"success".equalsIgnoreCase(status)) {
474 String url =
"/api/version";
475 long startTime = System.currentTimeMillis();
479 HttpClient httpClient = HttpClient.newHttpClient();
480 HttpRequest httpRequest =
481 getRequestBuilderDefault(
new URI(this.host + url))
490 HttpResponse<String> response =
491 httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofString());
492 statusCode = response.statusCode();
493 String responseString = response.body();
494 if (statusCode == 200) {
501 }
catch (InterruptedException ie) {
502 Thread.currentThread().interrupt();
504 }
catch (Exception e) {
530 pullModel(modelName,
null);
543 if (numberOfRetriesForModelPull == 0) {
544 this.doPullModel(modelName, listener);
547 int numberOfRetries = 0;
548 long baseDelayMillis = 3000L;
549 while (numberOfRetries < numberOfRetriesForModelPull) {
551 this.doPullModel(modelName, listener);
557 numberOfRetriesForModelPull,
563 "Failed to pull model "
566 + numberOfRetriesForModelPull
568 }
catch (InterruptedException ie) {
569 Thread.currentThread().interrupt();
571 }
catch (Exception e) {
584 long startTime = System.currentTimeMillis();
585 String url =
"/api/show";
590 HttpRequest request =
591 getRequestBuilderDefault(
new URI(this.host + url))
598 .POST(HttpRequest.BodyPublishers.ofString(jsonData))
600 HttpClient client = HttpClient.newHttpClient();
601 HttpResponse<String> response =
602 client.send(request, HttpResponse.BodyHandlers.ofString());
603 statusCode = response.statusCode();
604 String responseBody = response.body();
605 if (statusCode == 200) {
610 }
catch (InterruptedException ie) {
611 Thread.currentThread().interrupt();
613 }
catch (Exception e) {
638 createModel(customModelRequest,
null);
650 long startTime = System.currentTimeMillis();
651 String url =
"/api/create";
655 String jsonData = customModelRequest.toString();
656 HttpRequest request =
657 getRequestBuilderDefault(
new URI(this.host + url))
665 HttpRequest.BodyPublishers.ofString(
666 jsonData, StandardCharsets.UTF_8))
668 HttpClient client = HttpClient.newHttpClient();
669 HttpResponse<InputStream> response =
670 client.send(request, HttpResponse.BodyHandlers.ofInputStream());
671 statusCode = response.statusCode();
672 if (statusCode != 200) {
674 new String(response.body().readAllBytes(), StandardCharsets.UTF_8);
678 try (BufferedReader reader =
680 new InputStreamReader(response.body(), StandardCharsets.UTF_8))) {
682 StringBuilder lines =
new StringBuilder();
683 while ((line = reader.readLine()) !=
null) {
687 LOG.debug(res.getStatus());
688 if (modelPullListener !=
null) {
689 modelPullListener.onStatusUpdate(customModelRequest.getModel(), res);
691 if (listener !=
null && listener != modelPullListener) {
692 listener.onStatusUpdate(customModelRequest.getModel(), res);
694 if (res.getError() !=
null) {
695 out = res.getError();
701 }
catch (InterruptedException e) {
702 Thread.currentThread().interrupt();
704 }
catch (Exception e) {
730 long startTime = System.currentTimeMillis();
731 String url =
"/api/delete";
736 HttpRequest request =
737 getRequestBuilderDefault(
new URI(this.host + url))
740 HttpRequest.BodyPublishers.ofString(
741 jsonData, StandardCharsets.UTF_8))
749 HttpClient client = HttpClient.newHttpClient();
750 HttpResponse<String> response =
751 client.send(request, HttpResponse.BodyHandlers.ofString());
752 statusCode = response.statusCode();
753 String responseBody = response.body();
755 if (statusCode == 404
756 && responseBody.contains(
"model")
757 && responseBody.contains(
"not found")) {
760 if (statusCode != 200) {
763 }
catch (InterruptedException e) {
764 Thread.currentThread().interrupt();
766 }
catch (Exception e) {
793 long startTime = System.currentTimeMillis();
794 String url =
"/api/generate";
798 ObjectMapper objectMapper =
new ObjectMapper();
799 Map<String, Object> jsonMap =
new java.util.HashMap<>();
800 jsonMap.put(
"model", modelName);
801 jsonMap.put(
"keep_alive", 0);
802 String jsonData = objectMapper.writeValueAsString(jsonMap);
803 HttpRequest request =
804 getRequestBuilderDefault(
new URI(this.host + url))
807 HttpRequest.BodyPublishers.ofString(
808 jsonData, StandardCharsets.UTF_8))
816 LOG.debug(
"Unloading model with request: {}", jsonData);
817 HttpClient client = HttpClient.newHttpClient();
818 HttpResponse<String> response =
819 client.send(request, HttpResponse.BodyHandlers.ofString());
820 statusCode = response.statusCode();
821 String responseBody = response.body();
822 if (statusCode == 404
823 && responseBody.contains(
"model")
824 && responseBody.contains(
"not found")) {
825 LOG.debug(
"Unload response: {} - {}", statusCode, responseBody);
828 if (statusCode != 200) {
829 LOG.debug(
"Unload response: {} - {}", statusCode, responseBody);
832 }
catch (InterruptedException e) {
833 Thread.currentThread().interrupt();
834 LOG.debug(
"Unload interrupted: {} - {}", statusCode, out);
836 }
catch (Exception e) {
837 LOG.debug(
"Unload failed: {} - {}", statusCode, out);
862 long startTime = System.currentTimeMillis();
863 String url =
"/api/embed";
868 HttpClient httpClient = HttpClient.newHttpClient();
869 HttpRequest request =
870 HttpRequest.newBuilder(
new URI(this.host + url))
874 .POST(HttpRequest.BodyPublishers.ofString(jsonData))
876 HttpResponse<String> response =
877 httpClient.send(request, HttpResponse.BodyHandlers.ofString());
878 statusCode = response.statusCode();
879 String responseBody = response.body();
880 if (statusCode == 200) {
885 }
catch (InterruptedException e) {
886 Thread.currentThread().interrupt();
888 }
catch (Exception e) {
918 if (request.isUseTools()) {
919 return generateWithToolsInternal(request, streamObserver);
922 if (streamObserver !=
null) {
924 return generateSyncForOllamaRequestModel(
926 streamObserver.getThinkingStreamHandler(),
927 streamObserver.getResponseStreamHandler());
929 return generateSyncForOllamaRequestModel(
930 request,
null, streamObserver.getResponseStreamHandler());
933 return generateSyncForOllamaRequestModel(request,
null,
null);
934 }
catch (Exception e) {
951 long startTime = System.currentTimeMillis();
952 String url =
"/api/generate";
957 HttpClient httpClient = HttpClient.newHttpClient();
958 HttpRequest httpRequest =
959 HttpRequest.newBuilder(
new URI(this.host + url))
966 .POST(HttpRequest.BodyPublishers.ofString(jsonData))
969 HttpResponse<InputStream> response =
970 httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofInputStream());
971 statusCode = response.statusCode();
973 if (statusCode != 200) {
977 InputStream inputStream = response.body();
978 BufferedReader reader =
new BufferedReader(
new InputStreamReader(inputStream));
981 while ((line = reader.readLine()) !=
null) {
982 if (line.trim().isEmpty())
continue;
990 if (result.getCompleted() !=
null && result.getTotal() !=
null) {
991 int progressPercentage =
992 (int) ((result.getCompleted() * 100f) / result.getTotal());
995 + result.getCompleted()
998 +
" steps complete] - "
1002 if (result !=
null && result.isDone()) {
1003 finalResult = result;
1006 }
catch (Exception ignore) {
1011 if (finalResult !=
null) {
1016 }
catch (Exception e) {
1037 ArrayList<OllamaChatMessage> msgs =
new ArrayList<>();
1039 chatRequest.setModel(request.getModel());
1042 ocm.setResponse(request.getPrompt());
1043 chatRequest.setMessages(msgs);
1049 List<
Tools.
Tool> allTools =
new ArrayList<>();
1050 if (request.getTools() !=
null) {
1051 allTools.addAll(request.getTools());
1053 List<
Tools.
Tool> registeredTools = this.getRegisteredTools();
1054 if (registeredTools !=
null) {
1055 allTools.addAll(registeredTools);
1059 chatRequest.setUseTools(
true);
1060 chatRequest.setTools(allTools);
1061 if (streamObserver !=
null) {
1062 chatRequest.setStream(
true);
1063 if (streamObserver.getResponseStreamHandler() !=
null) {
1065 chatResponseModel ->
1067 .getResponseStreamHandler()
1068 .accept(chatResponseModel.getMessage().getResponse());
1073 res.getResponseModel().getMessage().getResponse(),
1074 res.getResponseModel().getMessage().getThinking(),
1075 res.getResponseModel().getTotalDuration(),
1091 long startTime = System.currentTimeMillis();
1092 String url =
"/api/generate";
1093 int statusCode = -1;
1096 ollamaRequestModel.setRaw(raw);
1097 ollamaRequestModel.setThink(think);
1100 getRequestBuilderDefault(
new URI(this.host + url)),
1102 requestTimeoutSeconds);
1103 ollamaAsyncResultStreamer.start();
1104 statusCode = ollamaAsyncResultStreamer.getHttpStatusCode();
1105 return ollamaAsyncResultStreamer;
1106 }
catch (Exception e) {
1110 url, model, raw, think,
true,
null,
null, startTime, statusCode,
null);
1134 if (request.isUseTools()) {
1139 if (tokenHandler !=
null) {
1140 request.setStream(
true);
1141 result = requestCaller.
call(request, tokenHandler);
1143 result = requestCaller.
callSync(request);
1147 List<OllamaChatToolCalls> toolCalls =
1148 result.getResponseModel().getMessage().getToolCalls();
1150 int toolCallTries = 0;
1151 while (toolCalls !=
null
1152 && !toolCalls.isEmpty()
1153 && toolCallTries < maxChatToolCallRetries) {
1155 String toolName = toolCall.getFunction().getName();
1156 for (
Tools.
Tool t : request.getTools()) {
1157 if (t.getToolSpec().getName().equals(toolName)) {
1159 if (toolFunction ==
null) {
1161 "Tool function not found: " + toolName);
1164 "Invoking tool {} with arguments: {}",
1165 toolCall.getFunction().getName(),
1166 toolCall.getFunction().getArguments());
1167 Map<String, Object> arguments = toolCall.getFunction().getArguments();
1168 Object res = toolFunction.
apply(arguments);
1169 String argumentKeys =
1170 arguments.keySet().stream()
1171 .map(Object::toString)
1172 .collect(Collectors.joining(
", "));
1173 request.getMessages()
1183 +
" [/TOOL_RESULTS]"));
1187 if (tokenHandler !=
null) {
1188 result = requestCaller.
call(request, tokenHandler);
1190 result = requestCaller.
callSync(request);
1192 toolCalls = result.getResponseModel().getMessage().getToolCalls();
1196 }
catch (InterruptedException e) {
1197 Thread.currentThread().interrupt();
1199 }
catch (Exception e) {
1211 LOG.debug(
"Registered tool: {}", tool.getToolSpec().getName());
1233 toolRegistry.
clear();
1234 LOG.debug(
"All tools have been deregistered.");
1247 Class<?> callerClass =
null;
1250 Class.forName(Thread.currentThread().getStackTrace()[2].getClassName());
1251 }
catch (ClassNotFoundException e) {
1257 if (ollamaToolServiceAnnotation ==
null) {
1258 throw new IllegalStateException(
1262 Class<?>[] providers = ollamaToolServiceAnnotation.
providers();
1263 for (Class<?> provider : providers) {
1264 registerAnnotatedTools(provider.getDeclaredConstructor().newInstance());
1266 }
catch (InstantiationException
1267 | NoSuchMethodException
1268 | IllegalAccessException
1269 | InvocationTargetException e) {
1284 Class<?> objectClass =
object.getClass();
1285 Method[] methods = objectClass.getMethods();
1286 for (Method m : methods) {
1288 if (toolSpec ==
null) {
1291 String operationName = !toolSpec.
name().isBlank() ? toolSpec.
name() : m.getName();
1292 String operationDesc = !toolSpec.
desc().isBlank() ? toolSpec.
desc() : operationName;
1295 LinkedHashMap<String, String> methodParams =
new LinkedHashMap<>();
1296 for (Parameter parameter : m.getParameters()) {
1299 String propType = parameter.getType().getTypeName();
1300 if (toolPropertyAnn ==
null) {
1301 methodParams.put(parameter.getName(),
null);
1305 !toolPropertyAnn.
name().isBlank()
1306 ? toolPropertyAnn.
name()
1307 : parameter.getName();
1308 methodParams.put(propName, propType);
1313 .description(toolPropertyAnn.
desc())
1314 .required(toolPropertyAnn.
required())
1317 Tools.ToolSpec toolSpecification =
1319 .name(operationName)
1320 .description(operationDesc)
1327 .toolFunction(reflectionalToolFunction)
1328 .toolSpec(toolSpecification)
1364 String jsonContent =
1365 java.nio.file.Files.readString(java.nio.file.Paths.get(mcpConfigJsonFilePath));
1366 MCPToolsConfig config =
1367 McpJsonMapper.getDefault().readValue(jsonContent, MCPToolsConfig.class);
1369 if (config.mcpServers !=
null && !config.mcpServers.isEmpty()) {
1370 for (Map.Entry<String, MCPToolConfig> tool : config.mcpServers.entrySet()) {
1371 ServerParameters.Builder serverParamsBuilder =
1372 ServerParameters.builder(tool.getValue().command);
1373 if (tool.getValue().args !=
null && !tool.getValue().args.isEmpty()) {
1375 "Runnable MCP Tool command: \n\n\t{} {}\n\n",
1376 tool.getValue().command,
1377 String.join(
" ", tool.getValue().args));
1378 serverParamsBuilder.args(tool.getValue().args.toArray(
new String[0]));
1380 ServerParameters serverParameters = serverParamsBuilder.build();
1381 StdioClientTransport transport =
1382 new StdioClientTransport(serverParameters, McpJsonMapper.getDefault());
1384 int mcpToolRequestTimeoutSeconds = 30;
1386 McpSyncClient client =
1387 McpClient.sync(transport)
1389 Duration.ofSeconds(mcpToolRequestTimeoutSeconds))
1391 client.initialize();
1393 ListToolsResult result = client.listTools();
1394 for (io.modelcontextprotocol.spec.McpSchema.Tool mcpTool : result.tools()) {
1395 Tools.Tool mcpToolAsOllama4jTool =
1396 createOllamaToolFromMCPTool(
1397 tool.getKey(), mcpTool, serverParameters);
1398 toolRegistry.
addTool(mcpToolAsOllama4jTool);
1420 private CallToolResult callMCPTool(
1421 String mcpServerName, String toolName, Map<String, Object> arguments) {
1422 for (
Tools.
Tool tool : getRegisteredTools()) {
1423 if (tool.isMCPTool() && tool.getMcpServerName().equals(mcpServerName)) {
1424 if (tool.getToolSpec().getName().equals(toolName)) {
1425 ServerParameters serverParameters = tool.getMcpServerParameters();
1426 StdioClientTransport stdioTransport =
1427 new StdioClientTransport(serverParameters, McpJsonMapper.getDefault());
1430 "Calling MCP Tool: '{}.{}' with arguments: {}",
1434 try (McpSyncClient client =
1435 McpClient.sync(stdioTransport)
1436 .requestTimeout(Duration.ofSeconds(requestTimeoutSeconds))
1438 client.initialize();
1439 CallToolRequest request =
new CallToolRequest(toolName, arguments);
1440 return client.callTool(request);
1443 stdioTransport.close();
1448 throw new IllegalArgumentException(
1449 "No MCP tool found for server name: "
1451 +
" and tool name: "
1464 private static String encodeFileToBase64(File file)
throws IOException {
1465 return Base64.getEncoder().encodeToString(Files.readAllBytes(file.toPath()));
1474 private static String encodeByteArrayToBase64(
byte[] bytes) {
1475 return Base64.getEncoder().encodeToString(bytes);
1491 private OllamaResult generateSyncForOllamaRequestModel(
1496 long startTime = System.currentTimeMillis();
1497 int statusCode = -1;
1503 if (responseStreamHandler !=
null) {
1504 ollamaRequestModel.setStream(
true);
1507 ollamaRequestModel, thinkingStreamHandler, responseStreamHandler);
1509 result = requestCaller.
callSync(ollamaRequestModel);
1511 statusCode = result.getHttpStatusCode();
1514 }
catch (InterruptedException e) {
1515 Thread.currentThread().interrupt();
1517 }
catch (Exception e) {
1522 ollamaRequestModel.getModel(),
1523 ollamaRequestModel.isRaw(),
1524 ollamaRequestModel.getThink(),
1525 ollamaRequestModel.isStream(),
1526 ollamaRequestModel.getOptions(),
1527 ollamaRequestModel.getFormat(),
1540 private HttpRequest.Builder getRequestBuilderDefault(URI uri) {
1541 HttpRequest.Builder requestBuilder =
1542 HttpRequest.newBuilder(uri)
1546 .timeout(Duration.ofSeconds(requestTimeoutSeconds));
1550 return requestBuilder;
1558 private boolean isAuthSet() {
1559 return auth !=
null;
1565 public static class OllamaMCPTool {
1566 private String mcpServerName;
1567 private List<MCPToolInfo> toolInfos;
1568 private StdioClientTransport transport;
1574 public static class MCPToolInfo {
1575 private String toolName;
1576 private String toolDescription;
1579 public static class MCPToolConfig {
1580 @JsonProperty(
"command")
1581 public String command;
1583 @JsonProperty("args")
1584 public List<String> args;
1587 public static class MCPToolsConfig {
1588 @JsonProperty(
"mcpServers")
1589 public Map<String, MCPToolConfig> mcpServers;
1595 public static class OllamaMCPToolMatchResponse {
1596 @JsonProperty(
"mcpServerName")
1597 public String mcpServerName;
1599 @JsonProperty("toolName")
1600 public String toolName;
1602 @JsonProperty("arguments")
1603 public Map<String, Object> arguments;
1616 private
Tools.Tool createOllamaToolFromMCPTool(
1617 String mcpServerName,
1618 io.modelcontextprotocol.spec.McpSchema.Tool mcpTool,
1619 ServerParameters serverParameters) {
1620 Map<String,
Tools.
Property> properties =
new java.util.HashMap<>();
1621 java.util.List<String> requiredList =
new java.util.ArrayList<>();
1623 if (mcpTool.inputSchema() !=
null && mcpTool.inputSchema().properties() !=
null) {
1625 java.util.Set<String> requiredSet = new java.util.HashSet<>();
1626 if (mcpTool.inputSchema().required() != null) {
1627 requiredSet.addAll(mcpTool.inputSchema().required());
1629 for (Map.Entry<String, Object> entry : mcpTool.inputSchema().properties().entrySet()) {
1630 String propName = entry.getKey();
1631 Object propertyValue = entry.getValue();
1632 Map<String, Object> propertyMap = null;
1634 if (propertyValue instanceof Map) {
1635 propertyMap = (Map<String, Object>) propertyValue;
1643 propertyMap.get(
"type") !=
null ? propertyMap.get(
"type").toString() :
null;
1645 String description =
null;
1646 if (propertyMap.get(
"description") !=
null) {
1647 description = propertyMap.get(
"description").toString();
1648 }
else if (propertyMap.get(
"title") !=
null) {
1650 description = propertyMap.get(
"title").toString();
1654 boolean propRequired = requiredSet.contains(propName);
1659 .description(description)
1660 .required(propRequired)
1663 properties.put(propName, property);
1665 requiredList.add(propName);
1671 params.setProperties(properties);
1672 params.setRequired(requiredList);
1677 .name(mcpTool.name())
1678 .description(mcpTool.description())
1683 CallToolResult result =
1684 this.callMCPTool(mcpServerName, mcpTool.name(), arguments);
1685 return result.toString();
1688 .mcpServerName(mcpServerName)
1689 .mcpServerParameters(serverParameters)