Skip to content

Commit 5523494

Browse files
authored
Merge pull request #598 from loveTsong/feature/replace-mcp-client
[aipp-plugin] refactor: replace MCP client with LangChain4j MCP module
2 parents e11ef3e + f8f4836 commit 5523494

File tree

11 files changed

+363
-83
lines changed

11 files changed

+363
-83
lines changed

app-builder/plugins/aipp-plugin/pom.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -134,8 +134,8 @@
134134
<artifactId>waterflow-graph-service</artifactId>
135135
</dependency>
136136
<dependency>
137-
<groupId>org.fitframework.fel</groupId>
138-
<artifactId>tool-mcp-client-service</artifactId>
137+
<groupId>dev.langchain4j</groupId>
138+
<artifactId>langchain4j-mcp</artifactId>
139139
</dependency>
140140

141141
<!-- Redis -->

app-builder/plugins/aipp-plugin/src/main/java/modelengine/fit/jober/aipp/fel/FelComponentConfig.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@
99
import modelengine.fel.core.chat.ChatModel;
1010
import modelengine.fel.core.chat.Prompt;
1111
import modelengine.fel.engine.operators.patterns.AbstractAgent;
12-
import modelengine.fel.tool.mcp.client.McpClientFactory;
1312
import modelengine.fel.tool.service.ToolExecuteService;
1413
import modelengine.fit.jober.aipp.constants.AippConst;
14+
import modelengine.fit.jober.aipp.util.McpClientFactory;
1515
import modelengine.fitframework.annotation.Bean;
1616
import modelengine.fitframework.annotation.Component;
1717
import modelengine.fitframework.annotation.Fit;
@@ -29,12 +29,13 @@ public class FelComponentConfig {
2929
*
3030
* @param toolExecuteService 表示工具调用服务的 {@link ToolExecuteService}。
3131
* @param chatModel 表示模型流式服务的 {@link ChatModel}。
32-
* @param mcpClientFactory 表示大模型上下文客户端工厂的 {@link McpClientFactory}。
32+
* @param mcpClientFactory 表示 MCP 客户端工厂的 {@link McpClientFactory}。
3333
* @return 返回 WaterFlow 场景的 Agent 服务的 {@link AbstractAgent}{@code <}{@link Prompt}{@code ,
3434
* }{@link Prompt}{@code >}。
3535
*/
3636
@Bean(AippConst.WATER_FLOW_AGENT_BEAN)
37-
public AbstractAgent getWaterFlowAgent(@Fit ToolExecuteService toolExecuteService, ChatModel chatModel, McpClientFactory mcpClientFactory) {
37+
public AbstractAgent getWaterFlowAgent(@Fit ToolExecuteService toolExecuteService, ChatModel chatModel,
38+
McpClientFactory mcpClientFactory) {
3839
return new WaterFlowAgent(toolExecuteService, chatModel, mcpClientFactory);
3940
}
4041
}

app-builder/plugins/aipp-plugin/src/main/java/modelengine/fit/jober/aipp/fel/WaterFlowAgent.java

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,6 @@
66

77
package modelengine.fit.jober.aipp.fel;
88

9-
import com.alibaba.fastjson.JSON;
10-
import com.alibaba.fastjson.JSONObject;
11-
129
import modelengine.fel.core.chat.ChatMessage;
1310
import modelengine.fel.core.chat.ChatModel;
1411
import modelengine.fel.core.chat.Prompt;
@@ -22,20 +19,20 @@
2219
import modelengine.fel.engine.operators.models.ChatChunk;
2320
import modelengine.fel.engine.operators.models.ChatFlowModel;
2421
import modelengine.fel.engine.operators.patterns.AbstractAgent;
25-
import modelengine.fel.tool.mcp.client.McpClient;
26-
import modelengine.fel.tool.mcp.client.McpClientFactory;
2722
import modelengine.fel.tool.service.ToolExecuteService;
2823
import modelengine.fit.jober.aipp.common.exception.AippErrCode;
2924
import modelengine.fit.jober.aipp.common.exception.AippException;
3025
import modelengine.fit.jober.aipp.constants.AippConst;
26+
import modelengine.fit.jober.aipp.util.LangChain4jMcpClient;
27+
import modelengine.fit.jober.aipp.util.McpClientFactory;
3128
import modelengine.fit.jober.aipp.util.McpUtils;
3229
import modelengine.fit.waterflow.domain.context.StateContext;
3330
import modelengine.fitframework.annotation.Fit;
3431
import modelengine.fitframework.inspection.Validation;
32+
import modelengine.fitframework.log.Logger;
3533
import modelengine.fitframework.util.CollectionUtils;
3634
import modelengine.fitframework.util.ObjectUtils;
3735

38-
import java.io.IOException;
3936
import java.util.Collections;
4037
import java.util.List;
4138
import java.util.Map;
@@ -61,14 +58,14 @@ public class WaterFlowAgent extends AbstractAgent {
6158
*
6259
* @param toolExecuteService 表示工具调用服务的 {@link ToolExecuteService}。
6360
* @param chatStreamModel 表示流式对话大模型的 {@link ChatModel}。
64-
* @param mcpClientFactory 表示大模型上下文客户端工厂的 {@link McpClientFactory}。
61+
* @param mcpClientFactory 表示 MCP 客户端工厂的 {@link McpClientFactory}。
6562
*/
6663
public WaterFlowAgent(@Fit ToolExecuteService toolExecuteService, ChatModel chatStreamModel,
6764
McpClientFactory mcpClientFactory) {
6865
super(new ChatFlowModel(chatStreamModel, null));
6966
this.toolExecuteService = Validation.notNull(toolExecuteService, "The tool execute service cannot be null.");
70-
this.mcpClientFactory = Validation.notNull(mcpClientFactory, "The mcp client factory cannot be null.");
7167
this.agentMsgKey = AGENT_MSG_KEY;
68+
this.mcpClientFactory = mcpClientFactory;
7269
}
7370

7471
@Override
@@ -136,12 +133,10 @@ private ChatMessage callTool(ToolCall toolCall, Map<String, ToolInfo> toolsMap,
136133
if (mcpServerConfig != null) {
137134
String url = Validation.notBlank(ObjectUtils.cast(mcpServerConfig.get(AippConst.MCP_SERVER_URL_KEY)),
138135
"The mcp url should not be empty.");
139-
try (McpClient mcpClient = this.mcpClientFactory.create(McpUtils.getBaseUrl(url),
140-
McpUtils.getSseEndpoint(url))) {
141-
mcpClient.initialize();
142-
Object result = mcpClient.callTool(toolRealName, JSONObject.parseObject(toolCall.arguments()));
143-
return new ToolMessage(toolCall.id(), JSON.toJSONString(result));
144-
} catch (IOException exception) {
136+
try (LangChain4jMcpClient mcpClient = this.mcpClientFactory.create(url)) {
137+
String result = mcpClient.callTool(toolRealName, toolCall.arguments());
138+
return new ToolMessage(toolCall.id(), result);
139+
} catch (Exception exception) {
145140
throw new AippException(AippErrCode.CALL_MCP_SERVER_FAILED, exception.getMessage());
146141
}
147142
}

app-builder/plugins/aipp-plugin/src/main/java/modelengine/fit/jober/aipp/fitable/LlmComponent.java

Lines changed: 32 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,11 @@
2020
import modelengine.fel.engine.flows.AiProcessFlow;
2121
import modelengine.fel.engine.operators.patterns.AbstractAgent;
2222
import modelengine.fel.engine.operators.prompts.Prompts;
23-
import modelengine.fel.tool.mcp.client.McpClient;
24-
import modelengine.fel.tool.mcp.client.McpClientFactory;
25-
import modelengine.fel.tool.mcp.entity.Tool;
2623
import modelengine.fel.tool.model.transfer.ToolData;
2724
import modelengine.fit.jober.aipp.domains.appversion.service.AppVersionService;
2825
import modelengine.fit.jober.aipp.enums.MetaInstStatusEnum;
29-
import modelengine.fit.jober.aipp.util.McpUtils;
26+
import modelengine.fit.jober.aipp.util.LangChain4jMcpClient;
27+
import modelengine.fit.jober.aipp.util.McpClientFactory;
3028
import modelengine.fitframework.inspection.Validation;
3129
import modelengine.jade.store.service.ToolService;
3230
import modelengine.fit.jade.aipp.formatter.OutputFormatterChain;
@@ -70,6 +68,9 @@
7068
import modelengine.fitframework.util.StringUtils;
7169
import modelengine.fitframework.util.UuidUtils;
7270

71+
import dev.langchain4j.model.chat.request.json.JsonObjectSchema;
72+
import dev.langchain4j.agent.tool.ToolSpecification;
73+
7374
import java.io.IOException;
7475
import java.util.ArrayList;
7576
import java.util.Collections;
@@ -113,9 +114,9 @@ public class LlmComponent implements FlowableService {
113114
private final AippModelCenter aippModelCenter;
114115
private final PromptBuilderChain promptBuilderChain;
115116
private final AppTaskInstanceService appTaskInstanceService;
116-
private final McpClientFactory mcpClientFactory;
117117
private final OutputFormatterChain formatterChain;
118118
private final AppVersionService appVersionService;
119+
private final McpClientFactory mcpClientFactory;
119120

120121
/**
121122
* 大模型节点构造器,内部通过提供的 agent 和 tool 构建智能体工作流。
@@ -129,7 +130,9 @@ public class LlmComponent implements FlowableService {
129130
* @param aippModelCenter 表示模型中心的 {@link AippModelCenter}。
130131
* @param promptBuilderChain 表示提示器构造器职责链的 {@link PromptBuilderChain}。
131132
* @param appTaskInstanceService 表示任务实例服务的 {@link AppTaskInstanceService}。
132-
* @param mcpClientFactory 表示大模型上下文客户端工厂的 {@link McpClientFactory}。
133+
* @param formatterChain 表示输出格式化器链的 {@link OutputFormatterChain}。
134+
* @param appVersionService 表示应用版本服务的 {@link AppVersionService}。
135+
* @param mcpClientFactory 表示 MCP 客户端工厂的 {@link McpClientFactory}。
133136
*/
134137
public LlmComponent(FlowInstanceService flowInstanceService,
135138
@Fit ToolService toolService,
@@ -141,13 +144,15 @@ public LlmComponent(FlowInstanceService flowInstanceService,
141144
PromptBuilderChain promptBuilderChain,
142145
AppTaskInstanceService appTaskInstanceService,
143146
OutputFormatterChain formatterChain,
144-
McpClientFactory mcpClientFactory, AppVersionService appVersionService) {
147+
AppVersionService appVersionService,
148+
McpClientFactory mcpClientFactory) {
145149
this.flowInstanceService = flowInstanceService;
146150
this.toolService = toolService;
147151
this.aippLogService = aippLogService;
148152
this.aippLogStreamService = aippLogStreamService;
149153
this.serializer = notNull(serializer, "The serializer cannot be nul.");
150154
this.aippModelCenter = aippModelCenter;
155+
this.mcpClientFactory = mcpClientFactory;
151156

152157
// handleTask从入口开始处理,callback从agent node开始处理
153158
this.agentFlow = AiFlows.<Tip>create()
@@ -157,7 +162,6 @@ public LlmComponent(FlowInstanceService flowInstanceService,
157162
.close();
158163
this.promptBuilderChain = promptBuilderChain;
159164
this.appTaskInstanceService = appTaskInstanceService;
160-
this.mcpClientFactory = notNull(mcpClientFactory, "The mcp client factory cannot be null.");
161165
this.formatterChain = formatterChain;
162166
this.appVersionService = appVersionService;
163167
}
@@ -482,12 +486,12 @@ private List<ToolInfo> buildMcpToolInfos(Map<String, Object> mcpServersConfig) {
482486
String url = Validation.notBlank(ObjectUtils.cast(serverConfig.get(AippConst.MCP_SERVER_URL_KEY)),
483487
"The mcp url should not be empty.");
484488

485-
try (McpClient mcpClient = this.mcpClientFactory.create(McpUtils.getBaseUrl(url),
486-
McpUtils.getSseEndpoint(url))) {
487-
mcpClient.initialize();
488-
List<Tool> tools = mcpClient.getTools();
489-
result.addAll(tools.stream().map(tool -> buildMcpToolInfo(serverName, tool, serverConfig)).toList());
490-
} catch (IOException exception) {
489+
try (LangChain4jMcpClient mcpClient = this.mcpClientFactory.create(url)) {
490+
List<ToolSpecification> tools = mcpClient.getTools();
491+
result.addAll(tools.stream()
492+
.map(tool -> buildMcpToolInfo(serverName, tool, serverConfig))
493+
.toList());
494+
} catch (Exception exception) {
491495
throw new AippException(AippErrCode.CALL_MCP_SERVER_FAILED, exception.getMessage());
492496
}
493497
});
@@ -515,14 +519,23 @@ private ToolInfo buildToolInfo(ToolData toolData) {
515519
.build();
516520
}
517521

518-
private static ToolInfo buildMcpToolInfo(String serverName, Tool tool, Map<String, Object> serverConfig) {
522+
private static ToolInfo buildMcpToolInfo(String serverName,
523+
ToolSpecification tool, Map<String, Object> serverConfig) {
524+
JsonObjectSchema toolParams = tool.parameters();
525+
Map<String, Object> parametersMap = new HashMap<>();
526+
if (toolParams != null) {
527+
parametersMap.put("type", "object");
528+
parametersMap.put("properties", toolParams.properties());
529+
parametersMap.put("required", toolParams.required());
530+
}
531+
519532
return ToolInfo.custom()
520-
.name(buildUniqueToolName(AippConst.MCP_SERVER_TYPE, serverName, tool.getName()))
521-
.description(tool.getDescription())
522-
.parameters(tool.getInputSchema())
533+
.name(buildUniqueToolName(AippConst.MCP_SERVER_TYPE, serverName, tool.name()))
534+
.description(tool.description())
535+
.parameters(parametersMap)
523536
.extensions(MapBuilder.<String, Object>get()
524537
.put(AippConst.MCP_SERVER_KEY, serverConfig)
525-
.put(AippConst.TOOL_REAL_NAME, tool.getName())
538+
.put(AippConst.TOOL_REAL_NAME, tool.name())
526539
.build())
527540
.build();
528541
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) 2025 Huawei Technologies Co., Ltd. All rights reserved.
3+
* This file is a part of the ModelEngine Project.
4+
* Licensed under the MIT License. See License.txt in the project root for license information.
5+
*--------------------------------------------------------------------------------------------*/
6+
7+
package modelengine.fit.jober.aipp.util;
8+
9+
import modelengine.fitframework.annotation.Component;
10+
11+
/**
12+
* MCP 客户端工厂的默认实现,使用 {@link LangChain4jMcpClient} 创建客户端实例。
13+
*
14+
* @author songyongtan
15+
* @since 2026-03-02
16+
*/
17+
@Component
18+
public class DefaultMcpClientFactory implements McpClientFactory {
19+
@Override
20+
public LangChain4jMcpClient create(String url) {
21+
return new LangChain4jMcpClient(url);
22+
}
23+
}
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) 2025 Huawei Technologies Co., Ltd. All rights reserved.
3+
* This file is a part of the ModelEngine Project.
4+
* Licensed under the MIT License. See License.txt in the project root for license information.
5+
*--------------------------------------------------------------------------------------------*/
6+
7+
8+
package modelengine.fit.jober.aipp.util;
9+
10+
import dev.langchain4j.agent.tool.ToolExecutionRequest;
11+
import dev.langchain4j.agent.tool.ToolSpecification;
12+
import dev.langchain4j.mcp.client.DefaultMcpClient;
13+
import dev.langchain4j.mcp.client.McpClient;
14+
import dev.langchain4j.mcp.client.transport.http.HttpMcpTransport;
15+
16+
import modelengine.fit.jober.aipp.common.exception.AippErrCode;
17+
import modelengine.fit.jober.aipp.common.exception.AippException;
18+
import modelengine.fitframework.util.StringUtils;
19+
20+
import java.util.List;
21+
22+
/**
23+
* LangChain4jMcpClient is a client for calling ModelEngine's MCP server.
24+
*
25+
* @author songyongtan
26+
* @since 2026-03-01
27+
*/
28+
public class LangChain4jMcpClient implements AutoCloseable {
29+
private final McpClient mcpClient;
30+
private final String url;
31+
32+
/**
33+
* 构造函数,用于初始化LangChain4jMcpClient对象。
34+
*
35+
* @param url MCP服务器的URL,格式为http://host:port
36+
*/
37+
public LangChain4jMcpClient(String url) {
38+
this.url = url;
39+
40+
HttpMcpTransport transport = new HttpMcpTransport.Builder()
41+
.sseUrl(url)
42+
.build();
43+
44+
this.mcpClient = new DefaultMcpClient.Builder()
45+
.transport(transport)
46+
.build();
47+
}
48+
49+
/**
50+
* 获取MCP服务器上注册的所有工具。
51+
*
52+
* @return 包含所有工具规范的列表
53+
*/
54+
public List<ToolSpecification> getTools() {
55+
try {
56+
return this.mcpClient.listTools();
57+
} catch (Exception e) {
58+
throw new AippException(AippErrCode.CALL_MCP_SERVER_FAILED,
59+
StringUtils.format("Failed to get tools from MCP server. [url={0}]", this.url), e);
60+
}
61+
}
62+
63+
/**
64+
* 调用MCP服务器上的指定工具。
65+
*
66+
* @param toolName 要调用的工具名称
67+
* @param arguments 工具调用的参数,格式为JSON字符串
68+
* @return 工具调用的结果,格式为JSON字符串
69+
*/
70+
public String callTool(String toolName, String arguments) {
71+
try {
72+
ToolExecutionRequest request = ToolExecutionRequest.builder()
73+
.name(toolName)
74+
.arguments(arguments)
75+
.build();
76+
return mcpClient.executeTool(request).resultText();
77+
} catch (Exception e) {
78+
throw new AippException(AippErrCode.CALL_MCP_SERVER_FAILED,
79+
StringUtils.format("Failed to call tool. [toolName={0}, url={1}]", toolName, this.url), e);
80+
}
81+
}
82+
83+
@Override
84+
public void close() {
85+
try {
86+
this.mcpClient.close();
87+
} catch (Exception e) {
88+
throw new AippException(AippErrCode.CALL_MCP_SERVER_FAILED,
89+
StringUtils.format("Failed to close MCP client. [url={0}]", this.url), e);
90+
}
91+
}
92+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) 2025 Huawei Technologies Co., Ltd. All rights reserved.
3+
* This file is a part of the ModelEngine Project.
4+
* Licensed under the MIT License. See License.txt in the project root for license information.
5+
*--------------------------------------------------------------------------------------------*/
6+
7+
package modelengine.fit.jober.aipp.util;
8+
9+
/**
10+
* MCP 客户端工厂接口,用于创建 {@link LangChain4jMcpClient} 实例。
11+
*
12+
* @author songyongtan
13+
* @since 2026-03-02
14+
*/
15+
public interface McpClientFactory {
16+
/**
17+
* 创建 MCP 客户端实例。
18+
*
19+
* @param url 表示 MCP 服务器的 URL。
20+
* @return 返回创建的 {@link LangChain4jMcpClient} 实例。
21+
*/
22+
LangChain4jMcpClient create(String url);
23+
}

0 commit comments

Comments
 (0)