Skip to content

Commit d7d7c62

Browse files
authored
[fel] Add MCP error handle (ModelEngine-Group#234)
* [fel] Add MCP error handle * [fel] remove tools lock
1 parent 39b7c0c commit d7d7c62

File tree

2 files changed

+108
-21
lines changed

2 files changed

+108
-21
lines changed

framework/fel/java/plugins/tool-mcp-client/src/main/java/modelengine/fel/tool/mcp/client/support/DefaultMcpClient.java

Lines changed: 36 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -72,12 +72,10 @@ public class DefaultMcpClient implements McpClient {
7272
private volatile ServerSchema serverSchema;
7373
private volatile boolean initialized = false;
7474
private volatile boolean closed = false;
75-
private final List<Tool> tools = new ArrayList<>();
7675
private final Object initializedLock = LockUtils.newSynchronizedLock();
77-
private final Object toolsLock = LockUtils.newSynchronizedLock();
7876
private final Map<Long, Consumer<JsonRpc.Response<Long>>> responseConsumers = new ConcurrentHashMap<>();
7977
private final Map<Long, Boolean> pendingRequests = new ConcurrentHashMap<>();
80-
private final Map<Long, Object> pendingResults = new ConcurrentHashMap<>();
78+
private final Map<Long, Result> pendingResults = new ConcurrentHashMap<>();
8179

8280
private volatile Subscription subscription;
8381
private volatile ThreadPoolScheduler pingScheduler;
@@ -197,10 +195,6 @@ private void initializedMcpServer(JsonRpc.Response<Long> response) {
197195
response.error());
198196
throw new IllegalStateException(response.error().toString());
199197
}
200-
synchronized (this.initializedLock) {
201-
this.initialized = true;
202-
this.initializedLock.notifyAll();
203-
}
204198
this.recordServerSchema(response);
205199
HttpClassicClientRequest request =
206200
this.client.createRequest(HttpRequestMethod.POST, this.baseUri + this.messageEndpoint);
@@ -225,6 +219,10 @@ private void initializedMcpServer(JsonRpc.Response<Long> response) {
225219
} catch (IOException e) {
226220
throw new IllegalStateException(e);
227221
}
222+
synchronized (this.initializedLock) {
223+
this.initialized = true;
224+
this.initializedLock.notifyAll();
225+
}
228226
this.pingScheduler = ThreadPoolScheduler.custom()
229227
.threadPoolName("mcp-client-ping-" + this.name)
230228
.awaitTermination(3, TimeUnit.SECONDS)
@@ -262,27 +260,30 @@ public List<Tool> getTools() {
262260
while (this.pendingRequests.get(requestId)) {
263261
ThreadUtils.sleep(100);
264262
}
265-
synchronized (this.toolsLock) {
266-
return this.tools;
263+
Result result = this.pendingResults.remove(requestId);
264+
this.pendingRequests.remove(requestId);
265+
if (result.isSuccess()) {
266+
return ObjectUtils.cast(result.getContent());
267+
} else {
268+
throw new IllegalStateException(result.getError());
267269
}
268270
}
269271

270272
private void getTools0(JsonRpc.Response<Long> response) {
271273
if (response.error() != null) {
272-
log.error("Failed to get tools list from MCP server. [sessionId={}, response={}]",
274+
String error = StringUtils.format("Failed to get tools list from MCP server. [sessionId={0}, response={1}]",
273275
this.sessionId,
274276
response);
277+
this.pendingResults.put(response.id(), Result.error(error));
275278
this.pendingRequests.put(response.id(), false);
276279
return;
277280
}
278281
Map<String, Object> result = cast(response.result());
279282
List<Map<String, Object>> rawTools = cast(result.get("tools"));
280-
synchronized (this.toolsLock) {
281-
this.tools.clear();
282-
this.tools.addAll(rawTools.stream()
283-
.map(rawTool -> ObjectUtils.<Tool>toCustomObject(rawTool, Tool.class))
284-
.toList());
285-
}
283+
List<Tool> tools = new ArrayList<>(rawTools.stream()
284+
.map(rawTool -> ObjectUtils.<Tool>toCustomObject(rawTool, Tool.class))
285+
.toList());
286+
this.pendingResults.put(response.id(), Result.success(tools));
286287
this.pendingRequests.put(response.id(), false);
287288
}
288289

@@ -303,32 +304,46 @@ public Object callTool(String name, Map<String, Object> arguments) {
303304
while (this.pendingRequests.get(requestId)) {
304305
ThreadUtils.sleep(100);
305306
}
306-
return this.pendingResults.get(requestId);
307+
Result result = this.pendingResults.remove(requestId);
308+
this.pendingRequests.remove(requestId);
309+
if (result.isSuccess()) {
310+
return result.getContent();
311+
} else {
312+
throw new IllegalStateException(result.getError());
313+
}
307314
}
308315

309316
private void callTools0(JsonRpc.Response<Long> response) {
310317
if (response.error() != null) {
311-
log.error("Failed to call tool from MCP server. [sessionId={}, response={}]", this.sessionId, response);
318+
String error = StringUtils.format("Failed to call tool from MCP server. [sessionId={0}, response={1}]",
319+
this.sessionId,
320+
response);
321+
this.pendingResults.put(response.id(), Result.error(error));
312322
this.pendingRequests.put(response.id(), false);
313323
return;
314324
}
315325
Map<String, Object> result = cast(response.result());
316326
boolean isError = cast(result.get("isError"));
317327
if (isError) {
318-
log.error("Failed to call tool from MCP server. [sessionId={}, result={}]", this.sessionId, result);
328+
String error = StringUtils.format("Failed to call tool from MCP server. [sessionId={0}, result={1}]",
329+
this.sessionId,
330+
result);
331+
this.pendingResults.put(response.id(), Result.error(error));
319332
this.pendingRequests.put(response.id(), false);
320333
return;
321334
}
322335
List<Map<String, Object>> rawContents = cast(result.get("content"));
323336
if (CollectionUtils.isEmpty(rawContents)) {
324-
log.error("Failed to call tool from MCP server: no result returned. [sessionId={}, result={}]",
337+
String error = StringUtils.format(
338+
"Failed to call tool from MCP server: no result returned. [sessionId={0}, result={1}]",
325339
this.sessionId,
326340
result);
341+
this.pendingResults.put(response.id(), Result.error(error));
327342
this.pendingRequests.put(response.id(), false);
328343
return;
329344
}
330345
Map<String, Object> rawContent = rawContents.get(0);
331-
this.pendingResults.put(response.id(), rawContent.get("text"));
346+
this.pendingResults.put(response.id(), Result.success(rawContent.get("text")));
332347
this.pendingRequests.put(response.id(), false);
333348
}
334349

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
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.fel.tool.mcp.client.support;
8+
9+
/**
10+
* 表示调用 MCP 的结果。
11+
*
12+
* @author 季聿阶
13+
* @since 2025-08-04
14+
*/
15+
public class Result {
16+
private final boolean success;
17+
private final Object content;
18+
private final String error;
19+
20+
private Result(boolean success, Object content, String error) {
21+
this.success = success;
22+
this.content = content;
23+
this.error = error;
24+
}
25+
26+
/**
27+
* 创建一个成功的结果。
28+
*
29+
* @param content 表示成功结果的内容的 {@link Object}。
30+
* @return 表示成功结果的对象的 {@link Result}。
31+
*/
32+
public static Result success(Object content) {
33+
return new Result(true, content, null);
34+
}
35+
36+
/**
37+
* 创建一个失败的结果。
38+
*
39+
* @param error 表示错误结果的信息的 {@link String}。
40+
* @return 表示错误结果的对象的 {@link Result}。
41+
*/
42+
public static Result error(String error) {
43+
return new Result(false, null, error);
44+
}
45+
46+
/**
47+
* 获取结果是否成功。
48+
*
49+
* @return 如果结果成功,则返回 {@code true};否则返回 {@code false}。
50+
*/
51+
public boolean isSuccess() {
52+
return this.success;
53+
}
54+
55+
/**
56+
* 获取结果内容。
57+
*
58+
* @return 表示结果内容的 {@link Object}。
59+
*/
60+
public Object getContent() {
61+
return this.content;
62+
}
63+
64+
/**
65+
* 获取结果错误信息。
66+
*
67+
* @return 表示错误信息的 {@link String}。
68+
*/
69+
public String getError() {
70+
return this.error;
71+
}
72+
}

0 commit comments

Comments
 (0)