mirror of
https://github.com/slhaf/Partner.git
synced 2026-05-12 16:53:04 +08:00
feat(LocalRunnerClient): support load *.desc.json when DescMcpServer launched
This commit is contained in:
@@ -17,6 +17,7 @@ import io.modelcontextprotocol.server.McpStatelessAsyncServer;
|
|||||||
import io.modelcontextprotocol.server.McpStatelessServerFeatures;
|
import io.modelcontextprotocol.server.McpStatelessServerFeatures;
|
||||||
import io.modelcontextprotocol.spec.McpClientTransport;
|
import io.modelcontextprotocol.spec.McpClientTransport;
|
||||||
import io.modelcontextprotocol.spec.McpSchema;
|
import io.modelcontextprotocol.spec.McpSchema;
|
||||||
|
import javassist.NotFoundException;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
@@ -44,6 +45,8 @@ import java.util.concurrent.ExecutorService;
|
|||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
import java.util.function.BiFunction;
|
import java.util.function.BiFunction;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
@@ -798,16 +801,73 @@ public class LocalRunnerClient extends RunnerClient {
|
|||||||
private static final class Desc extends LocalWatchEventProcessor {
|
private static final class Desc extends LocalWatchEventProcessor {
|
||||||
|
|
||||||
private final McpStatelessAsyncServer mcpDescServer;
|
private final McpStatelessAsyncServer mcpDescServer;
|
||||||
|
private final HashMap<String, String> descCache = new HashMap<>();
|
||||||
|
|
||||||
private Desc(ConcurrentHashMap<String, MetaActionInfo> existedMetaActions, McpStatelessAsyncServer mcpDescServer, WatchContext ctx) {
|
private Desc(ConcurrentHashMap<String, MetaActionInfo> existedMetaActions, McpStatelessAsyncServer mcpDescServer, WatchContext ctx) {
|
||||||
super(existedMetaActions, ctx);
|
super(existedMetaActions, ctx);
|
||||||
this.mcpDescServer = mcpDescServer;
|
this.mcpDescServer = mcpDescServer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private McpStatelessServerFeatures.@NotNull AsyncResourceSpecification buildAsyncResourceSpecification(String name, String uri) {
|
||||||
|
McpSchema.Resource resource = McpSchema.Resource.builder()
|
||||||
|
.name(name)
|
||||||
|
.title(name)
|
||||||
|
.description("Action descriptor for " + name)
|
||||||
|
.mimeType("application/json")
|
||||||
|
.uri(uri)
|
||||||
|
.build();
|
||||||
|
BiFunction<McpTransportContext, McpSchema.ReadResourceRequest, Mono<McpSchema.ReadResourceResult>> readHandler = (context, request) -> {
|
||||||
|
String requestUri = request.uri();
|
||||||
|
String result = descCache.get(requestUri);
|
||||||
|
if (result == null) {
|
||||||
|
return Mono.error(new NotFoundException("未找到 Resource: " + requestUri));
|
||||||
|
}
|
||||||
|
return Mono.just(new McpSchema.ReadResourceResult(List.of(new McpSchema.TextResourceContents(requestUri, "application/json", result))));
|
||||||
|
};
|
||||||
|
return new McpStatelessServerFeatures.AsyncResourceSpecification(resource, readHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("UnusedReturnValue")
|
||||||
|
private boolean addResource(File file) {
|
||||||
|
String name = file.getName();
|
||||||
|
String pattern = "[a-z][A-Z]+::[a-z][A-Z]+.desk.json";
|
||||||
|
Pattern p = Pattern.compile(pattern);
|
||||||
|
Matcher matcher = p.matcher(name);
|
||||||
|
if (!matcher.find()) {
|
||||||
|
log.error("文件名称不符合要求: {}", name);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// 读取并解析为 MetaActionInfo,存入 resources
|
||||||
|
try {
|
||||||
|
MetaActionInfo info = JSONUtil.readJSONObject(file, StandardCharsets.UTF_8).toBean(MetaActionInfo.class);
|
||||||
|
String uri = ctx.root.resolve(name).toUri().toString();
|
||||||
|
descCache.put(uri, JSONObject.toJSONString(info));
|
||||||
|
mcpDescServer.addResource(buildAsyncResourceSpecification(name, uri)).subscribe();
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("desc.json 解析失败: {}", file.getAbsolutePath());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@NotNull
|
@NotNull
|
||||||
protected LocalWatchServiceBuild.InitLoader buildLoad() {
|
protected LocalWatchServiceBuild.InitLoader buildLoad() {
|
||||||
return null;
|
return () -> {
|
||||||
|
// DescMcp 的加载逻辑只负责读取已有的 *.desc.json 并注册为 resources
|
||||||
|
// 正常来讲 root 直接对应 MCP_DESC_PATH,先检查 root 是否为目录,否则拒绝启动
|
||||||
|
Path root = ctx.root;
|
||||||
|
if (!Files.isDirectory(root)) {
|
||||||
|
throw new ActionInitFailedException("未找到目录: " + root);
|
||||||
|
}
|
||||||
|
File[] files = root.toFile().listFiles();
|
||||||
|
if (files == null) {
|
||||||
|
throw new ActionInitFailedException("目录无法正常读取: " + root);
|
||||||
|
}
|
||||||
|
for (File file : files) {
|
||||||
|
addResource(file);
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
Reference in New Issue
Block a user