Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -84,22 +84,70 @@ protected String doCall(Map<String, Object> args, @Nullable ToolContext toolCont
result = delegate.call(argsJson);
}

if (result.startsWith("[")) {
JSONArray array = JSONArray.parseArray(result);
if (!array.isEmpty()) {
JSONObject data = array.getJSONObject(0);
if (data.containsKey("text") && data.getString("text") != null && !data.getString("text").isEmpty()) {
result = data.getString("text");
}
}
}
result = extractTextFromMcpResult(result);

logger.debug("McpToolCallbackAdapter#doCall - reason=原始ToolCallback调用完成, rawToolName={}, resultLength={}",
rawToolName, result != null ? result.length() : 0);

return result;
}

/**
* 从 MCP 返回结果中提取文本内容。
*
* <p>MCP 工具返回结果可能是以下格式之一:
* <ul>
* <li>JSON 数组,如 {@code [{"text":"..."}]}</li>
* <li>JSON 对象,如 {@code {"text":"..."}}</li>
* <li>普通文本字符串</li>
* </ul>
* 本方法会尝试按 JSON 解析,解析失败则直接返回原始文本。
*
* @param result MCP 工具返回的原始字符串
* @return 提取后的文本内容
*/
private String extractTextFromMcpResult(String result) {
if (result == null || result.isEmpty()) {
return result;
}

String trimmed = result.trim();

// 尝试按 JSON 数组解析
if (trimmed.startsWith("[")) {
try {
JSONArray array = JSONArray.parseArray(trimmed);
if (!array.isEmpty()) {
JSONObject data = array.getJSONObject(0);
if (data.containsKey("text") && data.getString("text") != null
&& !data.getString("text").isEmpty()) {
return data.getString("text");
Comment on lines +121 to +124
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里假设 JSONArray 的第 0 个元素一定是 JSONObject:当返回为如 ["ok"][1] 或混合数组时,array.getJSONObject(0) 会返回 null,随后 data.containsKey(...) 会触发 NullPointerException,导致 doCall 失败。建议先判断元素类型/非空(例如先取 Object first = array.get(0) 并仅在 first instanceof JSONObject 时读取 text)。

Suggested change
JSONObject data = array.getJSONObject(0);
if (data.containsKey("text") && data.getString("text") != null
&& !data.getString("text").isEmpty()) {
return data.getString("text");
Object first = array.get(0);
if (first instanceof JSONObject) {
JSONObject data = (JSONObject) first;
if (data.containsKey("text") && data.getString("text") != null
&& !data.getString("text").isEmpty()) {
return data.getString("text");
}

Copilot uses AI. Check for mistakes.
}
}
}
catch (Exception e) {
logger.debug("McpToolCallbackAdapter#extractTextFromMcpResult - reason=返回结果以'['开头但非合法JSON数组,按普通文本处理, rawToolName={}", rawToolName);
}
Comment on lines +128 to +130
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

两个 catch 分支都捕获了解析异常但日志里没有打印异常信息(未把 e 作为参数传给 logger),联调/线上排查会缺少关键上下文。建议在 debug 日志中包含异常(例如作为最后一个参数传入),或至少记录 e.getMessage()

Copilot uses AI. Check for mistakes.
}

// 尝试按 JSON 对象解析
if (trimmed.startsWith("{")) {
try {
JSONObject data = JSONObject.parseObject(trimmed);
if (data.containsKey("text") && data.getString("text") != null
&& !data.getString("text").isEmpty()) {
return data.getString("text");
}
}
catch (Exception e) {
logger.debug("McpToolCallbackAdapter#extractTextFromMcpResult - reason=返回结果以'{{'开头但非合法JSON对象,按普通文本处理, rawToolName={}", rawToolName);
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

日志文案里写的是“以'{{'开头”,但这里实际判断的是 trimmed.startsWith("{")。建议把日志中的 {{ 更正为 {,避免误导排查。

Suggested change
logger.debug("McpToolCallbackAdapter#extractTextFromMcpResult - reason=返回结果以'{{'开头但非合法JSON对象,按普通文本处理, rawToolName={}", rawToolName);
logger.debug("McpToolCallbackAdapter#extractTextFromMcpResult - reason=返回结果以'{'开头但非合法JSON对象,按普通文本处理, rawToolName={}", rawToolName);

Copilot uses AI. Check for mistakes.
}
}

// 非 JSON 格式,直接返回原始文本
return result;
}

/**
* 获取原始工具名。
*
Expand Down