diff --git a/mateclaw-server/src/main/java/vip/mate/skill/controller/SkillController.java b/mateclaw-server/src/main/java/vip/mate/skill/controller/SkillController.java index 7f2c74f3..a24a81d7 100644 --- a/mateclaw-server/src/main/java/vip/mate/skill/controller/SkillController.java +++ b/mateclaw-server/src/main/java/vip/mate/skill/controller/SkillController.java @@ -1,8 +1,6 @@ package vip.mate.skill.controller; import com.baomidou.mybatisplus.core.metadata.IPage; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.ObjectMapper; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; @@ -59,7 +57,6 @@ public class SkillController { private final AgentBindingService agentBindingService; private final vip.mate.skill.mcp.McpSkillBridge mcpSkillBridge; private final vip.mate.skill.acp.AcpSkillBridge acpSkillBridge; - private final ObjectMapper objectMapper; @Operation(summary = "获取技能分页列表(RFC-042 §2.1)") @GetMapping @@ -395,34 +392,8 @@ public class SkillController { @Operation(summary = "获取所有技能的运行时解析状态(管理页面使用)") @GetMapping("/runtime/status") - public R>> getRuntimeStatus() { - List skills = skillRuntimeService.resolveAllSkillsStatus(); - return R.ok(skills.stream().map(this::toRuntimeStatusView).toList()); - } - - /** - * Perform a display transformation on `effectiveAllowedTools` (returned to the frontend), - * and store the transformed result in `effectiveAllowedToolsDisplay`. - * In addition to displaying the tool name in the format `mcp___`, - * also display the original MCP tool name in parentheses immediately following it. - * - * @param skill - * @return - */ - private Map toRuntimeStatusView(ResolvedSkill skill) { - Map view = objectMapper.convertValue( - skill, - new TypeReference>() {} - ); - - if ("mcp".equalsIgnoreCase(skill.getSource())) { - List displayTools = skill.getEffectiveAllowedTools().stream() - .map(mcpSkillBridge::decorateToolNameForDisplay) - .toList(); - view.put("effectiveAllowedToolsDisplay", displayTools); - } - - return view; + public R> getRuntimeStatus() { + return R.ok(skillRuntimeService.resolveAllSkillsStatus()); } @Operation(summary = "刷新 active skills 缓存,resync=true 时同步内置技能到 workspace") diff --git a/mateclaw-server/src/main/java/vip/mate/skill/mcp/McpSkillBridge.java b/mateclaw-server/src/main/java/vip/mate/skill/mcp/McpSkillBridge.java index cfaf42a8..17766a9d 100644 --- a/mateclaw-server/src/main/java/vip/mate/skill/mcp/McpSkillBridge.java +++ b/mateclaw-server/src/main/java/vip/mate/skill/mcp/McpSkillBridge.java @@ -121,26 +121,6 @@ public class McpSkillBridge { } } - /** - * Add a "display-only" method to reverse-lookup a prefixed name back to its raw name. - * - * @param prefixedName - * @return - */ - public String decorateToolNameForDisplay(String prefixedName) { - McpToolNameResolver.ParsedRef ref = McpToolNameResolver.parse(prefixedName); - if (ref == null) return prefixedName; - - McpServerEntity server = mcpServerService.getById(ref.serverId()); - for (String raw : readToolRawNames(server)) { - String rebuilt = McpToolNameResolver.prefixedName(ref.serverId(), raw); - if (rebuilt.equals(prefixedName)) { - return prefixedName + " (" + raw + ")"; - } - } - return prefixedName; - } - private List listEnabledServers() { try { return mcpServerService.listEnabled(); @@ -166,12 +146,18 @@ public class McpSkillBridge { s.setTags("mcp"); s.setSecurityScanStatus("PASSED"); // MCP servers don't go through SkillSecurityService s.setConfigJson(buildConfigJson(server)); - s.setManifestJson(serializeManifest(buildManifest(server))); + s.setManifestJson(serializeManifest(buildManifestFrom(server, readToolRawNames(server)))); return s; } private ResolvedSkill serverToResolved(McpServerEntity server) { - SkillManifest manifest = buildManifest(server); + List rawNames = readToolRawNames(server); + Map toolDisplayNames = new LinkedHashMap<>(); + for (String raw : rawNames) { + String prefixed = McpToolNameResolver.prefixedName(server.getId(), raw); + toolDisplayNames.put(prefixed, prefixed + " (" + raw + ")"); + } + SkillManifest manifest = buildManifestFrom(server, rawNames); boolean connected = "connected".equalsIgnoreCase(nullSafe(server.getLastStatus())); boolean errored = "error".equalsIgnoreCase(nullSafe(server.getLastStatus())) || (server.getLastError() != null && !server.getLastError().isBlank()); @@ -212,6 +198,7 @@ public class McpSkillBridge { .manifest(manifest) .featureStatuses(featureStatuses) .activeFeatures(active) + .toolDisplayNames(toolDisplayNames) .build(); } @@ -234,8 +221,7 @@ public class McpSkillBridge { * don't appear in any agent's callbacks at chat time, and the LLM * would see no MCP tools even though the bindings were saved. */ - private SkillManifest buildManifest(McpServerEntity server) { - List rawNames = readToolRawNames(server); + private SkillManifest buildManifestFrom(McpServerEntity server, List rawNames) { List toolNames = new ArrayList<>(rawNames.size()); for (String raw : rawNames) { toolNames.add(McpToolNameResolver.prefixedName(server.getId(), raw)); diff --git a/mateclaw-server/src/main/java/vip/mate/skill/runtime/model/ResolvedSkill.java b/mateclaw-server/src/main/java/vip/mate/skill/runtime/model/ResolvedSkill.java index 84f9bbaf..262dd203 100644 --- a/mateclaw-server/src/main/java/vip/mate/skill/runtime/model/ResolvedSkill.java +++ b/mateclaw-server/src/main/java/vip/mate/skill/runtime/model/ResolvedSkill.java @@ -130,6 +130,25 @@ public class ResolvedSkill { @Builder.Default private Set activeFeatures = Set.of(); + /** + * Per-tool display-name decoration table, keyed by the prefixed callback + * name and valued by the human-readable form (e.g. + * {@code "mcp_4_fs_a1b2c3"} → {@code "mcp_4_fs_a1b2c3 (read_file)"}). + * + *

Populated by skill source providers that have a recoverable raw + * name (currently MCP-bridged skills); other sources leave it empty, + * in which case {@link #getEffectiveAllowedToolsDisplay()} falls + * through to the prefixed names unchanged. + * + *

Held internally rather than serialized: the wire shape exposes + * the decorated set via the derived getter, which keeps the + * source-of-truth (the feature filter in + * {@link #getEffectiveAllowedTools()}) in one place. + */ + @JsonIgnore + @Builder.Default + private Map toolDisplayNames = Map.of(); + /** RFC-090 §14.1 — replacement filter for {@code dependencyReady}. */ public boolean hasAnyActiveFeature() { return activeFeatures != null && !activeFeatures.isEmpty(); @@ -200,6 +219,26 @@ public class ResolvedSkill { return out; } + /** + * Display-friendly companion to {@link #getEffectiveAllowedTools()}. + * Each prefixed callback name is replaced by its decorated form (e.g. + * {@code "mcp_4_fs_a1b2c3 (read_file)"}) when {@link #toolDisplayNames} + * carries an entry for it; names without a decoration entry are kept + * verbatim. Feature-filter semantics match the prefixed getter, so a + * tool that is hidden by a SETUP_NEEDED feature stays hidden here too. + */ + public Set getEffectiveAllowedToolsDisplay() { + Set base = getEffectiveAllowedTools(); + if (base.isEmpty() || toolDisplayNames == null || toolDisplayNames.isEmpty()) { + return base; + } + Set out = new LinkedHashSet<>(base.size()); + for (String name : base) { + out.add(toolDisplayNames.getOrDefault(name, name)); + } + return out; + } + // ==================== 综合状态 ==================== /**