diff --git a/jcommon/mcp/mcp-hera-log/README.md b/jcommon/mcp/mcp-hera-log/README.md new file mode 100644 index 000000000..1fe449187 --- /dev/null +++ b/jcommon/mcp/mcp-hera-log/README.md @@ -0,0 +1 @@ +日志相关的mcp,可以通过它进行日志接入,日志查询 diff --git a/jcommon/mcp/mcp-hera-log/pom.xml b/jcommon/mcp/mcp-hera-log/pom.xml new file mode 100644 index 000000000..f6be2dd44 --- /dev/null +++ b/jcommon/mcp/mcp-hera-log/pom.xml @@ -0,0 +1,28 @@ + + + 4.0.0 + + run.mone + mcp + 1.6.1-jdk21-SNAPSHOT + + + mcp-hera-log + + + 21 + 21 + UTF-8 + + + + + com.squareup.okhttp3 + okhttp + 4.12.0 + + + + \ No newline at end of file diff --git a/jcommon/mcp/mcp-hera-log/src/main/java/run/mone/mcp/log/LogMcpBootstrap.java b/jcommon/mcp/mcp-hera-log/src/main/java/run/mone/mcp/log/LogMcpBootstrap.java new file mode 100644 index 000000000..88b487215 --- /dev/null +++ b/jcommon/mcp/mcp-hera-log/src/main/java/run/mone/mcp/log/LogMcpBootstrap.java @@ -0,0 +1,17 @@ +package run.mone.mcp.log; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + * @author wtt + * @version 1.0 + * @description + * @date 2025/2/21 9:57 + */ +@SpringBootApplication +public class LogMcpBootstrap { + public static void main(String[] args) { + SpringApplication.run(LogMcpBootstrap.class, args); + } +} diff --git a/jcommon/mcp/mcp-hera-log/src/main/java/run/mone/mcp/log/config/McpStdioTransportConfig.java b/jcommon/mcp/mcp-hera-log/src/main/java/run/mone/mcp/log/config/McpStdioTransportConfig.java new file mode 100644 index 000000000..9cd4892a2 --- /dev/null +++ b/jcommon/mcp/mcp-hera-log/src/main/java/run/mone/mcp/log/config/McpStdioTransportConfig.java @@ -0,0 +1,29 @@ +package run.mone.mcp.log.config; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import run.mone.hive.mcp.server.transport.StdioServerTransport; + +/** + * @author wtt + * @version 1.0 + * @description + * @date 2025/2/21 9:59 + */ +@Configuration +@ConditionalOnProperty(name = "stdio.enabled", havingValue = "true") +public class McpStdioTransportConfig { + /** + * stdio 通信 + * + * @param mapper + * @return + */ + @Bean + StdioServerTransport stdioServerTransport(ObjectMapper mapper) { + return new StdioServerTransport(mapper); + } + +} diff --git a/jcommon/mcp/mcp-hera-log/src/main/java/run/mone/mcp/log/function/HeraLogFunction.java b/jcommon/mcp/mcp-hera-log/src/main/java/run/mone/mcp/log/function/HeraLogFunction.java new file mode 100644 index 000000000..b4c0dd9a4 --- /dev/null +++ b/jcommon/mcp/mcp-hera-log/src/main/java/run/mone/mcp/log/function/HeraLogFunction.java @@ -0,0 +1,205 @@ +package run.mone.mcp.log.function; + +import com.google.gson.Gson; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import okhttp3.*; +import org.apache.commons.lang3.StringUtils; +import run.mone.hive.mcp.spec.McpSchema; + +import java.io.IOException; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import java.util.function.Function; + +/** + * @author wtt + * @version 1.0 + * @description + * @date 2025/2/21 10:01 + */ +@Slf4j +@Getter +public class HeraLogFunction implements Function, McpSchema.CallToolResult> { + + private static String heraLogUrl; + private static OkHttpClient okHttpClient; + + private static final Gson gson = new Gson(); + + private String name = "hera_log_executor"; + private String desc = "Execute hera-log operations (query_project, access_log...)"; + + private String githubToolSchema = """ + { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": ["query_project", "access_log"], + "description": "Type of Hera-log operation to execute" + }, + "app_id": { + "type": "Long", + "description": "miline appId" + }, + "env_id": { + "type": "Long", + "description": "miline envId" + }, + "user_name": { + "type": "String", + "description": "user name" + }, + "space_id": { + "type": "Long", + "description": "hera space tree id" + }, + "store_id": { + "type": "Long", + "description": "hera store tree id" + }, + }, + "required": ["type"] + } + """; + + public HeraLogFunction() { + heraLogUrl = System.getenv().getOrDefault("Hera_log_url", ""); + if (StringUtils.isBlank(heraLogUrl)) { + throw new IllegalStateException("Hera_log_url environment variable is required"); + } + okHttpClient = new OkHttpClient().newBuilder() + .connectTimeout(60, TimeUnit.SECONDS) + .readTimeout(5 * 60, TimeUnit.SECONDS) + .writeTimeout(5 * 60, TimeUnit.SECONDS) + .connectionPool(new ConnectionPool(100, 10, TimeUnit.MINUTES)) + .build(); + } + + @Override + public McpSchema.CallToolResult apply(Map args) { + String type = (String) args.get("type"); + try { + return switch (type.toLowerCase()) { + case "query_project" -> queryAccess( + (String) args.get("app_id"), + (String) args.get("env_id")); + case "access_log" -> accessLog( + (String) args.get("app_id"), + (String) args.get("env_id"), + (String) args.get("user_name"), + (String) args.get("space_id"), + (String) args.get("store_id")); + default -> throw new IllegalArgumentException("Unsupported operation type: " + type); + }; + } catch (Exception e) { + throw new RuntimeException("Hera-log operation failed: " + e.getMessage(), e); + } + } + + private McpSchema.CallToolResult accessLog(String appId, String envId, String userName, String spaceId, String storeId) { + if (StringUtils.isBlank(appId) || StringUtils.isBlank(envId)) { + throw new IllegalStateException("参数不能为空"); + } + JsonObject requestBody = new JsonObject(); + requestBody.addProperty("appId", appId); + requestBody.addProperty("envId", envId); + requestBody.addProperty("userName", userName); + requestBody.addProperty("spaceId", spaceId); + requestBody.addProperty("storeId", storeId); + + RequestBody body = RequestBody.create( + MediaType.parse("application/json; charset=utf-8"), + requestBody.toString() + ); + String url = heraLogUrl + "/access/log/auto"; + Request request = new Request.Builder() + .url(url) + .post(body) + .build(); + try (Response response = okHttpClient.newCall(request).execute()) { + if (!response.isSuccessful()) { + throw new IOException("Unexpected code " + response); + } + + String responseBody = response.body().string(); + JsonObject jsonObject = JsonParser.parseString(responseBody).getAsJsonObject(); + if (jsonObject.has("data") && jsonObject.get("data").getAsJsonObject() != null) { + String msg = jsonObject.get("data").getAsString(); + if (StringUtils.equals("success", msg)) { + return new McpSchema.CallToolResult( + List.of(new McpSchema.TextContent("接入成功")), + false); + } + return new McpSchema.CallToolResult( + List.of(new McpSchema.TextContent("接入失败")), + false); + + } + return new McpSchema.CallToolResult( + List.of(new McpSchema.TextContent(responseBody)), + false); + } catch (IOException e) { + log.error("Failed to execute Hera-log operation", e); + return new McpSchema.CallToolResult( + List.of(new McpSchema.TextContent("Error: " + e.getMessage())), + true); + } + } + + + public McpSchema.CallToolResult queryAccess(String appId, + String envId) { + if (StringUtils.isBlank(appId) || StringUtils.isBlank(envId)) { + throw new IllegalStateException("参数不能为空"); + } + JsonObject requestBody = new JsonObject(); + requestBody.addProperty("appId", appId); + requestBody.addProperty("envId", envId); + + RequestBody body = RequestBody.create( + MediaType.parse("application/json; charset=utf-8"), + requestBody.toString() + ); + String url = heraLogUrl + "/access/log/enable"; + Request request = new Request.Builder() + .url(url) + .post(body) + .build(); + // 发送请求并处理响应 + try (Response response = okHttpClient.newCall(request).execute()) { + if (!response.isSuccessful()) { + throw new IOException("Unexpected code " + response); + } + + // 获取响应体 + String responseBody = response.body().string(); + JsonObject jsonObject = JsonParser.parseString(responseBody).getAsJsonObject(); + if (jsonObject.has("data") && jsonObject.get("data").getAsJsonObject() != null) { + boolean asBoolean = jsonObject.get("data").getAsJsonObject().get("hasLoggingEnabled").getAsBoolean(); + if (asBoolean) { + String logUrl = jsonObject.get("data").getAsJsonObject().get("logUrl").getAsString(); + return new McpSchema.CallToolResult( + List.of(new McpSchema.TextContent(logUrl)), + false); + } + return new McpSchema.CallToolResult( + List.of(new McpSchema.TextContent("未接入日志")), + false); + + } + return new McpSchema.CallToolResult( + List.of(new McpSchema.TextContent(responseBody)), + false); + } catch (IOException e) { + log.error("Failed to execute Hera-log operation", e); + return new McpSchema.CallToolResult( + List.of(new McpSchema.TextContent("Error: " + e.getMessage())), + true); + } + } +} diff --git a/jcommon/mcp/mcp-hera-log/src/main/java/run/mone/mcp/log/serever/LogMcpServer.java b/jcommon/mcp/mcp-hera-log/src/main/java/run/mone/mcp/log/serever/LogMcpServer.java new file mode 100644 index 000000000..b37b1a842 --- /dev/null +++ b/jcommon/mcp/mcp-hera-log/src/main/java/run/mone/mcp/log/serever/LogMcpServer.java @@ -0,0 +1,72 @@ +package run.mone.mcp.log.serever; + +import jakarta.annotation.PostConstruct; +import jakarta.annotation.PreDestroy; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; +import run.mone.hive.mcp.server.McpServer; +import run.mone.hive.mcp.server.McpSyncServer; +import run.mone.hive.mcp.spec.McpSchema; +import run.mone.hive.mcp.spec.ServerMcpTransport; +import run.mone.mcp.log.function.HeraLogFunction; + +/** + * @author wtt + * @version 1.0 + * @description + * @date 2025/2/21 9:59 + */ +@Slf4j +@Component +public class LogMcpServer { + + // TODO:后续支持github + + private ServerMcpTransport transport; + + private McpSyncServer syncServer; + + public LogMcpServer(ServerMcpTransport transport) { + this.transport = transport; + log.info("GitMcpServer initialized with transport: {}", transport); + } + + public McpSyncServer start() { + log.info("Starting GitMcpServer..."); + McpSyncServer syncServer = McpServer.using(transport) + .serverInfo("git_mcp", "1.0.0") + .capabilities(McpSchema.ServerCapabilities.builder() + .tools(true) + .logging() + .build()) + .sync(); + + try { + HeraLogFunction heraLogFunction = new HeraLogFunction(); + var logToolRegistration = new McpServer.ToolRegistration( + new McpSchema.Tool(heraLogFunction.getName(), heraLogFunction.getDesc(), heraLogFunction.getGithubToolSchema()), heraLogFunction + ); + syncServer.addTool(logToolRegistration); + + log.info("Successfully registered git tool"); + } catch (Exception e) { + log.error("Failed to register git tool", e); + throw e; + } + + return syncServer; + } + + @PostConstruct + public void init() { + this.syncServer = start(); + } + + @PreDestroy + public void stop() { + if (this.syncServer != null) { + log.info("Stopping gitMcpServer..."); + this.syncServer.closeGracefully(); + } + } +} diff --git a/jcommon/mcp/mcp-hera-log/src/test/java/run/mone/mcp/log/function/HeraLogFunctionTest.java b/jcommon/mcp/mcp-hera-log/src/test/java/run/mone/mcp/log/function/HeraLogFunctionTest.java new file mode 100644 index 000000000..f1003ff73 --- /dev/null +++ b/jcommon/mcp/mcp-hera-log/src/test/java/run/mone/mcp/log/function/HeraLogFunctionTest.java @@ -0,0 +1,41 @@ +package run.mone.mcp.log.function; + +import org.junit.Assert; +import org.junit.jupiter.api.Test; +import run.mone.hive.mcp.spec.McpSchema; + +import java.util.Map; + +/** + * @author wtt + * @version 1.0 + * @description + * @date 2025/2/21 14:08 + */ +public class HeraLogFunctionTest { + + @Test + public void testQueryUserProject() { + HeraLogFunction heraLogFunction = new HeraLogFunction(); + McpSchema.CallToolResult apply = heraLogFunction.apply(Map.of( + "type", "query_project", + "app_id", "301410", + "env_id", "932279" + )); + Assert.assertNotNull(apply); + } + + @Test + public void testAccess() { + HeraLogFunction heraLogFunction = new HeraLogFunction(); + McpSchema.CallToolResult apply = heraLogFunction.apply(Map.of( + "type", "access_log", + "app_id", "91350", + "env_id", "930219", + "user_name", "wangtao29", + "space_id", "35", + "store_id", "121444" + )); + Assert.assertNotNull(apply); + } +} \ No newline at end of file