Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add hera log mcp #1034

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions jcommon/mcp/mcp-hera-log/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
日志相关的mcp,可以通过它进行日志接入,日志查询
28 changes: 28 additions & 0 deletions jcommon/mcp/mcp-hera-log/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>run.mone</groupId>
<artifactId>mcp</artifactId>
<version>1.6.1-jdk21-SNAPSHOT</version>
</parent>

<artifactId>mcp-hera-log</artifactId>

<properties>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>

<dependencies>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.12.0</version>
</dependency>
</dependencies>

</project>
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -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);
}

}
Original file line number Diff line number Diff line change
@@ -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<Map<String, Object>, 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<String, Object> 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);
}
}
}
Original file line number Diff line number Diff line change
@@ -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();
}
}
}
Loading
Loading