Skip to content

Commit 72ee6f0

Browse files
authored
McpClient and ToolProvider support added (helidon-io#10533)
McpClient and ToolProvider support added Signed-off-by: David Kral <david.k.kral@oracle.com>
1 parent 4c01a02 commit 72ee6f0

8 files changed

Lines changed: 377 additions & 0 deletions

File tree

docs/src/main/asciidoc/se/integrations/langchain4j/langchain4j.adoc

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,8 @@ Helidon's LangChain4J integration introduces a declarative Helidon Inject-based
240240
** *Retrieval Augmentor*: `dev.langchain4j.rag.RetrievalAugmentor`
241241
* *Callback Functions*:
242242
** Methods annotated with `dev.langchain4j.agent.tool.Tool`
243+
* *MCP Client*:
244+
** `dev.langchain4j.mcp.client.McpClient`
243245
244246
=== Creating AI Service
245247
@@ -270,6 +272,8 @@ In this scenario all LangChain4J components from the list above are taken from t
270272
| `Ai.ModerationModel` | Specifies the name of a service in the service registry that implements `ModerationModel` to use in the annotated Ai Service.
271273
| `Ai.ContentRetriever` | Specifies the name of a service in the service registry that implements `ContentRetriever` to use in the annotated Ai Service. Mutually exclusive with `Ai.RetrievalAugmentor`.
272274
| `Ai.RetrievalAugmentor` | Specifies the name of a service in the service registry that implements `RetrievalAugmentor` to use in the annotated Ai Service. Mutually exclusive with `Ai.ContentRetriever`.
275+
| `Ai.ToolProvider` | Specifies the name of a service in the service registry that implements `ToolProvider` to use in the annotated Ai Service. Mutually exclusive with `Ai.McpClients`.
276+
| `Ai.McpClients` | Specifies the name/s of a `McpClient` in the service registry that implements `ToolProvider` to use in the annotated Ai Service. `McpToolProvider` is created from these clients. Mutually exclusive with `Ai.ToolProvider`.
273277
|===
274278
275279
For example, in the snippet below a service with name "myCustomChatMemory" will be used as chat memory and all other components are discovered automatically.
@@ -285,6 +289,12 @@ public interface ChatAiService {
285289
286290
NOTE: There is a possibility to switch off automatic discovery by using `@Ai.Service(autodicovery=false)`. In this case the service components are not discovered automatically and users must add components manually using annotations specified above. `@ChatModel` or `@StreamingChatModel` annotations are required.
287291
292+
Since `Ai.ToolProvider` and `Ai.McpClients` are mutually exclusive, some clarification is needed regarding their functionality. Assume that automatic discovery is set to true in this context:
293+
294+
* If neither annotation is present, a `ToolProvider` will be automatically discovered from the service registry.
295+
* If the `Ai.McpClients` annotation is present, corresponding MCP client services will be discovered, and no `ToolProvider` will be discovered.
296+
* If both annotations are present, an exception will be thrown.
297+
288298
=== Tools (Callback Functions)
289299
290300
In LangChain4J, tools are callback functions that the language model can invoke during a conversation to perform specific tasks, retrieve information, or execute external logic. These tools extend the model's capabilities beyond simple text generation, allowing it to dynamically interact with external systems. For instance, a tool might query a database, call an external API, or perform calculations. Based on user input, the model can decide to call a tool, interpret its response, and incorporate it into the conversation for a more context-aware and multi-step interaction.
@@ -307,6 +317,24 @@ NOTE: If you are using Helidon MP, to enable `@Tool`-annotated methods in CDI be
307317
308318
For more details, read the https://docs.langchain4j.dev/tutorials/tools#high-level-tool-api[LangChain4J Documentation on Tools].
309319
320+
=== MCP Client
321+
In LangChain4J, an MCP (Model Context Protocol) client acts as a bridge between the language model and external services or resources that follow the MCP standard. Instead of directly embedding custom logic into the application, the MCP client enables the model to discover, connect to, and interact with external tools and data providers in a standardized way.
322+
323+
To add MCP Clients to your AI Service, use the following:
324+
[source,java]
325+
----
326+
@Ai.Service
327+
@Ai.McpClients
328+
public interface ChatAiService {
329+
String chat(String question);
330+
}
331+
----
332+
333+
If you want to have your MCP clients created from the configuration, it should be placed under the `langchain4j.mcp-clients`.
334+
These are all the MCP Client configuration options currently supported:
335+
336+
include::{rootdir}/config/io_helidon_integrations_langchain4j_McpClientConfig.adoc[leveloffset=+2,tag=config]
337+
310338
== Additional Information
311339
312340
* https://docs.langchain4j.dev/[LangChain4J documentation]

integrations/langchain4j/codegen/src/main/java/io/helidon/integrations/langchain4j/codegen/AiServiceCodegen.java

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,21 +42,26 @@
4242
import static io.helidon.integrations.langchain4j.codegen.LangchainTypes.AI_CHAT_MEMORY_WINDOW;
4343
import static io.helidon.integrations.langchain4j.codegen.LangchainTypes.AI_CHAT_MODEL;
4444
import static io.helidon.integrations.langchain4j.codegen.LangchainTypes.AI_CONTENT_RETRIEVER;
45+
import static io.helidon.integrations.langchain4j.codegen.LangchainTypes.AI_MCP_CLIENTS;
4546
import static io.helidon.integrations.langchain4j.codegen.LangchainTypes.AI_MODERATION_MODEL;
4647
import static io.helidon.integrations.langchain4j.codegen.LangchainTypes.AI_RETRIEVER_AUGMENTOR;
4748
import static io.helidon.integrations.langchain4j.codegen.LangchainTypes.AI_SERVICE;
4849
import static io.helidon.integrations.langchain4j.codegen.LangchainTypes.AI_STREAMING_CHAT_MODEL;
4950
import static io.helidon.integrations.langchain4j.codegen.LangchainTypes.AI_TOOLS;
51+
import static io.helidon.integrations.langchain4j.codegen.LangchainTypes.AI_TOOL_PROVIDER;
5052
import static io.helidon.integrations.langchain4j.codegen.LangchainTypes.LC_AI_SERVICES;
5153
import static io.helidon.integrations.langchain4j.codegen.LangchainTypes.LC_CHAT_MEMORY;
5254
import static io.helidon.integrations.langchain4j.codegen.LangchainTypes.LC_CHAT_MEMORY_PROVIDER;
5355
import static io.helidon.integrations.langchain4j.codegen.LangchainTypes.LC_CHAT_MEMORY_STORE;
5456
import static io.helidon.integrations.langchain4j.codegen.LangchainTypes.LC_CHAT_MEMORY_WINDOW;
5557
import static io.helidon.integrations.langchain4j.codegen.LangchainTypes.LC_CHAT_MODEL;
5658
import static io.helidon.integrations.langchain4j.codegen.LangchainTypes.LC_CONTENT_RETRIEVER;
59+
import static io.helidon.integrations.langchain4j.codegen.LangchainTypes.LC_MCP_CLIENT;
60+
import static io.helidon.integrations.langchain4j.codegen.LangchainTypes.LC_MCP_TOOL_PROVIDER;
5761
import static io.helidon.integrations.langchain4j.codegen.LangchainTypes.LC_MODERATION_MODEL;
5862
import static io.helidon.integrations.langchain4j.codegen.LangchainTypes.LC_RETRIEVAL_AUGMENTOR;
5963
import static io.helidon.integrations.langchain4j.codegen.LangchainTypes.LC_STREAMING_CHAT_MODEL;
64+
import static io.helidon.integrations.langchain4j.codegen.LangchainTypes.LC_TOOL_PROVIDER;
6065
import static io.helidon.service.codegen.ServiceCodegenTypes.SERVICE_ANNOTATION_NAMED;
6166
import static io.helidon.service.codegen.ServiceCodegenTypes.SERVICE_ANNOTATION_SINGLETON;
6267

@@ -162,6 +167,46 @@ private void aiServicesParameter(Constructor.Builder ctr,
162167
.addContentLine(");");
163168
}
164169

170+
private void aiMcpClientParameter(Constructor.Builder builder, TypeInfo aiInterface, CodegenContext ctx) {
171+
Optional<TypeInfo> mcpClientTypeInfo = ctx.typeInfo(LC_MCP_CLIENT);
172+
if (mcpClientTypeInfo.isEmpty()) {
173+
//McpClients annotation present. Dependency needs to be added.
174+
throw new CodegenException("McpClients annotation is being used, "
175+
+ "but the required LC4J MCP dependency is missing. "
176+
+ "Please add: dev.langchain4j:langchain4j-mcp");
177+
}
178+
List<String> mcpClients = aiInterface.findAnnotation(AI_MCP_CLIENTS)
179+
.flatMap(Annotation::stringValues)
180+
.orElseGet(List::of);
181+
List<String> mcpClientParameters = new ArrayList<>();
182+
if (mcpClients.isEmpty()) {
183+
builder.addParameter(tools -> tools
184+
.name("mcpClients")
185+
.type(listType(LC_MCP_CLIENT)));
186+
mcpClientParameters.add("mcpClients");
187+
} else {
188+
int index = 1;
189+
for (String clientName : mcpClients) {
190+
String toolParameter = "mcpClient_" + index++;
191+
builder.addParameter(param -> param
192+
.name(toolParameter)
193+
.type(LC_MCP_CLIENT)
194+
.addAnnotation(namedAnnotation(clientName)));
195+
mcpClientParameters.add(toolParameter);
196+
}
197+
}
198+
builder.addContent("var mcpToolProvider = ")
199+
.addContent(LC_MCP_TOOL_PROVIDER)
200+
.addContentLine(".builder()")
201+
.increaseContentPadding()
202+
.addContent(".mcpClients(")
203+
.addContent(String.join(", ", mcpClientParameters))
204+
.addContentLine(")")
205+
.addContentLine(".build();")
206+
.decreaseContentPadding();
207+
builder.addContentLine("builder.toolProvider(mcpToolProvider);");
208+
}
209+
165210
private Annotation namedAnnotation(String modelName) {
166211
return Annotation.create(SERVICE_ANNOTATION_NAMED, modelName);
167212
}
@@ -243,6 +288,7 @@ private void generateInterfaceSupplier(RoundContext roundCtx, TypeInfo aiInterfa
243288
AI_CONTENT_RETRIEVER,
244289
LC_CONTENT_RETRIEVER,
245290
"contentRetriever");
291+
aiMcpClientAndToolProvider(it, aiInterface, autoDiscovery, ctx);
246292
aiServicesToolParameters(it,
247293
aiInterface);
248294
})
@@ -262,6 +308,25 @@ private void generateInterfaceSupplier(RoundContext roundCtx, TypeInfo aiInterfa
262308
roundCtx.addGeneratedType(generatedType, classModel, aiInterfaceType, aiInterface.originatingElementValue());
263309
}
264310

