package com.vincent.rsf.server.ai.service.mcp;
|
|
import com.fasterxml.jackson.databind.JsonNode;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.vincent.rsf.server.ai.constant.AiMcpConstants;
|
import com.vincent.rsf.server.ai.constant.AiSceneCode;
|
import com.vincent.rsf.server.ai.model.AiDiagnosticToolResult;
|
import com.vincent.rsf.server.ai.model.AiMcpToolDescriptor;
|
import com.vincent.rsf.server.system.entity.AiMcpMount;
|
import org.springframework.stereotype.Component;
|
|
import javax.annotation.Resource;
|
import java.util.ArrayList;
|
import java.util.Iterator;
|
import java.util.LinkedHashMap;
|
import java.util.List;
|
import java.util.Map;
|
|
@Component
|
public class AiMcpPayloadMapper {
|
|
@Resource
|
private ObjectMapper objectMapper;
|
|
/**
|
* 将远程 MCP tools/list 返回的单个工具定义转换成本系统统一的工具描述符。
|
*/
|
public AiMcpToolDescriptor toExternalToolDescriptor(AiMcpMount mount, JsonNode item) {
|
String remoteName = item.path("name").asText("");
|
if (remoteName.trim().isEmpty()) {
|
return null;
|
}
|
return new AiMcpToolDescriptor()
|
.setMountCode(mount.getMountCode())
|
.setMountName(mount.getName())
|
.setToolCode(remoteName)
|
.setMcpToolName(buildMcpToolName(mount.getMountCode(), remoteName))
|
.setToolName(item.path("title").asText(remoteName))
|
.setSceneCode(resolveSceneCode(mount.getUsageScope()))
|
.setDescription(item.path("description").asText(""))
|
.setEnabledFlag(mount.getEnabledFlag())
|
.setPriority(999)
|
.setToolPrompt(item.path("description").asText(""))
|
.setUsageScope(normalizeUsageScope(mount.getUsageScope()))
|
.setTransportType(mount.getTransportType())
|
.setInputSchema(readInputSchema(item.path("inputSchema"), true));
|
}
|
|
/**
|
* 将远程 MCP tools/call 返回的结果转换为系统内部统一的工具结果模型。
|
*/
|
public AiDiagnosticToolResult toExternalToolResult(AiMcpMount mount, String toolName, JsonNode result) {
|
boolean isError = result.path("isError").asBoolean(false);
|
Map<String, Object> rawMeta = new LinkedHashMap<>();
|
if (result.has("structuredContent") && !result.get("structuredContent").isNull()) {
|
rawMeta.put("structuredContent", objectMapper.convertValue(result.get("structuredContent"), Map.class));
|
}
|
if (result.has("content") && !result.get("content").isNull()) {
|
rawMeta.put("content", objectMapper.convertValue(result.get("content"), Object.class));
|
}
|
return new AiDiagnosticToolResult()
|
.setToolCode(toolName)
|
.setMountCode(mount.getMountCode())
|
.setMcpToolName(buildMcpToolName(mount.getMountCode(), toolName))
|
.setToolName(toolName)
|
.setSeverity(isError ? "WARN" : "INFO")
|
.setSummaryText(extractContentText(result))
|
.setRawMeta(rawMeta);
|
}
|
|
/**
|
* 将本地工具描述符转换为 MCP 协议 tools/list 所需的数据结构。
|
*/
|
public Map<String, Object> toProtocolTool(AiMcpToolDescriptor descriptor) {
|
Map<String, Object> item = new LinkedHashMap<>();
|
item.put("name", descriptor.getMcpToolName());
|
item.put("title", descriptor.getToolName());
|
item.put("description", descriptor.getDescription() == null || descriptor.getDescription().trim().isEmpty()
|
? descriptor.getToolPrompt()
|
: descriptor.getDescription());
|
item.put("inputSchema", descriptor.getInputSchema() == null || descriptor.getInputSchema().isEmpty()
|
? defaultInputSchema(false)
|
: descriptor.getInputSchema());
|
return item;
|
}
|
|
/**
|
* 读取远程工具声明中的 inputSchema;缺省时回退到系统默认输入结构。
|
*/
|
public Map<String, Object> readInputSchema(JsonNode schemaNode, boolean includeTenantId) {
|
if (schemaNode == null || schemaNode.isMissingNode() || schemaNode.isNull()) {
|
return defaultInputSchema(includeTenantId);
|
}
|
return objectMapper.convertValue(schemaNode, Map.class);
|
}
|
|
/**
|
* 生成系统默认的工具输入 schema。
|
*/
|
public Map<String, Object> defaultInputSchema(boolean includeTenantId) {
|
Map<String, Object> schema = new LinkedHashMap<>();
|
schema.put("type", "object");
|
Map<String, Object> properties = new LinkedHashMap<>();
|
properties.put("question", fieldSchema("string", "当前诊断问题或调用目的"));
|
properties.put("sceneCode", fieldSchema("string", "诊断场景编码"));
|
if (includeTenantId) {
|
properties.put("tenantId", fieldSchema("integer", "租户ID"));
|
}
|
schema.put("properties", properties);
|
return schema;
|
}
|
|
/**
|
* 从 MCP tools/call 结果中提取可直接给模型看的文本摘要。
|
*/
|
public String extractContentText(JsonNode result) {
|
List<String> parts = new ArrayList<>();
|
JsonNode contentNode = result.path("content");
|
if (contentNode.isArray()) {
|
for (Iterator<JsonNode> it = contentNode.elements(); it.hasNext(); ) {
|
JsonNode item = it.next();
|
if ("text".equals(item.path("type").asText(""))) {
|
parts.add(item.path("text").asText(""));
|
} else if (item.isObject() || item.isArray()) {
|
parts.add(item.toString());
|
} else {
|
parts.add(item.asText(""));
|
}
|
}
|
}
|
if (!parts.isEmpty()) {
|
return String.join("\n", parts).trim();
|
}
|
if (result.has("structuredContent") && !result.get("structuredContent").isNull()) {
|
return result.get("structuredContent").toString();
|
}
|
return result.toString();
|
}
|
|
/**
|
* 构造系统统一的 MCP 工具全名。
|
*/
|
public String buildMcpToolName(String mountCode, String toolCode) {
|
return (mountCode == null ? AiMcpConstants.DEFAULT_LOCAL_MOUNT_CODE : mountCode) + "_" + toolCode;
|
}
|
|
/**
|
* 将用途预设转换成运行时 sceneCode。
|
*/
|
public String resolveSceneCode(String usageScope) {
|
if (AiMcpConstants.USAGE_SCOPE_DIAGNOSE_ONLY.equalsIgnoreCase(usageScope)) {
|
return AiSceneCode.SYSTEM_DIAGNOSE;
|
}
|
return "";
|
}
|
|
/**
|
* 标准化用途范围字段,统一成系统支持的三个枚举值。
|
*/
|
public String normalizeUsageScope(String usageScope) {
|
if (usageScope == null || usageScope.trim().isEmpty()) {
|
return AiMcpConstants.USAGE_SCOPE_DIAGNOSE_ONLY;
|
}
|
String normalized = usageScope.trim().toUpperCase();
|
if (AiMcpConstants.USAGE_SCOPE_CHAT_AND_DIAGNOSE.equals(normalized)
|
|| AiMcpConstants.USAGE_SCOPE_DISABLED.equals(normalized)) {
|
return normalized;
|
}
|
return AiMcpConstants.USAGE_SCOPE_DIAGNOSE_ONLY;
|
}
|
|
/**
|
* 根据 sceneCode、enabledFlag 和 usageScope 推断最终用途范围。
|
*/
|
public String resolveUsageScope(String sceneCode, Integer enabledFlag, String usageScope) {
|
String normalized = normalizeUsageScope(usageScope);
|
if (AiMcpConstants.USAGE_SCOPE_DISABLED.equals(normalized)) {
|
return normalized;
|
}
|
if (enabledFlag != null && !Integer.valueOf(1).equals(enabledFlag)) {
|
return AiMcpConstants.USAGE_SCOPE_DISABLED;
|
}
|
if (AiMcpConstants.USAGE_SCOPE_CHAT_AND_DIAGNOSE.equals(normalized)) {
|
return normalized;
|
}
|
if (sceneCode == null || sceneCode.trim().isEmpty()) {
|
return AiMcpConstants.USAGE_SCOPE_CHAT_AND_DIAGNOSE;
|
}
|
return AiMcpConstants.USAGE_SCOPE_DIAGNOSE_ONLY;
|
}
|
|
/**
|
* 构造单个字段的 schema 定义。
|
*/
|
private Map<String, Object> fieldSchema(String type, String description) {
|
Map<String, Object> field = new LinkedHashMap<>();
|
field.put("type", type);
|
field.put("description", description);
|
return field;
|
}
|
}
|