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 jakarta.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 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 toProtocolTool(AiMcpToolDescriptor descriptor) { Map 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 readInputSchema(JsonNode schemaNode, boolean includeTenantId) { if (schemaNode == null || schemaNode.isMissingNode() || schemaNode.isNull()) { return defaultInputSchema(includeTenantId); } return objectMapper.convertValue(schemaNode, Map.class); } /** * 生成系统默认的工具输入 schema。 */ public Map defaultInputSchema(boolean includeTenantId) { Map schema = new LinkedHashMap<>(); schema.put("type", "object"); Map 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 parts = new ArrayList<>(); JsonNode contentNode = result.path("content"); if (contentNode.isArray()) { for (Iterator 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 fieldSchema(String type, String description) { Map field = new LinkedHashMap<>(); field.put("type", type); field.put("description", description); return field; } }