311+
private void aiMcpClientAndToolProvider(Constructor.Builder it,
312+
TypeInfo aiInterface,
313+
boolean autoDiscovery,
314+
CodegenContext ctx) {
315+
if (aiInterface.hasAnnotation(AI_TOOL_PROVIDER) && aiInterface.hasAnnotation(AI_MCP_CLIENTS)) {
316+
throw new CodegenException("McpClients and ToolProvider annotations cannot be used at the same time.",
317+
aiInterface.originatingElementValue());
318+
} else if (aiInterface.hasAnnotation(AI_MCP_CLIENTS)) {
319+
aiMcpClientParameter(it, aiInterface, ctx);
320+
} else {
321+
aiServicesParameter(it,
322+
autoDiscovery,
323+
aiInterface,
324+
AI_TOOL_PROVIDER,
325+
LC_TOOL_PROVIDER,
326+
"toolProvider");
327+
}
328+
}
329+
265330
private void aiServicesChatMemoryConstructor(Constructor.Builder ctr, boolean autoDiscovery, TypeInfo aiInterface) {
266331
Optional<Annotation> chatMemoryWindow = aiInterface.findAnnotation(AI_CHAT_MEMORY_WINDOW);
267332

integrations/langchain4j/codegen/src/main/java/io/helidon/integrations/langchain4j/codegen/LangchainTypes.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ final class LangchainTypes {
2929
static final TypeName AI_MODERATION_MODEL = TypeName.create("io.helidon.integrations.langchain4j.Ai.ModerationModel");
3030
static final TypeName AI_CONTENT_RETRIEVER = TypeName.create("io.helidon.integrations.langchain4j.Ai.ContentRetriever");
3131
static final TypeName AI_RETRIEVER_AUGMENTOR = TypeName.create("io.helidon.integrations.langchain4j.Ai.RetrievalAugmentor");
32+
static final TypeName AI_TOOL_PROVIDER = TypeName.create("io.helidon.integrations.langchain4j.Ai.ToolProvider");
33+
static final TypeName AI_MCP_CLIENTS = TypeName.create("io.helidon.integrations.langchain4j.Ai.McpClients");
3234
static final TypeName AI_TOOLS = TypeName.create("io.helidon.integrations.langchain4j.Ai.Tools");
3335
static final TypeName AI_TOOL = TypeName.create("io.helidon.integrations.langchain4j.Ai.Tool");
3436
static final Annotation TOOL_QUALIFIER_ANNOTATION = Annotation.create(AI_TOOL);
@@ -58,6 +60,9 @@ final class LangchainTypes {
5860
static final TypeName LC_HTTP_CLIENT_BUILDER = TypeName.create("dev.langchain4j.http.client.HttpClientBuilder");
5961
static final TypeName LC_CHAT_MODEL_LISTENER = TypeName.create("dev.langchain4j.model.chat.listener.ChatModelListener");
6062
static final TypeName LC_DEF_REQUEST_PARAMS = TypeName.create("dev.langchain4j.model.chat.request.ChatRequestParameters");
63+
static final TypeName LC_TOOL_PROVIDER = TypeName.create("dev.langchain4j.service.tool.ToolProvider");
64+
static final TypeName LC_MCP_TOOL_PROVIDER = TypeName.create("dev.langchain4j.mcp.McpToolProvider");
65+
static final TypeName LC_MCP_CLIENT = TypeName.create("dev.langchain4j.mcp.client.McpClient");
6166

6267
static final TypeName SVC_QUALIFIED_INSTANCE = TypeName.create("io.helidon.service.registry.Service.QualifiedInstance");
6368
static final TypeName SVC_SERVICES_FACTORY = TypeName.create("io.helidon.service.registry.Service.ServicesFactory");

integrations/langchain4j/langchain4j/pom.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,10 @@
6363
<groupId>dev.langchain4j</groupId>
6464
<artifactId>langchain4j</artifactId>
6565
</dependency>
66+
<dependency>
67+
<groupId>dev.langchain4j</groupId>
68+
<artifactId>langchain4j-mcp</artifactId>
69+
</dependency>
6670
<dependency>
6771
<groupId>io.helidon.common.features</groupId>
6872
<artifactId>helidon-common-features-api</artifactId>

integrations/langchain4j/langchain4j/src/main/java/io/helidon/integrations/langchain4j/Ai.java

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,38 @@ private Ai() {
231231
String value();
232232
}
233233

234+
/**
235+
* Annotation to specify a ToolProvider for the service.
236+
* This is mutually exclusive with the {@link McpClients}.
237+
*/
238+
@Target(TYPE)
239+
@Retention(RUNTIME)
240+
public @interface ToolProvider {
241+
/**
242+
* Name of the Tool provider to be used.
243+
*
244+
* @return name of the Tool provider
245+
*/
246+
String value();
247+
}
248+
249+
/**
250+
* Annotation to specify an MCP Clients to be used in McpToolProvider.
251+
* This is mutually exclusive with the {@link ToolProvider}.
252+
* It requires to have {@code dev.langchain4j:langchain4j-mcp} dependency added on the classpath for it to work properly.
253+
*/
254+
@Target(TYPE)
255+
@Retention(RUNTIME)
256+
public @interface McpClients {
257+
/**
258+
* Names of the MCP Clients to be used in McpToolProvider creation.
259+
* These names are mapped from the {@code key} values of the MCP Client.
260+
*
261+
* @return client names
262+
*/
263+
String[] value() default {};
264+
}
265+
234266
/**
235267
* Annotation to manually specify classes containing tools for the service.
236268
*/
Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
/*
2+
* Copyright (c) 2025 Oracle and/or its affiliates.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.helidon.integrations.langchain4j;
18+
19+
import java.net.URI;
20+
import java.time.Duration;
21+
import java.util.Optional;
22+
23+
import io.helidon.builder.api.Option;
24+
import io.helidon.builder.api.Prototype;
25+
26+
@Prototype.Configured
27+
@Prototype.Blueprint
28+
interface McpClientConfigBlueprint {
29+
30+
/**
31+
* The default configuration prefix.
32+
*/
33+
String CONFIG_ROOT = "langchain4j.mcp-clients";
34+
35+
/**
36+
* The initial URI where to connect to the server and request a SSE
37+
* channel.
38+
*
39+
* @return sse uri
40+
*/
41+
@Option.Configured
42+
URI sseUri();
43+
44+
/**
45+
* Sets the name that the client will use to identify itself to the
46+
* MCP server in the initialization message.
47+
* Overwrites the default client name from langchain4j.
48+
*
49+
* @return client name
50+
*/
51+
@Option.Configured
52+
Optional<String> clientName();
53+
54+
/**
55+
* Sets the version string that the client will use to identify
56+
* itself to the MCP server in the initialization message.
57+
* Overwrites the default client version from langchain4j.
58+
*
59+
* @return client version
60+
*/
61+
@Option.Configured
62+
Optional<String> clientVersion();
63+
64+
/**
65+
* Sets a unique identifier for the client. If none is provided, a
66+
* UUID will be automatically generated. This key is later used to identify the client
67+
* in the service registry.
68+
*
69+
* @return client key
70+
*/
71+
@Option.Configured
72+
Optional<String> key();
73+
74+
/**
75+
* Sets the protocol version that the client will advertise in the
76+
* initialization message. Overwrites the default version from langchain4j.
77+
*
78+
* @return protocol version
79+
*/
80+
@Option.Configured
81+
Optional<String> protocolVersion();
82+
83+
/**
84+
* Sets the timeout for initializing the client.
85+
* Overwrites the default timeout for initializing from langchain4j.
86+
*
87+
* @return initialization timout
88+
*/
89+
@Option.Configured
90+
Optional<Duration> initializationTimeout();
91+
92+
/**
93+
* Sets the timeout for tool execution.
94+
* This value applies to each tool execution individually.
95+
* A value of zero means no timeout.
96+
* Overwrites the default timeout for tool execution from langchain4j.
97+
*
98+
* @return tool execution timout
99+
*/
100+
@Option.Configured
101+
Optional<Duration> toolExecutionTimeout();
102+
103+
/**
104+
* Sets the timeout for resource-related operations (listing resources as well as reading the contents of a resource).
105+
* A value of zero means no timeout.
106+
* Overwrites the default timeout for resource-related operations from langchain4j.
107+
*
108+
* @return resources timeout
109+
*/
110+
@Option.Configured
111+
Optional<Duration> resourcesTimeout();
112+
113+
/**
114+
* The timeout for prompt-related operations (listing prompts as well as rendering the contents of a prompt).
115+
* A value of zero means no timeout.
116+
* Overwrites the default timeout for prompt-related operations from langchain4j.
117+
*
118+
* @return prompts timeout
119+
*/
120+
@Option.Configured
121+
Optional<Duration> promptsTimeout();
122+
123+
/**
124+
* The timeout to apply when waiting for a ping response.
125+
* Overwrites the default timeout when waiting for a ping response from langchain4j.
126+
*
127+
* @return ping timeout
128+
*/
129+
@Option.Configured
130+
Optional<Duration> pingTimeout();
131+
132+
/**
133+
* The delay before attempting to reconnect after a failed connection.
134+
* Overwrites the default reconnect interval from langchain4j.
135+
*
136+
* @return reconnect interval
137+
*/
138+
@Option.Configured
139+
Optional<Duration> reconnectInterval();
140+
141+
/**
142+
* The error message to return when a tool execution times out.
143+
* Overwrites the default error message from langchain4j.
144+
*
145+
* @return time out error message
146+
*/
147+
@Option.Configured
148+
Optional<String> toolExecutionTimeoutErrorMessage();
149+
150+
/**
151+
* Whether to log request traffic.
152+
*
153+
* @return log request traffic
154+
*/
155+
@Option.Configured
156+
Optional<Boolean> logRequests();
157+
158+
/**
159+
* Whether to log response traffic.
160+
*
161+
* @return log response traffic
162+
*/
163+
@Option.Configured
164+
Optional<Boolean> logResponses();
165+
166+
}

0 commit comments

Comments
 (0)