diff --git a/jcommon/mcp/mcp-hologres/README.md b/jcommon/mcp/mcp-hologres/README.md new file mode 100644 index 000000000..9165ee693 --- /dev/null +++ b/jcommon/mcp/mcp-hologres/README.md @@ -0,0 +1,26 @@ +使用Druid链接池连接Hologres数据库, 查询数据后给大模型可进行数据分析 +```json +{ + "mcpServers": { + "hologres_mcp":{ + "command": "D:\\software\\jdk21\\bin\\java.exe", + "args": [ + "-jar", + "-Dhologres.url=hgprecn-cn-5yd3l8m6l002-cn-beijing-vpc-st.hologres.aliyuncs.com:80/proretail_car_pre?currentSchema=proretail_car_pre_view&autoReconnect=true&rewriteBatchedStatements=true&characterEncoding=utf8", + "-Dhologres.userName=", + "-Dhologres.password=", + "D:\\workspace\\xiaomiMone\\jcommon\\mcp\\mcp-hologres\\target\\app.jar" + ] + } + } +} +``` + +```properties +帮我查看一下2024年3月2号到3月4号那些省创建了门店 + +查询结果已完成。以下是2024年3月2日至4日创建门店的省份: +1. 福建省 +2. 山西省 +3. 河北省 +``` \ No newline at end of file diff --git a/jcommon/mcp/mcp-hologres/pom.xml b/jcommon/mcp/mcp-hologres/pom.xml new file mode 100644 index 000000000..e1672d8c7 --- /dev/null +++ b/jcommon/mcp/mcp-hologres/pom.xml @@ -0,0 +1,86 @@ + + + 4.0.0 + + run.mone + mcp + 1.6.1-jdk21-SNAPSHOT + + + run.mone.mcp + mcp-hologres + + + 17 + 17 + UTF-8 + 1.5.12 + + + + + druid + com.alibaba + 1.2.4 + + + org.postgresql + postgresql + 42.2.25 + + + org.xerial + sqlite-jdbc + 3.42.0.0 + + + com.blinkfox + zealot + 1.3.1 + + + + + + + + + + maven-compiler-plugin + 3.11.0 + + 17 + 17 + true + UTF-8 + + ${project.basedir}/src/main/java + + + + + + org.springframework.boot + spring-boot-maven-plugin + 2.7.14 + + run.mone.mcp.hologres.HoloBootstrap + app + + + + + repackage + + + + + + + + + + + \ No newline at end of file diff --git a/jcommon/mcp/mcp-hologres/src/main/java/run/mone/mcp/hologres/HoloBootstrap.java b/jcommon/mcp/mcp-hologres/src/main/java/run/mone/mcp/hologres/HoloBootstrap.java new file mode 100644 index 000000000..ba55858b7 --- /dev/null +++ b/jcommon/mcp/mcp-hologres/src/main/java/run/mone/mcp/hologres/HoloBootstrap.java @@ -0,0 +1,11 @@ +package run.mone.mcp.hologres; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication(scanBasePackages = {"run.mone.mcp.hologres"}) +public class HoloBootstrap { + public static void main(String[] args) { + SpringApplication.run(HoloBootstrap.class, args); + } +} diff --git a/jcommon/mcp/mcp-hologres/src/main/java/run/mone/mcp/hologres/config/McpStdioTransportConfig.java b/jcommon/mcp/mcp-hologres/src/main/java/run/mone/mcp/hologres/config/McpStdioTransportConfig.java new file mode 100644 index 000000000..e788c6111 --- /dev/null +++ b/jcommon/mcp/mcp-hologres/src/main/java/run/mone/mcp/hologres/config/McpStdioTransportConfig.java @@ -0,0 +1,22 @@ +package run.mone.mcp.hologres.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; + +@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-hologres/src/main/java/run/mone/mcp/hologres/function/HoloFunction.java b/jcommon/mcp/mcp-hologres/src/main/java/run/mone/mcp/hologres/function/HoloFunction.java new file mode 100644 index 000000000..766ab3400 --- /dev/null +++ b/jcommon/mcp/mcp-hologres/src/main/java/run/mone/mcp/hologres/function/HoloFunction.java @@ -0,0 +1,123 @@ +package run.mone.mcp.hologres.function; + +import com.blinkfox.zealot.bean.SqlInfo; +import lombok.Data; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; +import run.mone.hive.mcp.spec.McpSchema; + +import javax.annotation.Resource; +import javax.sql.DataSource; +import java.sql.*; +import java.util.List; +import java.util.Map; +import java.util.function.Function; + +@Data +@Slf4j +public class HoloFunction implements Function, McpSchema.CallToolResult> { + + private String name = "hologres_executor"; + + private String desc = "Execute Hologres query operations"; + + private String sqlToolSchema = """ + { + "type": "object", + "properties": { + "tableName": { + "type": "string", + "enum": ["dim_org"], + "description": "the name of execute table" + }, + "startTime": { + "type": "string", + "description": "the start time of where condition" + }, + "endTime": { + "type": "string", + "description": "the start time of where condition" + }, + "count": { + "type": "Integer", + "description": "the number of query row" + } + }, + "required": ["tableName", "startTime", "endTime"] + } + """; + private DataSource dataSource; + + + public HoloFunction(DataSource dataSource) { + this.dataSource = dataSource; + } + + @SneakyThrows + @Override + public McpSchema.CallToolResult apply(Map args) { + String tableName = (String) args.get("tableName"); + String startTime = (String) args.get("startTime"); + Integer count = (Integer) args.get("count"); + String endTime = (String) args.get("endTime"); + if (tableName == null || tableName.trim().isEmpty()) { + log.error("没有指明表明"); + throw new IllegalArgumentException("tableName is required"); + } + log.info("tableName: {}, startTime: {}, endTime: {}", tableName, startTime, endTime); + + // 获取连接 + Connection conn = dataSource.getConnection(); + + try { + SqlInfo sql = SQLGenerator.generateSQLQuery(tableName, startTime, endTime, count); + log.info("sql : {}", sql); + return executeQuery(conn, sql); + } catch (Throwable ex) { + throw new RuntimeException(ex.getMessage()); + } + } + + + private McpSchema.CallToolResult executeQuery(Connection conn, SqlInfo sqlInfo) throws SQLException { + + // 创建 PreparedStatement 执行 SQL 语句 + try (PreparedStatement ps = conn.prepareStatement(sqlInfo.getSql())) { + if (sqlInfo.getParams() != null && sqlInfo.getParams().size() > 0) { + for (int i = 0; i < sqlInfo.getParams().size(); i++) { + ps.setObject(i + 1, sqlInfo.getParams().get(i)); + } + + } + + ResultSet rs = ps.executeQuery(); + + StringBuilder result = new StringBuilder(); + ResultSetMetaData metaData = rs.getMetaData(); + int columnCount = metaData.getColumnCount(); + + // Add column names + for (int i = 1; i <= columnCount; i++) { + result.append(metaData.getColumnName(i)).append("\t"); + } + result.append("\n"); + + // Add data rows + while (rs.next()) { + for (int i = 1; i <= columnCount; i++) { + result.append(rs.getString(i)).append("\t"); + } + result.append("\n"); + } + + log.info("Successfully executed query"); + return new McpSchema.CallToolResult( + List.of(new McpSchema.TextContent(result.toString())), + false + ); + } + + } + +} \ No newline at end of file diff --git a/jcommon/mcp/mcp-hologres/src/main/java/run/mone/mcp/hologres/function/SQLGenerator.java b/jcommon/mcp/mcp-hologres/src/main/java/run/mone/mcp/hologres/function/SQLGenerator.java new file mode 100644 index 000000000..d590686ce --- /dev/null +++ b/jcommon/mcp/mcp-hologres/src/main/java/run/mone/mcp/hologres/function/SQLGenerator.java @@ -0,0 +1,44 @@ +package run.mone.mcp.hologres.function; + +import com.blinkfox.zealot.bean.SqlInfo; +import com.blinkfox.zealot.core.ZealotKhala; +import lombok.Data; +import lombok.extern.slf4j.Slf4j; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; + +@Data +@Slf4j +public class SQLGenerator { + + /** + * 根据表名和时间生成 SQL 查询语句 + * https://github.com/blinkfox/zealot + * @param tableName 表名 + * @param startTime 开始时间(格式:yyyy-MM-dd HH:mm:ss) + * @param endTime 结束时间(格式:yyyy-MM-dd HH:mm:ss) + * @return 生成的 SQL 查询语句 + */ + public static SqlInfo generateSQLQuery(String tableName, String startTime, String endTime, Integer count) { + // 验证时间格式 + // 验证时间格式 +// DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd"); +// String startDate = LocalDate.parse(startTime).format(formatter); +// String endDate = LocalDate.parse(endTime).format(formatter); + int num = count == null ? 5 : count; + return ZealotKhala.start() + .select("*") + .from(tableName) + .where("1=1") + .andBetween("create_date", startTime, endTime) + .limit(String.valueOf(num)) + .end(); + } + + public static void main(String[] args) { + generateSQLQuery("dig_org", "2019-01-01", "2019-01-01", null); + } + +} \ No newline at end of file diff --git a/jcommon/mcp/mcp-hologres/src/main/java/run/mone/mcp/hologres/server/DataSourceMcpServer.java b/jcommon/mcp/mcp-hologres/src/main/java/run/mone/mcp/hologres/server/DataSourceMcpServer.java new file mode 100644 index 000000000..2f60e88a9 --- /dev/null +++ b/jcommon/mcp/mcp-hologres/src/main/java/run/mone/mcp/hologres/server/DataSourceMcpServer.java @@ -0,0 +1,91 @@ +package run.mone.mcp.hologres.server; + +import com.alibaba.druid.pool.DruidDataSource; +import jakarta.annotation.PostConstruct; +import jakarta.annotation.PreDestroy; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +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.Tool; +import run.mone.hive.mcp.spec.ServerMcpTransport; +import run.mone.hive.mcp.server.McpServer.ToolRegistration; +import run.mone.hive.mcp.spec.McpSchema.ServerCapabilities; +import run.mone.mcp.hologres.function.HoloFunction; + +import javax.sql.DataSource; + +@Slf4j +@Component +public class DataSourceMcpServer { + + private ServerMcpTransport transport; + + private McpSyncServer syncServer; + + @Value("${hologres.url}") + private String url; + + @Value("${hologres.userName}") + private String userName; + + @Value("${hologres.password}") + private String password; + + public DataSourceMcpServer(ServerMcpTransport transport) { + this.transport = transport; + log.info("MysqlMcpServer initialized with transport: {}", transport); + } + + public DataSource hologresCarDataSource() { + DruidDataSource druidDataSource = new DruidDataSource(); + druidDataSource.setUsername(userName); + druidDataSource.setPassword(password); + druidDataSource.setUrl("jdbc:postgresql://" + url); + druidDataSource.setName("mpc-hologres"); + return druidDataSource; + } + + public McpSyncServer start() { + log.info("Starting MysqlMcpServer..."); + McpSyncServer syncServer = McpServer.using(transport) + .serverInfo("mysql_mcp_serverV2", "1.0.0") + .capabilities(ServerCapabilities.builder() + .tools(true) + .logging() + .build()) + .sync(); + + // 注册execute_sql工具 + log.info("Registering execute_sql tool..."); + try { + HoloFunction mysqlFunction = new HoloFunction(hologresCarDataSource()); + var sqlToolRegistration = new ToolRegistration( + new Tool(mysqlFunction.getName(), mysqlFunction.getDesc(), mysqlFunction.getSqlToolSchema()), mysqlFunction + ); + syncServer.addTool(sqlToolRegistration); + + log.info("Successfully registered execute_sql tool"); + } catch (Exception e) { + log.error("Failed to register execute_sql tool", e); + throw e; + } + + return syncServer; + } + + @PostConstruct + public void init() { + this.syncServer = start(); + } + + @PreDestroy + public void stop() { + if (this.syncServer != null) { + log.info("Stopping MysqlMcpServer..."); + this.syncServer.closeGracefully(); + } + } +} \ No newline at end of file diff --git a/jcommon/mcp/mcp-hologres/src/main/resources/application.properties b/jcommon/mcp/mcp-hologres/src/main/resources/application.properties new file mode 100644 index 000000000..f66c1b623 --- /dev/null +++ b/jcommon/mcp/mcp-hologres/src/main/resources/application.properties @@ -0,0 +1,6 @@ +stdio.enabled=true +spring.main.web-application-type=none +log.path=D:\\ +#hologres.url=hgprecn-cn-5yd3l8m6l002-cn-beijing-vpc-st.hologres.aliyuncs.com:80/proretail_car_pre?currentSchema=proretail_car_pre_view&autoReconnect=true&rewriteBatchedStatements=true&characterEncoding=utf8 +#hologres.userName=BASIC$proretail_car_pre_rn +#hologres.password=108787A14efe84da \ No newline at end of file diff --git a/jcommon/mcp/mcp-hologres/src/main/resources/logback.xml b/jcommon/mcp/mcp-hologres/src/main/resources/logback.xml new file mode 100644 index 000000000..77a908f7f --- /dev/null +++ b/jcommon/mcp/mcp-hologres/src/main/resources/logback.xml @@ -0,0 +1,35 @@ + + + + + + %d|%-5level|%thread|%logger{40}|%M|%L|%X{trace_id}|%X{span_id}|%msg%n + + + + + + %d|%-5level|%thread|%logger|%M|%L|%msg%n + + + ${log.path}/mcp-hologres/server.log + + 120 + ${log.path}/service-pds/server.log.%d{yyyy-MM-dd-HH} + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/jcommon/mcp/mcp-hologres/src/test/java/run/mone/mcp/hologres/function/SqlFuncitonTest.java b/jcommon/mcp/mcp-hologres/src/test/java/run/mone/mcp/hologres/function/SqlFuncitonTest.java new file mode 100644 index 000000000..c3b212564 --- /dev/null +++ b/jcommon/mcp/mcp-hologres/src/test/java/run/mone/mcp/hologres/function/SqlFuncitonTest.java @@ -0,0 +1,39 @@ + +package run.mone.mcp.hologres.function; + +import lombok.extern.slf4j.Slf4j; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; +import run.mone.hive.mcp.spec.McpSchema; +import run.mone.hive.utils.JsonUtils; +import run.mone.m78.client.util.GsonUtils; +import run.mone.mcp.hologres.HoloBootstrap; +import run.mone.mcp.hologres.server.DataSourceMcpServer; + +import javax.annotation.Resource; +import java.util.HashMap; +import java.util.Map; + +@Slf4j +@RunWith(SpringRunner.class) +@SpringBootTest(classes = HoloBootstrap.class) +public class SqlFuncitonTest { + + @Resource + DataSourceMcpServer dataSourceMcpServer; + + @Test + public void tearDownTestDatabase() { + HoloFunction holoFunction = new HoloFunction(dataSourceMcpServer.hologresCarDataSource()); + Map parameters = new HashMap<>(); + + // 将参数放入 Map 中 + parameters.put("tableName", "dim_org"); + parameters.put("startTime", "2024-03-01"); + parameters.put("endTime", "2024-04-20"); + McpSchema.CallToolResult apply = holoFunction.apply(parameters); + log.info("resp : {}", GsonUtils.GSON.toJson(apply)); + } +} diff --git a/jcommon/mcp/pom.xml b/jcommon/mcp/pom.xml index 2060fb5f7..7844de206 100644 --- a/jcommon/mcp/pom.xml +++ b/jcommon/mcp/pom.xml @@ -34,6 +34,7 @@ mcp-sequentialthinking mcp-git mcp-hammerspoon + mcp-hologres mcp-idea