mirror of
https://gitee.com/mateos/mateclaw.git
synced 2026-09-14 03:33:43 +08:00
refactor(skill-mcp): expose tool display names via ResolvedSkill instead of Map view
This commit is contained in:
parent
18148aa724
commit
9cac7a31d1
@ -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<List<Map<String, Object>>> getRuntimeStatus() {
|
||||
List<ResolvedSkill> 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_<serverId>_<slug>_<hash6>`,
|
||||
* also display the original MCP tool name in parentheses immediately following it.
|
||||
*
|
||||
* @param skill
|
||||
* @return
|
||||
*/
|
||||
private Map<String, Object> toRuntimeStatusView(ResolvedSkill skill) {
|
||||
Map<String, Object> view = objectMapper.convertValue(
|
||||
skill,
|
||||
new TypeReference<Map<String, Object>>() {}
|
||||
);
|
||||
|
||||
if ("mcp".equalsIgnoreCase(skill.getSource())) {
|
||||
List<String> displayTools = skill.getEffectiveAllowedTools().stream()
|
||||
.map(mcpSkillBridge::decorateToolNameForDisplay)
|
||||
.toList();
|
||||
view.put("effectiveAllowedToolsDisplay", displayTools);
|
||||
}
|
||||
|
||||
return view;
|
||||
public R<List<ResolvedSkill>> getRuntimeStatus() {
|
||||
return R.ok(skillRuntimeService.resolveAllSkillsStatus());
|
||||
}
|
||||
|
||||
@Operation(summary = "刷新 active skills 缓存,resync=true 时同步内置技能到 workspace")
|
||||
|
||||
@ -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<McpServerEntity> 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<String> rawNames = readToolRawNames(server);
|
||||
Map<String, String> 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<String> rawNames = readToolRawNames(server);
|
||||
private SkillManifest buildManifestFrom(McpServerEntity server, List<String> rawNames) {
|
||||
List<String> toolNames = new ArrayList<>(rawNames.size());
|
||||
for (String raw : rawNames) {
|
||||
toolNames.add(McpToolNameResolver.prefixedName(server.getId(), raw));
|
||||
|
||||
@ -130,6 +130,25 @@ public class ResolvedSkill {
|
||||
@Builder.Default
|
||||
private Set<String> 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)"}).
|
||||
*
|
||||
* <p>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.
|
||||
*
|
||||
* <p>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<String, String> 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<String> getEffectiveAllowedToolsDisplay() {
|
||||
Set<String> base = getEffectiveAllowedTools();
|
||||
if (base.isEmpty() || toolDisplayNames == null || toolDisplayNames.isEmpty()) {
|
||||
return base;
|
||||
}
|
||||
Set<String> out = new LinkedHashSet<>(base.size());
|
||||
for (String name : base) {
|
||||
out.add(toolDisplayNames.getOrDefault(name, name));
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
// ==================== 综合状态 ====================
|
||||
|
||||
/**
|
||||
|
||||
Loading…
Reference in New Issue
Block a user