This is an automated email from the ASF dual-hosted git repository. gongchao pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/hertzbeat.git
The following commit(s) were added to refs/heads/master by this push: new edfd857030 [GSOC] MCP server setup, authorization, and basic tool support (#3610) edfd857030 is described below commit edfd857030610ac4eeddeca89e4ca61edc20633d Author: Sarthak Arora <f20200...@pilani.bits-pilani.ac.in> AuthorDate: Sat Aug 16 16:38:13 2025 +0530 [GSOC] MCP server setup, authorization, and basic tool support (#3610) Signed-off-by: Sarthak Arora <f20200...@pilani.bits-pilani.ac.in> Co-authored-by: Calvin <zhengqi...@apache.org> Co-authored-by: Jast <shengh...@apache.org> Co-authored-by: tomsun28 <tomsu...@outlook.com> --- hertzbeat-ai-agent/pom.xml | 85 +++++++ .../ai/agent/adapters/MonitorServiceAdapter.java | 41 ++++ .../adapters/impl/MonitorServiceAdapterImpl.java | 100 +++++++++ .../ai/agent/config/CustomSseServerTransport.java | 246 +++++++++++++++++++++ .../hertzbeat/ai/agent/config/LlmConfig.java | 37 ++++ .../ai/agent/config/McpContextHolder.java | 53 +++++ .../hertzbeat/ai/agent/config/PromptProvider.java | 53 +++++ .../ai/agent/controller/ChatController.java | 70 ++++++ .../agent/controller/ConversationController.java | 26 +++ .../hertzbeat/ai/agent/dao/ConversationDao.java | 25 +++ .../apache/hertzbeat/ai/agent/dao/MessageDao.java | 25 +++ .../hertzbeat/ai/agent/dao/UserPreferenceDao.java | 25 +++ .../ai/agent/pojo/dto/ChatRequestContext.java | 40 ++++ .../hertzbeat/ai/agent/service/AgentService.java | 26 +++ .../agent/service/ChatClientProviderService.java | 31 +++ .../ai/agent/service/ConversationService.java | 71 ++++++ .../ai/agent/service/McpServerService.java | 28 +++ .../ai/agent/service/impl/AgentServiceImpl.java | 30 +++ .../impl/ChatClientProviderServiceImpl.java | 70 ++++++ .../service/impl/ConversationServiceImpl.java | 29 +++ .../agent/service/impl/McpServerServiceImpl.java | 82 +++++++ .../hertzbeat/ai/agent/tools/AlertTools.java | 25 +++ .../hertzbeat/ai/agent/tools/MetricsTools.java | 25 +++ .../hertzbeat/ai/agent/tools/MonitorTools.java | 50 +++++ .../ai/agent/tools/impl/AlertToolsImpl.java | 25 +++ .../ai/agent/tools/impl/MetricsToolsImpl.java | 25 +++ .../ai/agent/tools/impl/MonitorToolsImpl.java | 87 ++++++++ hertzbeat-manager/pom.xml | 5 + .../java/org/apache/hertzbeat/manager/Manager.java | 2 +- .../src/main/resources/application.yml | 28 +++ hertzbeat-manager/src/main/resources/sureness.yml | 3 +- home/docs/help/mcp_sse_server.md | 71 ++++++ home/sidebars.json | 1 + pom.xml | 2 + 34 files changed, 1540 insertions(+), 2 deletions(-) diff --git a/hertzbeat-ai-agent/pom.xml b/hertzbeat-ai-agent/pom.xml new file mode 100644 index 0000000000..6e637b943b --- /dev/null +++ b/hertzbeat-ai-agent/pom.xml @@ -0,0 +1,85 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Licensed to the Apache Software Foundation (ASF) under one or more + ~ contributor license agreements. See the NOTICE file distributed with + ~ this work for additional information regarding copyright ownership. + ~ The ASF licenses this file to You under the Apache License, Version 2.0 + ~ (the "License"); you may not use this file except in compliance with + ~ the License. You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<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 https://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + <parent> + <groupId>org.apache.hertzbeat</groupId> + <artifactId>hertzbeat</artifactId> + <version>2.0-SNAPSHOT</version> + </parent> + <artifactId>hertzbeat-ai-agent</artifactId> + <version>${hertzbeat.version}</version> + <properties> + <spring-ai.version>1.0.1</spring-ai.version> + <java.version>17</java.version> + </properties> + + <dependencies> + <dependency> + <groupId>org.springframework.ai</groupId> + <artifactId>spring-ai-starter-mcp-server-webmvc</artifactId> + </dependency> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-test</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.junit.platform</groupId> + <artifactId>junit-platform-launcher</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.springframework.ai</groupId> + <artifactId>spring-ai-starter-model-openai</artifactId> + </dependency> + <dependency> + <groupId>org.apache.hertzbeat</groupId> + <artifactId>hertzbeat-common</artifactId> + </dependency> + <dependency> + <groupId>com.usthe.sureness</groupId> + <artifactId>spring-boot3-starter-sureness</artifactId> + </dependency> + </dependencies> + <dependencyManagement> + <dependencies> + <dependency> + <groupId>org.springframework.ai</groupId> + <artifactId>spring-ai-bom</artifactId> + <version>${spring-ai.version}</version> + <type>pom</type> + <scope>import</scope> + </dependency> + </dependencies> + </dependencyManagement> + + <build> + <plugins> + <plugin> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-maven-plugin</artifactId> + <configuration> + <skip>true</skip> + </configuration> + </plugin> + </plugins> + </build> + +</project> \ No newline at end of file diff --git a/hertzbeat-ai-agent/src/main/java/org/apache/hertzbeat/ai/agent/adapters/MonitorServiceAdapter.java b/hertzbeat-ai-agent/src/main/java/org/apache/hertzbeat/ai/agent/adapters/MonitorServiceAdapter.java new file mode 100644 index 0000000000..05bdcc987c --- /dev/null +++ b/hertzbeat-ai-agent/src/main/java/org/apache/hertzbeat/ai/agent/adapters/MonitorServiceAdapter.java @@ -0,0 +1,41 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package org.apache.hertzbeat.ai.agent.adapters; + +import org.springframework.data.domain.Page; +import org.apache.hertzbeat.common.entity.manager.Monitor; +import java.util.List; + +/** + * Interface that provides access to monitor information by retrieving monitor data + * through the underlying monitor service. + */ +public interface MonitorServiceAdapter { + Page<Monitor> getMonitors( + List<Long> ids, + String app, + String search, + Byte status, + String sort, + String order, + Integer pageIndex, + Integer pageSize, + String labels + ); +} \ No newline at end of file diff --git a/hertzbeat-ai-agent/src/main/java/org/apache/hertzbeat/ai/agent/adapters/impl/MonitorServiceAdapterImpl.java b/hertzbeat-ai-agent/src/main/java/org/apache/hertzbeat/ai/agent/adapters/impl/MonitorServiceAdapterImpl.java new file mode 100644 index 0000000000..50d6eb4ced --- /dev/null +++ b/hertzbeat-ai-agent/src/main/java/org/apache/hertzbeat/ai/agent/adapters/impl/MonitorServiceAdapterImpl.java @@ -0,0 +1,100 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package org.apache.hertzbeat.ai.agent.adapters.impl; + +import com.usthe.sureness.subject.SubjectSum; +import lombok.extern.slf4j.Slf4j; +import org.apache.hertzbeat.ai.agent.adapters.MonitorServiceAdapter; +import org.apache.hertzbeat.ai.agent.config.McpContextHolder; +import org.springframework.data.domain.Page; +import org.apache.hertzbeat.common.entity.manager.Monitor; +import org.apache.hertzbeat.common.support.SpringContextHolder; +import org.springframework.stereotype.Component; + +import java.lang.reflect.Method; +import java.util.List; + +/** + * Implementation of the MonitorServiceAdapter interface that provides access to monitor information + * through reflection by invoking the underlying monitor service implementation. + */ +@Slf4j +@Component +public class MonitorServiceAdapterImpl implements MonitorServiceAdapter { + + @Override + public Page<Monitor> getMonitors( + List<Long> ids, + String app, + String search, + Byte status, + String sort, + String order, + Integer pageIndex, + Integer pageSize, + String labels) { + try { + // Provide default values for all nullable parameters + if (sort == null || sort.trim().isEmpty()) { + sort = "gmtCreate"; + } + if (order == null || order.trim().isEmpty()) { + order = "desc"; + } + if (pageIndex == null) { + pageIndex = 0; + } + if (pageSize == null) { + pageSize = 8; + } + + Object monitorService = null; + SubjectSum subjectSum = McpContextHolder.getSubject(); + log.debug("Current security subject: {}", subjectSum); + + try { + monitorService = SpringContextHolder.getBean("monitorServiceImpl"); + } catch (Exception e) { + log.debug("Could not find bean by name 'monitorServiceImpl', trying by class name"); + } + + assert monitorService != null; + log.debug("MonitorService bean found: {}", monitorService.getClass().getSimpleName()); + Method method = monitorService.getClass().getMethod( + "getMonitors", + List.class, String.class, String.class, Byte.class, + String.class, String.class, int.class, int.class, String.class); + + + @SuppressWarnings("unchecked") + Page<Monitor> result = (Page<Monitor>) method.invoke( + monitorService, + ids, app, search, status, sort, order, pageIndex, pageSize, labels); + log.debug("MonitorServiceAdapter.getMonitors result: {}", result.getContent()); + return result; + + } catch (NoSuchMethodException e) { + throw new RuntimeException("Method not found: getMonitors", e); + } catch (Exception e) { + log.debug("Failed to invoke getMonitors via adapter", e); + throw new RuntimeException("Failed to invoke getMonitors via adapter", e); + } + } + +} diff --git a/hertzbeat-ai-agent/src/main/java/org/apache/hertzbeat/ai/agent/config/CustomSseServerTransport.java b/hertzbeat-ai-agent/src/main/java/org/apache/hertzbeat/ai/agent/config/CustomSseServerTransport.java new file mode 100644 index 0000000000..7eacfb90bb --- /dev/null +++ b/hertzbeat-ai-agent/src/main/java/org/apache/hertzbeat/ai/agent/config/CustomSseServerTransport.java @@ -0,0 +1,246 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.hertzbeat.ai.agent.config; + + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.usthe.sureness.mgt.SurenessSecurityManager; +import com.usthe.sureness.subject.SubjectSum; +import io.modelcontextprotocol.spec.McpError; +import io.modelcontextprotocol.spec.McpSchema; +import io.modelcontextprotocol.spec.McpServerSession; +import io.modelcontextprotocol.spec.McpServerTransport; +import io.modelcontextprotocol.spec.McpServerTransportProvider; +import io.modelcontextprotocol.util.Assert; +import java.io.IOException; +import java.time.Duration; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +import jakarta.servlet.http.HttpServletRequest; +import lombok.Getter; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.web.servlet.function.RouterFunction; +import org.springframework.web.servlet.function.RouterFunctions; +import org.springframework.web.servlet.function.ServerRequest; +import org.springframework.web.servlet.function.ServerResponse; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +/** + * Custom Server-Sent Events transport provider for Model Context Protocol. + */ +@Slf4j +public class CustomSseServerTransport implements McpServerTransportProvider { + private final ObjectMapper objectMapper; + private final String messageEndpoint; + private final String sseEndpoint; + private final String baseUrl; + @Getter + private final RouterFunction<ServerResponse> routerFunction; + @Setter + private McpServerSession.Factory sessionFactory; + private final Map<String, Object> sessionRequest = new HashMap<>(); + private final ConcurrentHashMap<String, McpServerSession> sessions; + private volatile boolean isClosing; + + public CustomSseServerTransport(ObjectMapper objectMapper, String messageEndpoint) { + this(objectMapper, messageEndpoint, "/sse"); + } + + + public CustomSseServerTransport(ObjectMapper objectMapper, String messageEndpoint, String sseEndpoint) { + this(objectMapper, "", messageEndpoint, sseEndpoint); + } + + public CustomSseServerTransport(ObjectMapper objectMapper, String baseUrl, String messageEndpoint, String sseEndpoint) { + this.sessions = new ConcurrentHashMap(); + this.isClosing = false; + Assert.notNull(objectMapper, "ObjectMapper must not be null"); + Assert.notNull(baseUrl, "Message base URL must not be null"); + Assert.notNull(messageEndpoint, "Message endpoint must not be null"); + Assert.notNull(sseEndpoint, "SSE endpoint must not be null"); + this.objectMapper = objectMapper; + this.baseUrl = baseUrl; + this.messageEndpoint = messageEndpoint; + this.sseEndpoint = sseEndpoint; + this.routerFunction = RouterFunctions.route().GET(this.sseEndpoint, this::handleSseConnection).POST(this.messageEndpoint, this::handleMessage).build(); + } + + public Mono<Void> notifyClients(String method, Object params) { + if (this.sessions.isEmpty()) { + log.debug("No active sessions to broadcast message to"); + return Mono.empty(); + } else { + log.debug("Attempting to broadcast message to {} active sessions", this.sessions.size()); + return Flux.fromIterable(this.sessions.values()) + .flatMap((session) -> session.sendNotification(method, params) + .doOnError((e) -> log.error("Failed to send message to session {}: {}", session.getId(), e.getMessage())) + .onErrorComplete()) + .then(); + } + } + + public Mono<Void> closeGracefully() { + return Flux.fromIterable(this.sessions.values()).doFirst(() -> { + this.isClosing = true; + log.debug("Initiating graceful shutdown with {} active sessions", this.sessions.size()); + }).flatMap(McpServerSession::closeGracefully).then().doOnSuccess((v) -> log.debug("Graceful shutdown completed")); + } + + private ServerResponse handleSseConnection(ServerRequest request) { + log.debug("Handling SSE connection for request: {}", request); + HttpServletRequest servletRequest = request.servletRequest(); + + try { + + log.debug("Processing SSE connection for servlet request: {}", servletRequest); + log.debug("Authorization header: {}", servletRequest.getHeader("Authorization")); + + + } catch (Exception e) { + log.error("Authentication failed for SSE connection: {}", e.getMessage()); + return ServerResponse.status(HttpStatus.UNAUTHORIZED).body("Unauthorized: " + e.getMessage()); + } + + + + if (this.isClosing) { + return ServerResponse.status(HttpStatus.SERVICE_UNAVAILABLE).body("Server is shutting down"); + } else { + String sessionId = UUID.randomUUID().toString(); + log.debug("Generated session ID for SSE connection: {}", sessionId); + log.debug("Creating new SSE connection for session: {}", sessionId); + + + return ServerResponse.sse((sseBuilder) -> { + sseBuilder.onComplete(() -> { + log.debug("SSE connection completed for session: {}", sessionId); + this.sessions.remove(sessionId); + }); + sseBuilder.onTimeout(() -> { + log.debug("SSE connection timed out for session: {}", sessionId); + this.sessions.remove(sessionId); + }); + CustomSseServerTransport.WebMvcMcpSessionTransport sessionTransport = new CustomSseServerTransport.WebMvcMcpSessionTransport(sessionId, sseBuilder); + McpServerSession session = this.sessionFactory.create(sessionTransport); + this.sessionRequest.put(sessionId, request.servletRequest()); + this.sessions.put(sessionId, session); + + try { + sseBuilder.id(sessionId).event("endpoint").data(this.baseUrl + this.messageEndpoint + "?sessionId=" + sessionId); + } catch (Exception e) { + log.error("Failed to send initial endpoint event: {}", e.getMessage()); + sseBuilder.error(e); + } + + }, Duration.ZERO); + + } + } + + private ServerResponse handleMessage(ServerRequest request) { + if (this.isClosing) { + return ServerResponse.status(HttpStatus.SERVICE_UNAVAILABLE).body("Server is shutting down"); + } else if (request.param("sessionId").isEmpty()) { + return ServerResponse.badRequest().body(new McpError("Session ID missing in message endpoint")); + } else { + String sessionId = (String) request.param("sessionId").get(); + McpServerSession session = (McpServerSession) this.sessions.get(sessionId); + log.debug("Authorization header for message request: {}", request.servletRequest().getHeader("Authorization")); + SubjectSum subject = SurenessSecurityManager.getInstance().checkIn(sessionRequest.get(sessionId)); + McpContextHolder.setSubject(subject); + + + if (session == null) { + return ServerResponse.status(HttpStatus.NOT_FOUND).body(new McpError("Session not found: " + sessionId)); + } else { + try { + String body = request.body(String.class); + McpSchema.JSONRPCMessage message = McpSchema.deserializeJsonRpcMessage(this.objectMapper, body); + session.handle(message).block(); + return ServerResponse.ok().build(); + } catch (IOException | IllegalArgumentException e) { + log.error("Failed to deserialize message: {}", ((Exception) e).getMessage()); + return ServerResponse.badRequest().body(new McpError("Invalid message format")); + } catch (Exception e) { + log.error("Error handling message: {}", e.getMessage()); + return ServerResponse.status(HttpStatus.INTERNAL_SERVER_ERROR).body(new McpError(e.getMessage())); + } + } + } + } + + private class WebMvcMcpSessionTransport implements McpServerTransport { + private final String sessionId; + private final ServerResponse.SseBuilder sseBuilder; + + WebMvcMcpSessionTransport(String sessionId, ServerResponse.SseBuilder sseBuilder) { + this.sessionId = sessionId; + this.sseBuilder = sseBuilder; + log.debug("Session transport {} initialized with SSE builder", sessionId); + } + + public Mono<Void> sendMessage(McpSchema.JSONRPCMessage message) { + return Mono.fromRunnable(() -> { + try { + String jsonText = CustomSseServerTransport.this.objectMapper.writeValueAsString(message); + this.sseBuilder.id(this.sessionId).event("message").data(jsonText); + log.debug("Message sent to session {}", this.sessionId); + } catch (Exception e) { + log.error("Failed to send message to session {}: {}", this.sessionId, e.getMessage()); + this.sseBuilder.error(e); + } + + }); + } + + public <T> T unmarshalFrom(Object data, TypeReference<T> typeRef) { + return (T) CustomSseServerTransport.this.objectMapper.convertValue(data, typeRef); + } + + public Mono<Void> closeGracefully() { + return Mono.fromRunnable(() -> { + log.debug("Closing session transport: {}", this.sessionId); + + try { + this.sseBuilder.complete(); + log.debug("Successfully completed SSE builder for session {}", this.sessionId); + } catch (Exception e) { + log.warn("Failed to complete SSE builder for session {}: {}", this.sessionId, e.getMessage()); + } + + }); + } + + public void close() { + try { + this.sseBuilder.complete(); + log.debug("Successfully completed SSE builder for session {}", this.sessionId); + } catch (Exception e) { + log.warn("Failed to complete SSE builder for session {}: {}", this.sessionId, e.getMessage()); + } + + } + } +} diff --git a/hertzbeat-ai-agent/src/main/java/org/apache/hertzbeat/ai/agent/config/LlmConfig.java b/hertzbeat-ai-agent/src/main/java/org/apache/hertzbeat/ai/agent/config/LlmConfig.java new file mode 100644 index 0000000000..eb7bd6aef4 --- /dev/null +++ b/hertzbeat-ai-agent/src/main/java/org/apache/hertzbeat/ai/agent/config/LlmConfig.java @@ -0,0 +1,37 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package org.apache.hertzbeat.ai.agent.config; + +import org.springframework.ai.chat.client.ChatClient; +import org.springframework.ai.openai.OpenAiChatModel; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * Configuration class for Large Language Model (LLM) settings. + */ + +@Configuration +public class LlmConfig { + @Bean + public ChatClient openAiChatClient(OpenAiChatModel chatModel) { + return ChatClient.create(chatModel); + } + +} diff --git a/hertzbeat-ai-agent/src/main/java/org/apache/hertzbeat/ai/agent/config/McpContextHolder.java b/hertzbeat-ai-agent/src/main/java/org/apache/hertzbeat/ai/agent/config/McpContextHolder.java new file mode 100644 index 0000000000..2fd201c8a1 --- /dev/null +++ b/hertzbeat-ai-agent/src/main/java/org/apache/hertzbeat/ai/agent/config/McpContextHolder.java @@ -0,0 +1,53 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.hertzbeat.ai.agent.config; + +import com.usthe.sureness.subject.SubjectSum; +import org.springframework.core.NamedInheritableThreadLocal; + +/** + * Context holder for AI agent security context. + */ +public final class McpContextHolder { + private static final ThreadLocal<SubjectSum> subjectHolder = + new NamedInheritableThreadLocal<>("MCP Security and User Identification Context"); + + private McpContextHolder() {} + + /** + * Attaches the user's context to the current thread. + */ + public static void setSubject(SubjectSum subject) { + subjectHolder.set(subject); + + } + + /** + * Retrieves the context from the current thread. + */ + public static SubjectSum getSubject() { + return subjectHolder.get(); + } + + /** + * Clears the context from the thread to prevent memory leaks. + */ + public static void clear() { + subjectHolder.remove(); + } +} diff --git a/hertzbeat-ai-agent/src/main/java/org/apache/hertzbeat/ai/agent/config/PromptProvider.java b/hertzbeat-ai-agent/src/main/java/org/apache/hertzbeat/ai/agent/config/PromptProvider.java new file mode 100644 index 0000000000..fa7a685f00 --- /dev/null +++ b/hertzbeat-ai-agent/src/main/java/org/apache/hertzbeat/ai/agent/config/PromptProvider.java @@ -0,0 +1,53 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package org.apache.hertzbeat.ai.agent.config; + +import org.springframework.stereotype.Component; + +/** + * Provider for system prompts used in the AI agent + */ + +@Component +public class PromptProvider { + /** + * Static version of the HertzBeat monitoring prompt + */ + public static final String HERTZBEAT_MONITORING_PROMPT = """ + You are an AI assistant specialized in monitoring infrastructure and applications with HertzBeat. + Your role is to help users manage and analyze their monitoring data using the available tools. + You have access to the following HertzBeat monitoring tools: + - list_monitors: Query monitor information with flexible filtering and pagination + - add_monitor: Add a new monitor to the system + When users ask questions about their monitoring setup or data, identify which tool would be most helpful + and use it to provide relevant information. Always provide clear explanations of the monitoring data and + suggest next steps or insights based on the results. + For monitoring-related queries: + 1. If users want to see their monitors, use list_monitors with appropriate filters + 2. If users want to add a new monitor, use add_monitor with the necessary details + 3. If the monitoring information shows potential issues, highlight them and suggest troubleshooting steps + For parameters that accept specific values: + - Monitor status values: 0 (no monitor), 1 (usable), 2 (disabled), 9 (all) + - Sort fields typically include: name, host, app, gmtCreate + - Sort order should be 'asc' or 'desc' + Keep responses focused on monitoring topics and HertzBeat capabilities. + If you're unsure about specific monitoring details, ask clarifying questions before using the tools. + """; + +} diff --git a/hertzbeat-ai-agent/src/main/java/org/apache/hertzbeat/ai/agent/controller/ChatController.java b/hertzbeat-ai-agent/src/main/java/org/apache/hertzbeat/ai/agent/controller/ChatController.java new file mode 100644 index 0000000000..b2d84cbe8b --- /dev/null +++ b/hertzbeat-ai-agent/src/main/java/org/apache/hertzbeat/ai/agent/controller/ChatController.java @@ -0,0 +1,70 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package org.apache.hertzbeat.ai.agent.controller; + +import org.springframework.ai.chat.client.ChatClient; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.http.MediaType; +import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; +import org.apache.hertzbeat.ai.agent.pojo.dto.ChatRequestContext; +import org.apache.hertzbeat.ai.agent.service.ChatClientProviderService; + + +/** + * Controller class for handling chat-related HTTP requests. + */ +@RestController +@RequestMapping("/api/chat") +public class ChatController { + + private final ChatClientProviderService chatClientProviderService; + + @Autowired + public ChatController(@Qualifier("openAiChatClient") ChatClient openAiChatClient, + ChatClientProviderService chatClientProviderService) { + this.chatClientProviderService = chatClientProviderService; + } + + /** + * Send a message and get a streaming response + * + * @param context The chat request context containing message and optional + * conversationId + * @return SSE emitter for streaming response + */ + @PostMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE) + public SseEmitter streamChat(@RequestBody ChatRequestContext context) { + SseEmitter emitter = new SseEmitter(); + new Thread(() -> { + try { + String aiResponse = chatClientProviderService.streamChat(context); + emitter.send(aiResponse); + emitter.complete(); + } catch (Exception e) { + emitter.completeWithError(e); + } + }).start(); + return emitter; + } +} diff --git a/hertzbeat-ai-agent/src/main/java/org/apache/hertzbeat/ai/agent/controller/ConversationController.java b/hertzbeat-ai-agent/src/main/java/org/apache/hertzbeat/ai/agent/controller/ConversationController.java new file mode 100644 index 0000000000..82b1114d9c --- /dev/null +++ b/hertzbeat-ai-agent/src/main/java/org/apache/hertzbeat/ai/agent/controller/ConversationController.java @@ -0,0 +1,26 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package org.apache.hertzbeat.ai.agent.controller; + +/** + * Controller for managing conversations. + */ +public class ConversationController { + +} diff --git a/hertzbeat-ai-agent/src/main/java/org/apache/hertzbeat/ai/agent/dao/ConversationDao.java b/hertzbeat-ai-agent/src/main/java/org/apache/hertzbeat/ai/agent/dao/ConversationDao.java new file mode 100644 index 0000000000..4fe1ffe39a --- /dev/null +++ b/hertzbeat-ai-agent/src/main/java/org/apache/hertzbeat/ai/agent/dao/ConversationDao.java @@ -0,0 +1,25 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package org.apache.hertzbeat.ai.agent.dao; + +/** + * Data Access Object interface for Conversation entities. + */ +public interface ConversationDao { +} diff --git a/hertzbeat-ai-agent/src/main/java/org/apache/hertzbeat/ai/agent/dao/MessageDao.java b/hertzbeat-ai-agent/src/main/java/org/apache/hertzbeat/ai/agent/dao/MessageDao.java new file mode 100644 index 0000000000..4d66284862 --- /dev/null +++ b/hertzbeat-ai-agent/src/main/java/org/apache/hertzbeat/ai/agent/dao/MessageDao.java @@ -0,0 +1,25 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package org.apache.hertzbeat.ai.agent.dao; + +/** + * Data Access Object interface for Message entities. + */ +public interface MessageDao { +} diff --git a/hertzbeat-ai-agent/src/main/java/org/apache/hertzbeat/ai/agent/dao/UserPreferenceDao.java b/hertzbeat-ai-agent/src/main/java/org/apache/hertzbeat/ai/agent/dao/UserPreferenceDao.java new file mode 100644 index 0000000000..4aff3dd599 --- /dev/null +++ b/hertzbeat-ai-agent/src/main/java/org/apache/hertzbeat/ai/agent/dao/UserPreferenceDao.java @@ -0,0 +1,25 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package org.apache.hertzbeat.ai.agent.dao; + +/** + * Data Access Object interface for UserPreference entities. + */ +public interface UserPreferenceDao { +} diff --git a/hertzbeat-ai-agent/src/main/java/org/apache/hertzbeat/ai/agent/pojo/dto/ChatRequestContext.java b/hertzbeat-ai-agent/src/main/java/org/apache/hertzbeat/ai/agent/pojo/dto/ChatRequestContext.java new file mode 100644 index 0000000000..043e19c57c --- /dev/null +++ b/hertzbeat-ai-agent/src/main/java/org/apache/hertzbeat/ai/agent/pojo/dto/ChatRequestContext.java @@ -0,0 +1,40 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package org.apache.hertzbeat.ai.agent.pojo.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * Chat request context for AI chat endpoint. + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class ChatRequestContext { + /** + * The user's message (required) + */ + private String message; + /** + * Optional conversation ID for context + */ + private String conversationId; +} diff --git a/hertzbeat-ai-agent/src/main/java/org/apache/hertzbeat/ai/agent/service/AgentService.java b/hertzbeat-ai-agent/src/main/java/org/apache/hertzbeat/ai/agent/service/AgentService.java new file mode 100644 index 0000000000..c9c1f3b7d6 --- /dev/null +++ b/hertzbeat-ai-agent/src/main/java/org/apache/hertzbeat/ai/agent/service/AgentService.java @@ -0,0 +1,26 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package org.apache.hertzbeat.ai.agent.service; + +/** + * Service interface for agent operations. + */ +public interface AgentService { + +} diff --git a/hertzbeat-ai-agent/src/main/java/org/apache/hertzbeat/ai/agent/service/ChatClientProviderService.java b/hertzbeat-ai-agent/src/main/java/org/apache/hertzbeat/ai/agent/service/ChatClientProviderService.java new file mode 100644 index 0000000000..b12b5537f0 --- /dev/null +++ b/hertzbeat-ai-agent/src/main/java/org/apache/hertzbeat/ai/agent/service/ChatClientProviderService.java @@ -0,0 +1,31 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package org.apache.hertzbeat.ai.agent.service; + +import org.apache.hertzbeat.ai.agent.pojo.dto.ChatRequestContext; + +/** + * Service for interacting with LLM providers (like OpenAI, Anthropic, etc.) + */ +public interface ChatClientProviderService { + + String complete(String message); + + String streamChat(ChatRequestContext context); +} diff --git a/hertzbeat-ai-agent/src/main/java/org/apache/hertzbeat/ai/agent/service/ConversationService.java b/hertzbeat-ai-agent/src/main/java/org/apache/hertzbeat/ai/agent/service/ConversationService.java new file mode 100644 index 0000000000..724e876e5e --- /dev/null +++ b/hertzbeat-ai-agent/src/main/java/org/apache/hertzbeat/ai/agent/service/ConversationService.java @@ -0,0 +1,71 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package org.apache.hertzbeat.ai.agent.service; + + +import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; + +import java.util.List; +import java.util.Map; + +/** + * Service for managing chat conversations and interactions with LLM providers. + */ +public interface ConversationService { + + /** + * Send a message and receive a streaming response + * + * @param message The user's message + * @param conversationId Optional conversation ID for continuing a chat + * @return SseEmitter for streaming the response + */ + SseEmitter streamChat(String message, String conversationId); + + /** + * Send a message and get a complete response + * + * @param message The user's message + * @param conversationId Optional conversation ID for continuing a chat + * @return Response object containing the AI's response and conversation metadata + */ + Map<String, Object> chat(String message, String conversationId); + + /** + * Get conversation history for a specific conversation + * + * @param conversationId Conversation ID + * @return Conversation data including messages + */ + Map<String, Object> getConversation(String conversationId); + + /** + * Get all conversations for the current user + * + * @return List of conversations + */ + List<Map<String, Object>> getAllConversations(); + + /** + * Delete a conversation + * + * @param conversationId Conversation ID to delete + */ + void deleteConversation(String conversationId); +} diff --git a/hertzbeat-ai-agent/src/main/java/org/apache/hertzbeat/ai/agent/service/McpServerService.java b/hertzbeat-ai-agent/src/main/java/org/apache/hertzbeat/ai/agent/service/McpServerService.java new file mode 100644 index 0000000000..75b4de981d --- /dev/null +++ b/hertzbeat-ai-agent/src/main/java/org/apache/hertzbeat/ai/agent/service/McpServerService.java @@ -0,0 +1,28 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package org.apache.hertzbeat.ai.agent.service; + +import org.springframework.ai.tool.ToolCallbackProvider; + +/** + * Service interface for MCP server operations. + */ +public interface McpServerService { + ToolCallbackProvider hertzbeatTools(); +} diff --git a/hertzbeat-ai-agent/src/main/java/org/apache/hertzbeat/ai/agent/service/impl/AgentServiceImpl.java b/hertzbeat-ai-agent/src/main/java/org/apache/hertzbeat/ai/agent/service/impl/AgentServiceImpl.java new file mode 100644 index 0000000000..d6bf6e98a4 --- /dev/null +++ b/hertzbeat-ai-agent/src/main/java/org/apache/hertzbeat/ai/agent/service/impl/AgentServiceImpl.java @@ -0,0 +1,30 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package org.apache.hertzbeat.ai.agent.service.impl; + +import org.apache.hertzbeat.ai.agent.service.AgentService; +import org.springframework.stereotype.Service; + +/** + * Implementation of the AgentService interface. + * This service provides functionality for handling AI agent operations. + */ +@Service +public class AgentServiceImpl implements AgentService { +} diff --git a/hertzbeat-ai-agent/src/main/java/org/apache/hertzbeat/ai/agent/service/impl/ChatClientProviderServiceImpl.java b/hertzbeat-ai-agent/src/main/java/org/apache/hertzbeat/ai/agent/service/impl/ChatClientProviderServiceImpl.java new file mode 100644 index 0000000000..8d6582bae8 --- /dev/null +++ b/hertzbeat-ai-agent/src/main/java/org/apache/hertzbeat/ai/agent/service/impl/ChatClientProviderServiceImpl.java @@ -0,0 +1,70 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package org.apache.hertzbeat.ai.agent.service.impl; + +import org.apache.hertzbeat.ai.agent.config.PromptProvider; +import org.apache.hertzbeat.ai.agent.service.ChatClientProviderService; +import org.springframework.stereotype.Service; +import org.apache.hertzbeat.ai.agent.pojo.dto.ChatRequestContext; +import org.springframework.ai.chat.client.ChatClient; +import org.springframework.ai.tool.ToolCallbackProvider; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; + +/** + * Implementation of the {@link ChatClientProviderService}. + * Provides functionality to interact with the ChatClient for handling chat + * messages. + */ +@Service +public class ChatClientProviderServiceImpl implements ChatClientProviderService { + + private final ChatClient chatClient; + + @Qualifier("hertzbeatTools") + @Autowired + private ToolCallbackProvider toolCallbackProvider; + + @Autowired + public ChatClientProviderServiceImpl(@Qualifier("openAiChatClient") ChatClient openAiChatClient) { + this.chatClient = openAiChatClient; + } + + @Override + public String complete(String message) { + return this.chatClient.prompt() + .user(message) + .call() + .content(); + } + + @Override + public String streamChat(ChatRequestContext context) { + try { + return this.chatClient.prompt(PromptProvider.HERTZBEAT_MONITORING_PROMPT) + .user(context.getMessage()) + .toolCallbacks(toolCallbackProvider) + .call() + .content(); + } catch (Exception e) { + return "Error: " + e.getMessage(); + } + + } +} diff --git a/hertzbeat-ai-agent/src/main/java/org/apache/hertzbeat/ai/agent/service/impl/ConversationServiceImpl.java b/hertzbeat-ai-agent/src/main/java/org/apache/hertzbeat/ai/agent/service/impl/ConversationServiceImpl.java new file mode 100644 index 0000000000..8e5a1b8eff --- /dev/null +++ b/hertzbeat-ai-agent/src/main/java/org/apache/hertzbeat/ai/agent/service/impl/ConversationServiceImpl.java @@ -0,0 +1,29 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package org.apache.hertzbeat.ai.agent.service.impl; + +import org.springframework.stereotype.Service; + +/** + * Implementation of the ConversationService interface for managing chat conversations. + */ +@Service +public class ConversationServiceImpl { + +} diff --git a/hertzbeat-ai-agent/src/main/java/org/apache/hertzbeat/ai/agent/service/impl/McpServerServiceImpl.java b/hertzbeat-ai-agent/src/main/java/org/apache/hertzbeat/ai/agent/service/impl/McpServerServiceImpl.java new file mode 100644 index 0000000000..a22a843dc5 --- /dev/null +++ b/hertzbeat-ai-agent/src/main/java/org/apache/hertzbeat/ai/agent/service/impl/McpServerServiceImpl.java @@ -0,0 +1,82 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package org.apache.hertzbeat.ai.agent.service.impl; + +import org.apache.hertzbeat.ai.agent.config.CustomSseServerTransport; +import org.apache.hertzbeat.ai.agent.service.McpServerService; +import org.springframework.ai.mcp.server.autoconfigure.McpServerProperties; +import org.springframework.context.annotation.Primary; +import org.springframework.stereotype.Service; +import org.apache.hertzbeat.ai.agent.tools.impl.MonitorToolsImpl; +import org.springframework.ai.tool.ToolCallbackProvider; +import org.springframework.ai.tool.method.MethodToolCallbackProvider; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.beans.factory.annotation.Autowired; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.springframework.web.servlet.function.RouterFunction; +import org.springframework.web.servlet.function.ServerResponse; + +/** + * Implementation of the McpServerService interface. + * This service provides functionality for handling MCP server operations. + */ +@Service +@Configuration +public class McpServerServiceImpl implements McpServerService { + @Autowired + private MonitorToolsImpl monitorTools; + + @Bean + public ToolCallbackProvider hertzbeatTools() { + return MethodToolCallbackProvider.builder().toolObjects(monitorTools).build(); + } + /** + * Provides a custom SSE server transport for the MCP server. + * + * @param objectMapper the ObjectMapper instance for JSON serialization + * @param serverProperties the properties for the MCP server configuration + * @return a CustomSseServerTransport instance configured with the provided properties + */ + + @Bean + public CustomSseServerTransport webMvcSseServerTransportProvider( + ObjectMapper objectMapper, + McpServerProperties serverProperties + ) { + return new CustomSseServerTransport( + objectMapper, + serverProperties.getBaseUrl(), + serverProperties.getSseMessageEndpoint(), + serverProperties.getSseEndpoint() + ); + } + /** + * Provides the MCP server transport bean. + * + * @param transport the custom SSE server transport + * @return the MCP server transport instance + */ + + @Primary + @Bean + public RouterFunction<ServerResponse> mvcMcpRouterFunction(CustomSseServerTransport transport) { + return transport.getRouterFunction(); + } +} diff --git a/hertzbeat-ai-agent/src/main/java/org/apache/hertzbeat/ai/agent/tools/AlertTools.java b/hertzbeat-ai-agent/src/main/java/org/apache/hertzbeat/ai/agent/tools/AlertTools.java new file mode 100644 index 0000000000..9190539a25 --- /dev/null +++ b/hertzbeat-ai-agent/src/main/java/org/apache/hertzbeat/ai/agent/tools/AlertTools.java @@ -0,0 +1,25 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package org.apache.hertzbeat.ai.agent.tools; + +/** + * Tools for alert operations + */ +public interface AlertTools { +} diff --git a/hertzbeat-ai-agent/src/main/java/org/apache/hertzbeat/ai/agent/tools/MetricsTools.java b/hertzbeat-ai-agent/src/main/java/org/apache/hertzbeat/ai/agent/tools/MetricsTools.java new file mode 100644 index 0000000000..0c237c222a --- /dev/null +++ b/hertzbeat-ai-agent/src/main/java/org/apache/hertzbeat/ai/agent/tools/MetricsTools.java @@ -0,0 +1,25 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package org.apache.hertzbeat.ai.agent.tools; + +/** + * Tools for metrics operations + */ +public interface MetricsTools { +} diff --git a/hertzbeat-ai-agent/src/main/java/org/apache/hertzbeat/ai/agent/tools/MonitorTools.java b/hertzbeat-ai-agent/src/main/java/org/apache/hertzbeat/ai/agent/tools/MonitorTools.java new file mode 100644 index 0000000000..fc4a8cff11 --- /dev/null +++ b/hertzbeat-ai-agent/src/main/java/org/apache/hertzbeat/ai/agent/tools/MonitorTools.java @@ -0,0 +1,50 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package org.apache.hertzbeat.ai.agent.tools; + +import org.springframework.ai.chat.model.ToolContext; + +import java.util.List; + +/** + * Interface for Monitoring Tools + */ +public interface MonitorTools { + + String addMonitor(String name, ToolContext context); + + /** + * Query monitor information with flexible filtering and pagination. + * Supports filtering by monitor IDs, type, status, host, labels, sorting, and + * pagination. + * Returns results as plain JSON. + */ + String listMonitors( + List<Long> ids, + String app, + Byte status, + String search, + String labels, + String sort, + String order, + Integer pageIndex, + Integer pageSize, + ToolContext context); + +} diff --git a/hertzbeat-ai-agent/src/main/java/org/apache/hertzbeat/ai/agent/tools/impl/AlertToolsImpl.java b/hertzbeat-ai-agent/src/main/java/org/apache/hertzbeat/ai/agent/tools/impl/AlertToolsImpl.java new file mode 100644 index 0000000000..b846d35f68 --- /dev/null +++ b/hertzbeat-ai-agent/src/main/java/org/apache/hertzbeat/ai/agent/tools/impl/AlertToolsImpl.java @@ -0,0 +1,25 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package org.apache.hertzbeat.ai.agent.tools.impl; + +/** + * Implementation of Alert Tools functionality + */ +public class AlertToolsImpl { +} diff --git a/hertzbeat-ai-agent/src/main/java/org/apache/hertzbeat/ai/agent/tools/impl/MetricsToolsImpl.java b/hertzbeat-ai-agent/src/main/java/org/apache/hertzbeat/ai/agent/tools/impl/MetricsToolsImpl.java new file mode 100644 index 0000000000..c79b1b4d83 --- /dev/null +++ b/hertzbeat-ai-agent/src/main/java/org/apache/hertzbeat/ai/agent/tools/impl/MetricsToolsImpl.java @@ -0,0 +1,25 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package org.apache.hertzbeat.ai.agent.tools.impl; + +/** + * Implementation of Metrics Tools functionality + */ +public class MetricsToolsImpl { +} diff --git a/hertzbeat-ai-agent/src/main/java/org/apache/hertzbeat/ai/agent/tools/impl/MonitorToolsImpl.java b/hertzbeat-ai-agent/src/main/java/org/apache/hertzbeat/ai/agent/tools/impl/MonitorToolsImpl.java new file mode 100644 index 0000000000..3f07c97f35 --- /dev/null +++ b/hertzbeat-ai-agent/src/main/java/org/apache/hertzbeat/ai/agent/tools/impl/MonitorToolsImpl.java @@ -0,0 +1,87 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.hertzbeat.ai.agent.tools.impl; + +import com.usthe.sureness.subject.SubjectSum; +import lombok.extern.slf4j.Slf4j; +import org.apache.hertzbeat.ai.agent.adapters.MonitorServiceAdapter; +import org.apache.hertzbeat.ai.agent.config.McpContextHolder; +import org.springframework.ai.chat.model.ToolContext; +import org.springframework.ai.tool.annotation.Tool; +import org.springframework.data.domain.Page; +import org.springframework.stereotype.Service; +import org.apache.hertzbeat.ai.agent.tools.MonitorTools; +import org.springframework.ai.tool.annotation.ToolParam; +import org.springframework.beans.factory.annotation.Autowired; +import org.apache.hertzbeat.common.entity.manager.Monitor; + +import java.util.List; + +/** + * Implementation of Monitoring Tools functionality + */ +@Slf4j +@Service +public class MonitorToolsImpl implements MonitorTools { + + @Autowired + private MonitorServiceAdapter monitorServiceAdapter; + + + /** + * Tool to query monitor information with flexible filtering and pagination. + * Supports filtering by monitor IDs, type, status, host, labels, sorting, and + * pagination. + * Returns monitor names as string. + */ + @Override + @Tool(name = "list_monitors", returnDirect = true, description = """ + Query monitor information with flexible filtering and pagination. + Supports filtering by monitor IDs, type, status, host, labels, sorting, and pagination. + Returns results as String. When no parameters are available, pass the default value as mentioned below. If the user doesn't provide any specific parameter, the default value will be used. + """) + public String listMonitors( + @ToolParam(description = "List of monitor IDs to filter (default: empty list)", required = false) List<Long> ids, + @ToolParam(description = "Monitor type, e.g., 'linux' (default: null)", required = false) String app, + @ToolParam(description = "Monitor status (0: no monitor, 1: usable, 2: disabled, 9: all) (default: null)", required = false) Byte status, + @ToolParam(description = "Fuzzy search for host or name (default: null)", required = false) String search, + @ToolParam(description = "Monitor labels, e.g., 'env:prod,instance:22' (default: null)", required = false) String labels, + @ToolParam(description = "Sort field, e.g., 'name' (default: gmtCreate)", required = false) String sort, + @ToolParam(description = "Sort order, 'asc' or 'desc' (default: desc)", required = false) String order, + @ToolParam(description = "Page index (default: 0)", required = false) Integer pageIndex, + @ToolParam(description = "Page size (default: 8)", required = false) Integer pageSize, + ToolContext context) { + try { + Page<Monitor> result = monitorServiceAdapter.getMonitors(ids, app, search, status, sort, order, pageIndex, pageSize, labels); + log.debug("MonitorServiceAdapter.getMonitors result: {}", result); + return result.getContent().stream().map(Monitor::getName).toList().toString(); + } catch (Exception e) { + return "error is" + e.getMessage(); + } + } + + @Override + @Tool(name = "add_monitor", description = "Add a new monitor") + public String addMonitor(@ToolParam(description = "Name of the monitor") String name, ToolContext context) { + log.debug("Adding monitor with name: {}", name); + SubjectSum subjectSum = McpContextHolder.getSubject(); + log.debug("Current subject in tool: {}", subjectSum); + return "Monitor added: " + name; + } + +} diff --git a/hertzbeat-manager/pom.xml b/hertzbeat-manager/pom.xml index b9065fdc56..6d69f83817 100644 --- a/hertzbeat-manager/pom.xml +++ b/hertzbeat-manager/pom.xml @@ -210,6 +210,11 @@ <groupId>org.apache.arrow</groupId> <artifactId>arrow-memory-netty</artifactId> </dependency> + <dependency> + <groupId>org.apache.hertzbeat</groupId> + <artifactId>hertzbeat-ai-agent</artifactId> + <version>${project.version}</version> + </dependency> <dependency> <groupId>io.micrometer</groupId> <artifactId>micrometer-registry-prometheus</artifactId> diff --git a/hertzbeat-manager/src/main/java/org/apache/hertzbeat/manager/Manager.java b/hertzbeat-manager/src/main/java/org/apache/hertzbeat/manager/Manager.java index 94584238e5..addb281ce7 100644 --- a/hertzbeat-manager/src/main/java/org/apache/hertzbeat/manager/Manager.java +++ b/hertzbeat-manager/src/main/java/org/apache/hertzbeat/manager/Manager.java @@ -52,4 +52,4 @@ public class Manager { public void init() { System.setProperty("jdk.jndi.object.factoriesFilter", "!com.zaxxer.hikari.HikariJNDIFactory"); } -} +} \ No newline at end of file diff --git a/hertzbeat-manager/src/main/resources/application.yml b/hertzbeat-manager/src/main/resources/application.yml index 9965183bd5..2666eec9a6 100644 --- a/hertzbeat-manager/src/main/resources/application.yml +++ b/hertzbeat-manager/src/main/resources/application.yml @@ -19,6 +19,33 @@ spring: name: ${HOSTNAME:@hertzbeat@}${PID} profiles: active: prod + ai: + mcp: + server: + enabled: true + stdio: false + name: sse-mcp-server + version: 1.0.0 + resource-change-notification: true + tool-change-notification: true + prompt-change-notification: true + sse-endpoint: /api/sse + sse-message-endpoint: /api/mcp/message + type: SYNC + capabilities: + tool: true + resource: true + prompt: true + completion: true + chat: + client: + enabled: false + openai: + api-key: OPENAI_API_KEY + chat: + options: + model: gpt-4.1-nano-2025-04-14 + mvc: static-path-pattern: /** jackson: @@ -38,6 +65,7 @@ spring: max-file-size: 100MB max-request-size: 100MB + management: health: mail: diff --git a/hertzbeat-manager/src/main/resources/sureness.yml b/hertzbeat-manager/src/main/resources/sureness.yml index d846c6a616..39a27970a0 100644 --- a/hertzbeat-manager/src/main/resources/sureness.yml +++ b/hertzbeat-manager/src/main/resources/sureness.yml @@ -66,7 +66,8 @@ resourceRole: - /api/bulletin/**===post===[admin,user] - /api/bulletin/**===put===[admin,user] - /api/bulletin/**===delete===[admin] - + - /api/sse/**===get===[admin,user] + - /api/sse/**===post===[admin,user] # config the resource restful api that need bypass auth protection # rule: api===method # eg: /api/v1/source3===get means /api/v1/source3===get can be access by anyone, no need auth. diff --git a/home/docs/help/mcp_sse_server.md b/home/docs/help/mcp_sse_server.md new file mode 100644 index 0000000000..45cfe47272 --- /dev/null +++ b/home/docs/help/mcp_sse_server.md @@ -0,0 +1,71 @@ +--- +id: mcp_sse_server +title: MCP SSE Server +sidebar_label: MCP SSE Server +keywords: [MCP, SSE, streaming, server] +--- + +This page explains how connect to the HertzBeat MCP SSE server. The MCP server auto starts on the default port 1157 when you start the HertzBeat server. + +### Overview + +- Provides a Server‑Sent Events (SSE) stream for tool calling. +- Intended for MCP integrations and clients that consume streaming events. + +### Connect to the MCP server + +Make sure that hertzbeat server is up and running. If you are using any other port than 1157, replace the following accordingly +- URL: `http://localhost:1157/api/sse` + +### Authentication + +You must authenticate each request using one of the following methods: + +- JWT bearer token + + - Header: `Authorization: Bearer <your-jwt-token>` + +- Basic authentication + - Header: `Authorization: Basic <base64(username:password)>` + +### Cursor MCP configuration + +Create or edit `.cursor/mcp.json` in your home directory or project root. + +Basic auth: + +```json +{ + "Hertzbeat-MCP": { + "url": "http://localhost:1157/api/sse", + "headers": { + "Authorization": "Basic <base64(username:password)>" + } + } +} +``` + +JWT bearer: + +```json +{ + "Hertzbeat-MCP": { + "url": "http://localhost:1157/api/sse", + "headers": { + "Authorization": "Bearer <your-jwt-token>" + } + } +} +``` + +After saving, reload MCP in Cursor or restart the editor. + +### Tools available + +- list_monitors: Returns the list of names of all configured monitors. + +More tools are coming soon to expand management and query capabilities. + +### Notes + +- If the connection drops, reconnect using the same headers. diff --git a/home/sidebars.json b/home/sidebars.json index ebd4e7efec..c06acf8560 100755 --- a/home/sidebars.json +++ b/home/sidebars.json @@ -273,6 +273,7 @@ "help/plugin", "help/time_expression", "help/grafana_dashboard", + "help/mcp_sse_server", "help/collector", "help/ai_config", "help/issue" diff --git a/pom.xml b/pom.xml index 359e290892..80d84e87cf 100644 --- a/pom.xml +++ b/pom.xml @@ -92,6 +92,8 @@ <module>hertzbeat-e2e</module> <module>hertzbeat-base</module> <module>hertzbeat-mcp</module> + <module>hertzbeat-ai-agent</module> + </modules> <properties> --------------------------------------------------------------------- To unsubscribe, e-mail: notifications-unsubscr...@hertzbeat.apache.org For additional commands, e-mail: notifications-h...@hertzbeat.apache.org