From c88c232889499507b1cb1bb199c120f6b7c3d990 Mon Sep 17 00:00:00 2001 From: Tomsun28 Date: Mon, 8 Dec 2025 22:12:35 +0800 Subject: [PATCH] feat: support mcp streamable http protocol and upgrade spring ai version (#3890) Signed-off-by: tomsun28 Co-authored-by: shown --- .markdownlint-cli2.jsonc | 2 +- hertzbeat-ai/pom.xml | 2 +- .../ai/config/CustomSseServerTransport.java | 246 ------------- .../ai/service/McpServerService.java | 23 -- .../impl/ChatClientProviderServiceImpl.java | 4 +- .../ai/service/impl/McpServerServiceImpl.java | 38 -- .../hertzbeat/ai/tools/MonitorTools.java | 28 +- .../ai/tools/impl/AlertDefineToolsImpl.java | 12 +- .../ai/tools/impl/AlertToolsImpl.java | 4 +- .../ai/tools/impl/MetricsToolsImpl.java | 6 +- .../ai/tools/impl/MonitorToolsImpl.java | 334 +++++++++--------- .../hertzbeat/common/util/IpDomainUtil.java | 26 +- .../manager/pojo/dto/MonitorDto.java | 16 +- .../service/impl/MonitorServiceImpl.java | 3 +- .../src/main/resources/application.yml | 15 +- .../src/main/resources/sureness.yml | 21 +- home/docs/community/become_committer.md | 2 +- home/docs/community/become_pmc_member.md | 16 +- home/docs/community/how-to-verify.md | 31 +- home/docs/community/maturity.md | 2 +- home/docs/community/new_committer_process.md | 28 +- home/docs/community/new_pmc_member_process.md | 104 +++--- home/docs/help/mcp_server.md | 74 +++- .../current/community/become_committer.md | 2 +- .../current/community/become_pmc_member.md | 8 +- .../current/community/how-to-verify.md | 31 +- .../community/new_committer_process.md | 30 +- .../community/new_pmc_member_process.md | 78 ++-- .../current/help/mcp_server.md | 82 ++++- script/application.yml | 15 +- .../conf/application.yml | 15 +- .../hertzbeat-mysql-iotdb/conf/sureness.yml | 21 +- .../conf/application.yml | 15 +- .../conf/sureness.yml | 21 +- .../conf/application.yml | 15 +- .../conf/sureness.yml | 21 +- .../conf/application.yml | 15 +- .../conf/sureness.yml | 21 +- .../conf/application.yml | 15 +- .../conf/sureness.yml | 21 +- script/helm/hertzbeat-helm-chart | 2 +- script/sureness.yml | 21 +- 42 files changed, 578 insertions(+), 908 deletions(-) delete mode 100644 hertzbeat-ai/src/main/java/org/apache/hertzbeat/ai/config/CustomSseServerTransport.java diff --git a/.markdownlint-cli2.jsonc b/.markdownlint-cli2.jsonc index 97b4b771de..2ecdd1aea9 100644 --- a/.markdownlint-cli2.jsonc +++ b/.markdownlint-cli2.jsonc @@ -31,7 +31,7 @@ "MD025": true, "MD029": true, "MD033": { - "allowed_elements": ["table", "tr", "td", "a", "img", "sub", "b", "br", "img", "tbody", "mark", "font"] + "allowed_elements": ["table", "tr", "td", "a", "img", "sub", "b", "br", "img", "tbody", "mark", "font", "Tabs", "TabItem"] }, "MD036": false, "MD040": true, diff --git a/hertzbeat-ai/pom.xml b/hertzbeat-ai/pom.xml index de91bf7fa9..d026a98b3b 100644 --- a/hertzbeat-ai/pom.xml +++ b/hertzbeat-ai/pom.xml @@ -26,7 +26,7 @@ hertzbeat-ai ${hertzbeat.version} - 1.0.3 + 1.1.1 17 diff --git a/hertzbeat-ai/src/main/java/org/apache/hertzbeat/ai/config/CustomSseServerTransport.java b/hertzbeat-ai/src/main/java/org/apache/hertzbeat/ai/config/CustomSseServerTransport.java deleted file mode 100644 index 825985e86a..0000000000 --- a/hertzbeat-ai/src/main/java/org/apache/hertzbeat/ai/config/CustomSseServerTransport.java +++ /dev/null @@ -1,246 +0,0 @@ -/* - * 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.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 routerFunction; - @Setter - private McpServerSession.Factory sessionFactory; - private final Map sessionRequest = new HashMap<>(); - private final ConcurrentHashMap 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 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 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 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 unmarshalFrom(Object data, TypeReference typeRef) { - return (T) CustomSseServerTransport.this.objectMapper.convertValue(data, typeRef); - } - - public Mono 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/src/main/java/org/apache/hertzbeat/ai/service/McpServerService.java b/hertzbeat-ai/src/main/java/org/apache/hertzbeat/ai/service/McpServerService.java index 6b518413d2..695a26ae03 100644 --- a/hertzbeat-ai/src/main/java/org/apache/hertzbeat/ai/service/McpServerService.java +++ b/hertzbeat-ai/src/main/java/org/apache/hertzbeat/ai/service/McpServerService.java @@ -18,12 +18,7 @@ package org.apache.hertzbeat.ai.service; -import com.fasterxml.jackson.databind.ObjectMapper; -import org.apache.hertzbeat.ai.config.CustomSseServerTransport; -import org.springframework.ai.mcp.server.autoconfigure.McpServerProperties; import org.springframework.ai.tool.ToolCallbackProvider; -import org.springframework.web.servlet.function.RouterFunction; -import org.springframework.web.servlet.function.ServerResponse; /** * Service interface for MCP server operations. @@ -35,22 +30,4 @@ public interface McpServerService { * @return ToolCallbackProvider with all HertzBeat monitoring tools */ ToolCallbackProvider hertzbeatTools(); - - /** - * 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 - */ - CustomSseServerTransport webMvcSseServerTransportProvider( - ObjectMapper objectMapper, - McpServerProperties serverProperties - ); - - /** - * Provides the MCP server router function for web MVC - * @param transport Custom SSE server transport - * @return RouterFunction for handling MCP server requests - */ - RouterFunction mvcMcpRouterFunction(CustomSseServerTransport transport); } diff --git a/hertzbeat-ai/src/main/java/org/apache/hertzbeat/ai/service/impl/ChatClientProviderServiceImpl.java b/hertzbeat-ai/src/main/java/org/apache/hertzbeat/ai/service/impl/ChatClientProviderServiceImpl.java index 38e49c3a35..293edf11a2 100644 --- a/hertzbeat-ai/src/main/java/org/apache/hertzbeat/ai/service/impl/ChatClientProviderServiceImpl.java +++ b/hertzbeat-ai/src/main/java/org/apache/hertzbeat/ai/service/impl/ChatClientProviderServiceImpl.java @@ -34,7 +34,6 @@ import org.springframework.ai.chat.messages.Message; import org.springframework.ai.chat.messages.UserMessage; import org.springframework.ai.tool.ToolCallbackProvider; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.ApplicationContext; import reactor.core.publisher.Flux; @@ -53,8 +52,7 @@ public class ChatClientProviderServiceImpl implements ChatClientProviderService private final ApplicationContext applicationContext; private final GeneralConfigDao generalConfigDao; - - @Qualifier("hertzbeatTools") + @Autowired private ToolCallbackProvider toolCallbackProvider; diff --git a/hertzbeat-ai/src/main/java/org/apache/hertzbeat/ai/service/impl/McpServerServiceImpl.java b/hertzbeat-ai/src/main/java/org/apache/hertzbeat/ai/service/impl/McpServerServiceImpl.java index 6af80b1b12..cdb2418397 100644 --- a/hertzbeat-ai/src/main/java/org/apache/hertzbeat/ai/service/impl/McpServerServiceImpl.java +++ b/hertzbeat-ai/src/main/java/org/apache/hertzbeat/ai/service/impl/McpServerServiceImpl.java @@ -18,23 +18,17 @@ package org.apache.hertzbeat.ai.service.impl; -import org.apache.hertzbeat.ai.config.CustomSseServerTransport; import org.apache.hertzbeat.ai.service.McpServerService; import org.apache.hertzbeat.ai.tools.AlertDefineTools; import org.apache.hertzbeat.ai.tools.AlertTools; import org.apache.hertzbeat.ai.tools.MetricsTools; import org.apache.hertzbeat.ai.tools.MonitorTools; -import org.springframework.ai.mcp.server.autoconfigure.McpServerProperties; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Primary; import org.springframework.stereotype.Service; 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 com.fasterxml.jackson.databind.ObjectMapper; -import org.springframework.web.servlet.function.RouterFunction; -import org.springframework.web.servlet.function.ServerResponse; /** * Implementation of the McpServerService interface. @@ -56,36 +50,4 @@ public class McpServerServiceImpl implements McpServerService { public ToolCallbackProvider hertzbeatTools() { return MethodToolCallbackProvider.builder().toolObjects(monitorTools, alertTools, alertDefineTools, metricsTools).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 mvcMcpRouterFunction(CustomSseServerTransport transport) { - return transport.getRouterFunction(); - } } diff --git a/hertzbeat-ai/src/main/java/org/apache/hertzbeat/ai/tools/MonitorTools.java b/hertzbeat-ai/src/main/java/org/apache/hertzbeat/ai/tools/MonitorTools.java index 95d6cfe676..24a32313de 100644 --- a/hertzbeat-ai/src/main/java/org/apache/hertzbeat/ai/tools/MonitorTools.java +++ b/hertzbeat-ai/src/main/java/org/apache/hertzbeat/ai/tools/MonitorTools.java @@ -27,35 +27,25 @@ public interface MonitorTools { /** * Add a new monitor with comprehensive configuration - * + * * @param name Monitor name * @param app Monitor type/application (e.g., 'linux', 'mysql', 'http') - * @param host Target host (IP address or domain name) - * @param port Target port (optional, depends on monitor type) * @param intervals Collection interval in seconds (default: 600) - * @param username Username for authentication (optional) - * @param password Password for authentication (optional) - * @param database Database name (for database monitors) - * @param additionalParams Additional app-specific parameters as JSON string (optional) + * @param params Monitor-specific parameters as JSON string (e.g., host, port, username, password, etc.) * @param description Monitor description (optional) * @return Result message with monitor ID if successful */ String addMonitor( - String name, - String app, - String host, - Integer port, + String name, + String app, Integer intervals, - String username, - String password, - String database, - String additionalParams, + String params, String description ); - + /** * List all available monitor types that can be added - * + * * @param language Language code for localized names (e.g., 'en-US', 'zh-CN') * @return Formatted string list of available monitor types with descriptions */ @@ -89,9 +79,9 @@ public interface MonitorTools { /** * Get parameter definitions required for a specific monitor type - * + * * @param app Monitor type/application name (e.g., 'linux', 'mysql', 'redis') * @return Formatted string with parameter definitions including field names, types, and requirements */ - String getMonitorAdditionalParams(String app); + String getMonitorParams(String app); } diff --git a/hertzbeat-ai/src/main/java/org/apache/hertzbeat/ai/tools/impl/AlertDefineToolsImpl.java b/hertzbeat-ai/src/main/java/org/apache/hertzbeat/ai/tools/impl/AlertDefineToolsImpl.java index 726617d815..561de4cb19 100644 --- a/hertzbeat-ai/src/main/java/org/apache/hertzbeat/ai/tools/impl/AlertDefineToolsImpl.java +++ b/hertzbeat-ai/src/main/java/org/apache/hertzbeat/ai/tools/impl/AlertDefineToolsImpl.java @@ -54,7 +54,7 @@ public class AlertDefineToolsImpl implements AlertDefineTools { @Override @Tool(name = "create_alert_rule", description = """ - ALERT RULE means when to alert a user + HertzBeat: ALERT RULE means when to alert a user THESE ARE ALERT RULES WITH THRESHOLD VALUES. USERS CAN SPECIFY THE THRESHOLD VALUES FOR EXAMPLE, IF THE USER SAYS "ALERT ME WHEN MY COST EXCEEDS 700, THE EXPRESSION SHOULD BE 'cost > 700' NOT 'cost < 700'. APPLY THE SAME LOGIC FOR LESS THAN OPERATOR. @@ -281,7 +281,7 @@ public class AlertDefineToolsImpl implements AlertDefineTools { @Override @Tool(name = "list_alert_rules", description = """ - List existing alert rules with filtering options. + HertzBeat: List existing alert rules with filtering options. Shows configured thresholds and alert definitions. """) public String listAlertRules( @@ -343,7 +343,7 @@ public class AlertDefineToolsImpl implements AlertDefineTools { @Override @Tool(name = "toggle_alert_rule", description = """ - Enable or disable an alert rule. + HertzBeat: Enable or disable an alert rule. Allows activating or deactivating threshold monitoring. """) public String toggleAlertRule( @@ -382,7 +382,7 @@ public class AlertDefineToolsImpl implements AlertDefineTools { @Override @Tool(name = "get_alert_rule_details", description = """ - Get detailed information about a specific alert rule. + HertzBeat: Get detailed information about a specific alert rule. Shows complete threshold configuration and rule settings. """) public String getAlertRuleDetails( @@ -441,7 +441,7 @@ public class AlertDefineToolsImpl implements AlertDefineTools { @Override @Tool(name = "get_apps_metrics_hierarchy", description = """ - Get the hierarchical structure of all available apps and their metrics for alert rule creation. + HertzBeat: Get the hierarchical structure of all available apps and their metrics for alert rule creation. This tool provides the exact app name, metric name and corresponding param names according to each metric. Returns structured JSON data showing the complete hierarchy with field parameters for alert expressions. @@ -494,7 +494,7 @@ public class AlertDefineToolsImpl implements AlertDefineTools { @Override @Tool(name = "bind_monitors_to_alert_rule", description = """ - Bind monitors to an alert rule. + HertzBeat: Bind monitors to an alert rule. Call this tool if users want to bind specific monitors to their alert rule. Get the right monitor ids for a particular app using the query_monitors tool. Get the alert rule ID from the create_alert_rule tool output OR use the list_alert_rules tool with app_name search filter, if the output of create_alert_rule is not applicable. diff --git a/hertzbeat-ai/src/main/java/org/apache/hertzbeat/ai/tools/impl/AlertToolsImpl.java b/hertzbeat-ai/src/main/java/org/apache/hertzbeat/ai/tools/impl/AlertToolsImpl.java index ad1f978828..d90de29b28 100644 --- a/hertzbeat-ai/src/main/java/org/apache/hertzbeat/ai/tools/impl/AlertToolsImpl.java +++ b/hertzbeat-ai/src/main/java/org/apache/hertzbeat/ai/tools/impl/AlertToolsImpl.java @@ -45,7 +45,7 @@ public class AlertToolsImpl implements AlertTools { @Override @Tool(name = "query_alerts", description = """ - Query alerts with comprehensive filtering and pagination options. + HertzBeat: Query alerts with comprehensive filtering and pagination options. ALERT TYPES: - Pass alertType='single' for individual alert instances @@ -196,7 +196,7 @@ public class AlertToolsImpl implements AlertTools { @Override @Tool(name = "get_alerts_summary", description = """ - Get alerts summary statistics including total counts, status distribution, and recent trends. + HertzBeat: Get alerts summary statistics including total counts, status distribution, and recent trends. Returns comprehensive overview of the current alerting status across all monitors. """) public String getAlertsSummary() { diff --git a/hertzbeat-ai/src/main/java/org/apache/hertzbeat/ai/tools/impl/MetricsToolsImpl.java b/hertzbeat-ai/src/main/java/org/apache/hertzbeat/ai/tools/impl/MetricsToolsImpl.java index 439a85d801..1cd0f85827 100644 --- a/hertzbeat-ai/src/main/java/org/apache/hertzbeat/ai/tools/impl/MetricsToolsImpl.java +++ b/hertzbeat-ai/src/main/java/org/apache/hertzbeat/ai/tools/impl/MetricsToolsImpl.java @@ -47,7 +47,7 @@ public class MetricsToolsImpl implements MetricsTools { @Override @Tool(name = "query_realtime_metrics", description = """ - Get the supported monitor types/names from the list_monitor_types tool, make sure to use right name in the next call + HertzBeat: Get the supported monitor types/names from the list_monitor_types tool, make sure to use right name in the next call Use the query_monitors tool to find monitor IDs in case the user does not tell the id explicitly. You might have to use this multiple times based on the user's query Get real-time metrics data for a specific monitor. Returns current metrics values including CPU, memory, disk usage, etc. @@ -144,7 +144,7 @@ public class MetricsToolsImpl implements MetricsTools { @Override @Tool(name = "get_historical_metrics", description = """ - Get historical metrics data for analysis and trending. + HertzBeat: Get historical metrics data for analysis and trending. Returns time-series data for specified metrics over a time range. Use the query_monitors tool to find the correct monitor IDs/ name or type for the monitor(s) user asked the metrics for Pass that name into the get_apps_metrics_hierarchy tool to get the metrics hierarchy i.e metrics and the field paramater @@ -226,7 +226,7 @@ public class MetricsToolsImpl implements MetricsTools { @Override @Tool(name = "get_warehouse_status", description = """ - Check the status of the metrics storage warehouse system. + HertzBeat: Check the status of the metrics storage warehouse system. Returns whether the metrics storage is operational and accessible. """) public String getWarehouseStatus() { diff --git a/hertzbeat-ai/src/main/java/org/apache/hertzbeat/ai/tools/impl/MonitorToolsImpl.java b/hertzbeat-ai/src/main/java/org/apache/hertzbeat/ai/tools/impl/MonitorToolsImpl.java index 5da5e53269..0c2ec738b4 100644 --- a/hertzbeat-ai/src/main/java/org/apache/hertzbeat/ai/tools/impl/MonitorToolsImpl.java +++ b/hertzbeat-ai/src/main/java/org/apache/hertzbeat/ai/tools/impl/MonitorToolsImpl.java @@ -20,6 +20,7 @@ package org.apache.hertzbeat.ai.tools.impl; import com.usthe.sureness.subject.SubjectSum; import lombok.extern.slf4j.Slf4j; import org.apache.hertzbeat.ai.config.McpContextHolder; +import org.apache.hertzbeat.manager.pojo.dto.MonitorDto; import org.apache.hertzbeat.manager.service.MonitorService; import org.apache.hertzbeat.manager.service.AppService; import org.apache.hertzbeat.ai.utils.UtilityClass; @@ -36,7 +37,6 @@ import org.apache.hertzbeat.common.entity.manager.ParamDefine; import java.util.ArrayList; import java.util.List; import java.util.Map; -import java.util.Objects; /** * Implementation of Monitoring Tools functionality @@ -57,48 +57,48 @@ public class MonitorToolsImpl implements MonitorTools { * Returns detailed monitor information including ID, name, type, host, and status. */ @Override - @Tool(name = "query_monitors", description = """ - Query Existing/configured monitors in HertzBeat. + @Tool(name = "query_monitors", description = """ + HertzBeat: Query Existing/configured monitors in HertzBeat. This tool retrieves monitors based on various filters and parameters. Comprehensive monitor querying with flexible filtering, pagination, and specialized views. - + MONITOR STATUSES: - status=1: Online/Active monitors (healthy, responding normally) - status=2: Offline monitors (not responding, connection failed) - status=3: Unreachable monitors (network/connectivity issues) - status=0: Paused monitors (manually disabled/suspended) - status=9 or null: All monitors regardless of status (default) - + COMMON USE CASES & PARAMETER COMBINATIONS: - + 1. BASIC MONITOR LISTING: - Default: No parameters (shows all monitors, 8 per page) - By type: app='linux' (show only Linux monitors) - Search: search='web' (find monitors with 'web' in name/host) - + 2. STATUS-BASED QUERIES: - Healthy monitors: status=1, pageSize=50 - Problem monitors: status=2 or status=3, pageSize=50 - Offline monitors only: status=2 - Unreachable monitors only: status=3 - Paused monitors: status=0 - + 3. MONITORING HEALTH OVERVIEW: - All statuses with statistics: status=9, includeStats=true, pageSize=100 - Unhealthy monitors: Pass both status=2 AND status=3 (make 2 separate calls) - + 4. ADVANCED FILTERING: - Specific monitor types: app='mysql', status=1 (healthy MySQL monitors) - Label-based: labels='env:prod,critical:true' - Host search: search='192.168' (find by IP pattern) - Monitor IDs: ids=[1,2,3] (specific monitors by ID) - + 5. SORTING & PAGINATION: - Recently updated: sort='gmtUpdate', order='desc' - Alphabetical: sort='name', order='asc' - By creation: sort='gmtCreate', order='desc' (newest first) - Large datasets: pageSize=50-100 for bulk operations - + RESPONSE FORMAT: - includeStats=true: Adds status distribution summary at top - Default: Simple list with ID, name, type, host, status @@ -126,18 +126,18 @@ public class MonitorToolsImpl implements MonitorTools { if (includeStats == null) { includeStats = false; } - + SubjectSum subjectSum = McpContextHolder.getSubject(); log.debug("Current security subject: {}", subjectSum); Page result = monitorService.getMonitors( ids, app, search, status, sort, order, pageIndex, pageSize, labels); log.debug("MonitorService.getMonitors result: {}", result); - + StringBuilder response = new StringBuilder(); response.append("MONITOR QUERY RESULTS\n"); response.append("====================\n\n"); - + // Include statistics if requested if (includeStats) { // Get status distribution by calling with different status values @@ -145,13 +145,13 @@ public class MonitorToolsImpl implements MonitorTools { long offlineCount = monitorService.getMonitors(null, app, search, (byte) 2, null, null, 0, 1000, labels).getTotalElements(); long unreachableCount = monitorService.getMonitors(null, app, search, (byte) 3, null, null, 0, 1000, labels).getTotalElements(); long pausedCount = monitorService.getMonitors(null, app, search, (byte) 0, null, null, 0, 1000, labels).getTotalElements(); - + response.append("STATUS OVERVIEW:\n"); response.append("- Online: ").append(onlineCount).append("\n"); - response.append("- Offline: ").append(offlineCount).append("\n"); + response.append("- Offline: ").append(offlineCount).append("\n"); response.append("- Unreachable: ").append(unreachableCount).append("\n"); response.append("- Paused: ").append(pausedCount).append("\n"); - + long total = onlineCount + offlineCount + unreachableCount + pausedCount; if (total > 0) { double healthPercentage = (onlineCount * 100.0) / total; @@ -159,34 +159,34 @@ public class MonitorToolsImpl implements MonitorTools { } response.append("\n"); } - + response.append("Query Results: ").append(result.getContent().size()) .append(" monitors (Total: ").append(result.getTotalElements()).append(")\n"); - + if (result.getTotalPages() > 1) { response.append("Page ").append(pageIndex + 1).append(" of ").append(result.getTotalPages()).append("\n"); } response.append("\n"); - + for (Monitor monitor : result.getContent()) { response.append("ID: ").append(monitor.getId()) .append(" | Name: ").append(monitor.getName()) .append(" | Type: ").append(monitor.getApp()) .append(" | Instance: ").append(monitor.getInstance()) .append(" | Status: ").append(UtilityClass.getStatusText(monitor.getStatus())); - + // Add creation date for better context if (monitor.getGmtCreate() != null) { response.append(" | Created: ").append(monitor.getGmtCreate().toString(), 0, 10); } response.append("\n"); } - + if (result.getContent().isEmpty()) { response.append("No monitors found matching the specified criteria.\n"); response.append("Try adjusting your filters or search terms."); } - + return response.toString(); } catch (Exception e) { return "Error retrieving monitors: " + e.getMessage(); @@ -196,11 +196,11 @@ public class MonitorToolsImpl implements MonitorTools { @Override @Tool(name = "add_monitor", description = """ - Add a new monitoring target to HertzBeat with comprehensive configuration. + HertzBeat: Add a new monitoring target to HertzBeat with comprehensive configuration. This tool dynamically handles different parameter requirements for each monitor type. - + This tool creates monitors with proper app-specific parameters. - + ********* VERY IMPORTANT: ALWAYS use get_monitor_additional_params to check the additional required parameters for the chosen type before adding a monitor or even mentioning it. @@ -208,41 +208,36 @@ public class MonitorToolsImpl implements MonitorTools { Use the information obtained from this to query user for parameters. If the User has not given any parameters, ask them to provide the necessary parameters, until all the necessary parameters are provided. ********** - + Examples of natural language requests this tool handles: - "Monitor website example.com with HTTPS on port 443" - "Add MySQL monitoring for database server at 192.168.1.10 with user admin" - "Monitor Linux server health on host server.company.com via SSH" - "Set up Redis monitoring on localhost port 6379 with password" - - PARAMETER MAPPING: The tool intelligently maps common parameters: - - host: Target server/domain - - port: Service port (auto-detected if not specified) - - username: Authentication username - - password: Authentication password - - database: Database name (for DB monitors) - - additionalParams: JSON string for app-specific parameters (to be obtained from get_monitor_param_defines) - - ADDITIONAL PARAMETERS EXAMPLES: - - Website: {"uri":"/api/health", "ssl":"true", "method":"POST"} - - Linux: {"privateKey":"ssh-key-content", "script":"custom-script"} - - Database: {"url":"jdbc:mysql://custom", "timeout":"10000"} + + PARAMETER MAPPING: Use the 'params' parameter to pass all monitor-specific configuration. + The params should be a JSON string containing key-value pairs for the monitor type. + Use get_monitor_additional_params tool to see what parameters are required for each monitor type. + + PARAMS EXAMPLES: + - Website: {"host":"example.com", "port":"443", "uri":"/api/health", "ssl":"true", "method":"GET"} + - Linux: {"host":"192.168.1.10", "port":"22", "username":"root", "password":"xxx"} + - MySQL: {"host":"db.server.com", "port":"3306", "username":"admin", "password":"xxx", "database":"mydb"} + - Redis: {"host":"redis.server.com", "port":"6379", "password":"xxx"} """) public String addMonitor( @ToolParam(description = "Monitor name (required)", required = true) String name, @ToolParam(description = "Monitor type: website, mysql, postgresql, redis, linux, windows, etc.", required = true) String app, - @ToolParam(description = "Target host: IP address or domain name", required = true) String host, - @ToolParam(description = "Target port (optional, auto-detected if not specified)", required = false) Integer port, @ToolParam(description = "Collection interval in seconds (default: 600)", required = false) Integer intervals, - @ToolParam(description = "Username for authentication (optional)", required = false) String username, - @ToolParam(description = "Password for authentication (optional)", required = false) String password, - @ToolParam(description = "Database name (for database monitors)", required = false) String database, - @ToolParam(description = "Additional app-specific parameters as JSON: {\"uri\":\"/api\", \"ssl\":\"true\", \"method\":\"POST\"}", required = false) String additionalParams, + @ToolParam(description = "Monitor-specific parameters as JSON string. " + + "Use get_monitor_additional_params to see required fields. " + + "Example: {\"host\":\"192.168.1.1\", \"port\":\"22\", \"username\":\"root\"}", + required = true) String params, @ToolParam(description = "Monitor description (optional)", required = false) String description) { - + try { - log.info("Adding monitor: name={}, app={}, host={}", name, app, host); - + log.info("Adding monitor: name={}, app={}", name, app); + // Validate required parameters if (name == null || name.trim().isEmpty()) { return "Error: Monitor name is required"; @@ -250,16 +245,29 @@ public class MonitorToolsImpl implements MonitorTools { if (app == null || app.trim().isEmpty()) { return "Error: Monitor type/application is required"; } - if (host == null || host.trim().isEmpty()) { - return "Error: Host is required"; + if (params == null || params.trim().isEmpty()) { + return "Error: Monitor params is required. Use get_monitor_additional_params to see required fields for this monitor type."; } - + // Set defaults if (intervals == null || intervals < 10) { intervals = 600; } - String instance = Objects.nonNull(port) ? host.trim() + ":" + port : host.trim(); + // Parse params to extract host and port for instance + List paramList = parseParams(params); + String host = paramList.stream() + .filter(p -> "host".equals(p.getField())) + .map(Param::getParamValue) + .findFirst() + .orElse(""); + String port = paramList.stream() + .filter(p -> "port".equals(p.getField())) + .map(Param::getParamValue) + .findFirst() + .orElse(null); + + String instance = (port != null && !port.isEmpty()) ? host.trim() + ":" + port : host.trim(); // Create Monitor entity Monitor monitor = Monitor.builder() @@ -271,109 +279,119 @@ public class MonitorToolsImpl implements MonitorTools { .type((byte) 0) .description(description != null ? description.trim() : "") .build(); - - List params = createBasicParams(host, port, username, password, database, additionalParams); - + // Validate that all required parameters for this monitor type are provided try { - SubjectSum subjectSum = McpContextHolder.getSubject(); - log.debug("Current security subject for getMonitorParamDefines: {}", subjectSum); - - List requiredParams = appService.getAppParamDefines(app.toLowerCase().trim()); - log.info("Checking required parameters for monitor type '{}': {}", app, requiredParams); - List missingParams = new ArrayList<>(); - - for (ParamDefine paramDefine : requiredParams) { - if (paramDefine.isRequired()) { - String fieldName = paramDefine.getField(); - boolean hasParam = params.stream() - .anyMatch(param -> fieldName.equals(param.getField())); - if (!hasParam) { - missingParams.add(fieldName); - } - } + MonitorDto monitorDto = MonitorDto.builder().monitor(monitor).params(paramList).build(); + monitorService.validate(monitorDto, false); + } catch (IllegalArgumentException argumentException) { + if (argumentException.getMessage().contains("required")) { + return String.format("Error: %s. " + + "Or use get_monitor_additional_params tool to see all required parameters.", + argumentException.getMessage()); + } else { + return String.format("Error: %s. ", argumentException.getMessage()); } - - if (!missingParams.isEmpty()) { - return String.format("Error: Missing required parameters for monitor type '%s': %s. " - + "Use get_monitor_additional_params tool to see all required parameters.", - app, String.join(", ", missingParams)); - } - } catch (Exception e) { - log.warn("Could not validate required parameters for monitor type '{}': {}", app, e.getMessage()); } - - // Call monitor service - it handles all the complexity (validation, defaults, app-specific logic) - SubjectSum subjectSum = McpContextHolder.getSubject(); - log.debug("Current security subject for addMonitor: {}", subjectSum); + monitorService.addMonitor(monitor, paramList, null, null); + log.info("Successfully added monitor '{}' with ID: {}", monitor.getName(), monitor.getId()); + return String.format("Successfully added %s monitor '%s' with ID: %d (Instance: %s, Interval: %d seconds)", + app.toUpperCase(), monitor.getName(), monitor.getId(), monitor.getInstance(), monitor.getIntervals()); - monitorService.addMonitor(monitor, params, null, null); - Long monitorId = monitor.getId(); - - log.info("Successfully added monitor '{}' with ID: {}", name, monitorId); - return String.format("Successfully added %s monitor '%s' with ID: %d (Host: %s, Interval: %d seconds)", - app.toUpperCase(), name, monitorId, host, intervals); - } catch (Exception e) { log.error("Failed to add monitor '{}': {}", name, e.getMessage(), e); return "Error adding monitor '" + name + "': " + e.getMessage(); } } - + /** - * Create basic parameter list from user inputs + * Parse params JSON string to list of Param objects */ - private List createBasicParams(String host, Integer port, String username, - String password, String database, String additionalParams) { - List params = new ArrayList<>(); - - // Add host (always required) - params.add(Param.builder().field("host").paramValue(host.trim()).type((byte) 1).build()); - - // Add optional common parameters - if (port != null) { - params.add(Param.builder().field("port").paramValue(port.toString()).type((byte) 0).build()); + private List parseParams(String params) { + List paramList = new ArrayList<>(); + + if (params == null || params.trim().isEmpty()) { + return paramList; } - if (username != null && !username.trim().isEmpty()) { - params.add(Param.builder().field("username").paramValue(username.trim()).type((byte) 1).build()); - } - if (password != null && !password.trim().isEmpty()) { - params.add(Param.builder().field("password").paramValue(password.trim()).type((byte) 2).build()); - } - if (database != null && !database.trim().isEmpty()) { - params.add(Param.builder().field("database").paramValue(database.trim()).type((byte) 1).build()); - } - - // Parse additional parameters if provided - if (additionalParams != null && !additionalParams.trim().isEmpty()) { - try { - String cleaned = additionalParams.trim().replaceAll("[{}]", ""); - String[] pairs = cleaned.split(","); - for (String pair : pairs) { - String[] keyValue = pair.split(":"); - if (keyValue.length == 2) { - String key = keyValue[0].trim().replaceAll("\"", ""); - String value = keyValue[1].trim().replaceAll("\"", ""); - params.add(Param.builder().field(key).paramValue(value).type((byte) 1).build()); - } + + try { + String cleaned = params.trim(); + // Remove outer braces if present + if (cleaned.startsWith("{") && cleaned.endsWith("}")) { + cleaned = cleaned.substring(1, cleaned.length() - 1); + } + + // Split by comma, but handle values that might contain commas within quotes + List pairs = splitJsonPairs(cleaned); + + for (String pair : pairs) { + int colonIndex = pair.indexOf(':'); + if (colonIndex > 0) { + String key = pair.substring(0, colonIndex).trim().replaceAll("\"", ""); + String value = pair.substring(colonIndex + 1).trim().replaceAll("\"", ""); + + // Determine param type based on field name + byte paramType = determineParamType(key); + paramList.add(Param.builder().field(key).paramValue(value).type(paramType).build()); } - } catch (Exception e) { - log.warn("Failed to parse additionalParams: {}", e.getMessage()); + } + } catch (Exception e) { + log.warn("Failed to parse params: {}", e.getMessage()); + } + + return paramList; + } + + /** + * Split JSON key-value pairs, handling quoted values that may contain commas + */ + private List splitJsonPairs(String json) { + List pairs = new ArrayList<>(); + StringBuilder current = new StringBuilder(); + boolean inQuotes = false; + + for (char c : json.toCharArray()) { + if (c == '"') { + inQuotes = !inQuotes; + current.append(c); + } else if (c == ',' && !inQuotes) { + if (current.length() > 0) { + pairs.add(current.toString().trim()); + current = new StringBuilder(); + } + } else { + current.append(c); } } - - return params; + + if (current.length() > 0) { + pairs.add(current.toString().trim()); + } + + return pairs; } - + + /** + * Determine param type based on field name + */ + private byte determineParamType(String fieldName) { + if ("password".equalsIgnoreCase(fieldName) || "privateKey".equalsIgnoreCase(fieldName)) { + return (byte) 2; // Password type + } else if ("port".equalsIgnoreCase(fieldName) || "timeout".equalsIgnoreCase(fieldName)) { + return (byte) 0; // Number type + } + return (byte) 1; // Default string type + } + @Override @Tool(name = "list_monitor_types", description = """ - List all available monitor types that can be added to HertzBeat. + HertzBeat: List all available monitor types that can be added to HertzBeat. This tool shows all supported monitor types with their display names. Use this to see what types of monitors you can create with the add_monitor tool. """) public String listMonitorTypes( @ToolParam(description = "Language code for localized names (en-US, zh-CN, etc.). Default: en-US", required = false) String language) { - + try { log.info("Listing available monitor types for language: {}", language); SubjectSum subjectSum = McpContextHolder.getSubject(); @@ -386,20 +404,20 @@ public class MonitorToolsImpl implements MonitorTools { // Get available monitor types from app service Map monitorTypes = appService.getI18nApps(language); - + if (monitorTypes == null || monitorTypes.isEmpty()) { return "No monitor types are currently available."; } - + // Format the response as a nice list StringBuilder response = new StringBuilder(); response.append("Available Monitor Types (Total: ").append(monitorTypes.size()).append("):\n\n"); - + // Sort monitor types alphabetically by key List> sortedTypes = monitorTypes.entrySet().stream() .sorted(Map.Entry.comparingByKey()) .toList(); - + for (Map.Entry entry : sortedTypes) { String typeKey = entry.getKey(); String displayName = entry.getValue(); @@ -407,28 +425,28 @@ public class MonitorToolsImpl implements MonitorTools { .append(" - ").append(displayName) .append("\n"); } - + response.append("\nTo add a monitor, use the add_monitor tool with one of these types as the 'app' parameter."); log.info("Successfully listed {} monitor types", monitorTypes); return response.toString(); - + } catch (Exception e) { log.error("Failed to list monitor types: {}", e.getMessage(), e); return "Error retrieving monitor types: " + e.getMessage(); } } - + @Override - @Tool(name = "get_monitor_additional_params", description = """ - Get the parameter definitions required for a specific monitor type. + @Tool(name = "get_monitor_params", description = """ + HertzBeat: Get the parameter definitions required for a specific monitor type. This tool shows what parameters are needed when adding a monitor of the specified type, ALWAYS use this before adding a monitor to understand what parameters the user needs to provide. Use the app parameter to specify the monitor type/application name (e.g., 'linux', 'mysql', 'redis') this can be obtained from the list_monitor_types tool. """) - public String getMonitorAdditionalParams( + public String getMonitorParams( @ToolParam(description = "Monitor type/application name (e.g., 'linux', 'mysql', 'redis')", required = true) String app) { - + try { log.info("Getting parameter definitions for monitor type: {}", app); SubjectSum subjectSum = McpContextHolder.getSubject(); @@ -441,62 +459,62 @@ public class MonitorToolsImpl implements MonitorTools { // Get parameter definitions from app service List paramDefines = appService.getAppParamDefines(app.toLowerCase().trim()); - + if (paramDefines == null || paramDefines.isEmpty()) { return String.format("No parameter definitions found for monitor type '%s'. " + "This monitor type may not exist or may not require additional parameters.", app); } - + // Format the response StringBuilder response = new StringBuilder(); - response.append(String.format("Parameter Definitions for Monitor Type '%s' (Total: %d):\n\n", + response.append(String.format("Parameter Definitions for Monitor Type '%s' (Total: %d):\n\n", app, paramDefines.size())); - + for (ParamDefine paramDefine : paramDefines) { response.append("• Field: ").append(paramDefine.getField()).append("\n"); - + // Add display name if available if (paramDefine.getName() != null && !paramDefine.getName().toString().trim().isEmpty()) { response.append(" Name: ").append(paramDefine.getName()).append("\n"); } - + // Add type if (paramDefine.getType() != null && !paramDefine.getType().trim().isEmpty()) { response.append(" Type: ").append(paramDefine.getType()).append("\n"); } - + // Add required status response.append(" Required: ").append(paramDefine.isRequired() ? "Yes" : "No").append("\n"); - + // Add default value if present if (paramDefine.getDefaultValue() != null && !paramDefine.getDefaultValue().trim().isEmpty()) { response.append(" Default: ").append(paramDefine.getDefaultValue()).append("\n"); } - + // Add validation range if present if (paramDefine.getRange() != null && !paramDefine.getRange().trim().isEmpty()) { response.append(" Range: ").append(paramDefine.getRange()).append("\n"); } - + // Add limit if present if (paramDefine.getLimit() != null) { response.append(" Limit: ").append(paramDefine.getLimit()).append("\n"); } - + // Add placeholder text if present if (paramDefine.getPlaceholder() != null && !paramDefine.getPlaceholder().trim().isEmpty()) { response.append(" Placeholder: ").append(paramDefine.getPlaceholder()).append("\n"); } - + response.append("\n"); } - + response.append("To add a monitor of this type, use the add_monitor tool with these parameters.\n"); response.append(String.format("Example: add_monitor(name='my-monitor', app='%s', host='your-host', ...)", app)); - + log.info("Successfully retrieved {} parameter definitions for monitor type: {}", paramDefines.size(), app); return response.toString(); - + } catch (Exception e) { log.error("Failed to get parameter definitions for monitor type '{}': {}", app, e.getMessage(), e); return "Error retrieving parameter definitions for monitor type '" + app + "': " + e.getMessage(); diff --git a/hertzbeat-common/src/main/java/org/apache/hertzbeat/common/util/IpDomainUtil.java b/hertzbeat-common/src/main/java/org/apache/hertzbeat/common/util/IpDomainUtil.java index 8845fcfaf8..e882cbef59 100644 --- a/hertzbeat-common/src/main/java/org/apache/hertzbeat/common/util/IpDomainUtil.java +++ b/hertzbeat-common/src/main/java/org/apache/hertzbeat/common/util/IpDomainUtil.java @@ -25,6 +25,7 @@ import java.util.Enumeration; import java.util.regex.Pattern; import lombok.extern.slf4j.Slf4j; import org.apache.hertzbeat.common.constants.NetworkConstants; +import org.apache.hertzbeat.common.constants.SignConstants; import org.apache.http.conn.util.InetAddressUtils; import org.springframework.util.StringUtils; @@ -33,7 +34,7 @@ import org.springframework.util.StringUtils; */ @Slf4j public final class IpDomainUtil { - + private static final Pattern DOMAIN_PATTERN = Pattern.compile("^[-\\w]+(\\.[-\\w]+)*$"); @@ -81,6 +82,23 @@ public final class IpDomainUtil { return DOMAIN_SCHEMA.matcher(domainIp).matches(); } + /** + * if instance has the port with mark + * @param instance instance ip:port + * @return true if has + */ + public static boolean isHasPortWithMark(String instance) { + if (instance == null || !StringUtils.hasText(instance)) { + return false; + } + String[] parts = instance.split(SignConstants.DOUBLE_MARK); + if (parts.length >= 2) { + String port = parts[parts.length - 1]; + return CommonUtil.isNumeric(port); + } + return false; + } + /** * get localhost IP. * @return ip @@ -106,7 +124,7 @@ public final class IpDomainUtil { } return null; } - + /** * check IP address type. * @param ipDomain ip domain @@ -118,7 +136,7 @@ public final class IpDomainUtil { } return NetworkConstants.IPV4; } - + /** * get current local host name. * @return hostname @@ -126,7 +144,7 @@ public final class IpDomainUtil { public static String getCurrentHostName() { try { InetAddress inetAddress = InetAddress.getLocalHost(); - return inetAddress.getHostName(); + return inetAddress.getHostName(); } catch (UnknownHostException e) { return null; } diff --git a/hertzbeat-manager/src/main/java/org/apache/hertzbeat/manager/pojo/dto/MonitorDto.java b/hertzbeat-manager/src/main/java/org/apache/hertzbeat/manager/pojo/dto/MonitorDto.java index 6832c00c69..b194f85391 100644 --- a/hertzbeat-manager/src/main/java/org/apache/hertzbeat/manager/pojo/dto/MonitorDto.java +++ b/hertzbeat-manager/src/main/java/org/apache/hertzbeat/manager/pojo/dto/MonitorDto.java @@ -24,7 +24,10 @@ import jakarta.validation.Valid; import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotNull; import java.util.List; +import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.Data; +import lombok.NoArgsConstructor; import org.apache.hertzbeat.common.entity.grafana.GrafanaDashboard; import org.apache.hertzbeat.common.entity.manager.Monitor; import org.apache.hertzbeat.common.entity.manager.Param; @@ -34,24 +37,27 @@ import org.apache.hertzbeat.common.entity.manager.Param; */ @Data @Schema(description = "Monitoring information entities") +@Builder +@AllArgsConstructor +@NoArgsConstructor public class MonitorDto { - + @Schema(description = "monitor content", accessMode = READ_WRITE) @NotNull @Valid private Monitor monitor; - + @Schema(description = "monitor params", accessMode = READ_WRITE) @NotEmpty @Valid private List params; - + @Schema(description = "Monitor Metrics", accessMode = READ_ONLY) private List metrics; - + @Schema(description = "pinned collector, default null if system dispatch", accessMode = READ_WRITE) private String collector; - + @Schema(description = "grafana dashboard") private GrafanaDashboard grafanaDashboard; } diff --git a/hertzbeat-manager/src/main/java/org/apache/hertzbeat/manager/service/impl/MonitorServiceImpl.java b/hertzbeat-manager/src/main/java/org/apache/hertzbeat/manager/service/impl/MonitorServiceImpl.java index 07a1522fbf..b42e1ea030 100644 --- a/hertzbeat-manager/src/main/java/org/apache/hertzbeat/manager/service/impl/MonitorServiceImpl.java +++ b/hertzbeat-manager/src/main/java/org/apache/hertzbeat/manager/service/impl/MonitorServiceImpl.java @@ -43,6 +43,7 @@ import org.apache.hertzbeat.common.entity.manager.ParamDefine; import org.apache.hertzbeat.common.entity.message.CollectRep; import org.apache.hertzbeat.common.support.event.MonitorDeletedEvent; +import org.apache.hertzbeat.common.util.IpDomainUtil; import org.apache.hertzbeat.common.util.JexlCheckerUtil; import org.apache.hertzbeat.common.util.SnowFlakeIdGenerator; import org.apache.hertzbeat.grafana.service.DashboardService; @@ -188,7 +189,7 @@ public class MonitorServiceImpl implements MonitorService { String portWithMark = (Objects.isNull(portParam) || !StringUtils.hasText(portParam.getParamValue())) ? "" : SignConstants.DOUBLE_MARK + portParam.getParamValue(); - if (Objects.nonNull(instance)) { + if (!IpDomainUtil.isHasPortWithMark(instance)) { instance = instance + portWithMark; } monitor.setInstance(instance); diff --git a/hertzbeat-startup/src/main/resources/application.yml b/hertzbeat-startup/src/main/resources/application.yml index ea661edf93..c5d00079ed 100644 --- a/hertzbeat-startup/src/main/resources/application.yml +++ b/hertzbeat-startup/src/main/resources/application.yml @@ -24,19 +24,12 @@ spring: server: enabled: true stdio: false - name: sse-mcp-server + protocol: streamable + streamable-http: + mcp-endpoint: /api/mcp + name: hertzbeat-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 mvc: static-path-pattern: /** diff --git a/hertzbeat-startup/src/main/resources/sureness.yml b/hertzbeat-startup/src/main/resources/sureness.yml index b547db30c4..282e93928a 100644 --- a/hertzbeat-startup/src/main/resources/sureness.yml +++ b/hertzbeat-startup/src/main/resources/sureness.yml @@ -66,10 +66,10 @@ 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] + - /api/mcp/**===get===[admin] + - /api/mcp/**===post===[admin] - /api/chat/**===get===[admin,user] - - /api/chat/**===post===[admin,user] + - /api/chat/**===post===[admin] - /api/logs/ingest/**===post===[admin,user] # config the resource restful api that need bypass auth protection @@ -114,7 +114,7 @@ excludedResource: # h2 database - /h2-console/**===* -# account info config +# account info config refer the https://hertzbeat.apache.org/docs/start/account-modify # eg: admin has role [admin,user], password is hertzbeat # eg: tom has role [user], password is hertzbeat # eg: lili has role [guest], plain password is lili, salt is 123, salted password is 1A676730B0C7F54654B0E09184448289 @@ -122,16 +122,3 @@ account: - appId: admin credential: hertzbeat role: [admin] - - appId: tom - credential: hertzbeat - role: [user] - - appId: guest - credential: hertzbeat - role: [guest] - - appId: lili - # credential = MD5(password + salt) - # plain password: hertzbeat - # attention: digest authentication does not support salted encrypted password accounts - credential: 94C6B34E7A199A9F9D4E1F208093B489 - salt: 123 - role: [user] diff --git a/home/docs/community/become_committer.md b/home/docs/community/become_committer.md index fcecdddc3d..939edc29f1 100644 --- a/home/docs/community/become_committer.md +++ b/home/docs/community/become_committer.md @@ -46,7 +46,7 @@ contributing to our community's success. ## Committer requirements -There are no strict rules for becoming a committer or PPMC member. +There are no strict rules for becoming a committer or new PMC member. Candidates for new committers are typically people that are active contributors and community members. Anyway, if the rules can be clarified a little bit, it can somehow clear the doubts in the minds diff --git a/home/docs/community/become_pmc_member.md b/home/docs/community/become_pmc_member.md index c10ccd0ebd..660d189cd4 100644 --- a/home/docs/community/become_pmc_member.md +++ b/home/docs/community/become_pmc_member.md @@ -1,6 +1,6 @@ --- id: 'become_pmc_member' -title: 'Become A PPMC member' +title: 'Become A new PMC member' sidebar_position: 3 --- @@ -21,10 +21,10 @@ See the License for the specific language governing permissions and limitations under the License. --> -## Become A PPMC member of Apache HertzBeat +## Become A new PMC member of Apache HertzBeat Anyone being supportive of the community and working in any of the -CoPDoC areas can become an Apache HertzBeat™ PPMC member. The CoPDoC is an +CoPDoC areas can become an Apache HertzBeat™ new PMC member. The CoPDoC is an acronym from ASF to describe how we recognize your contributions not only by code. @@ -38,16 +38,16 @@ only by code. Apache HertzBeat™ community strives to be meritocratic. Thus, once someone has contributed sufficiently to any area of CoPDoC they can be a -candidate for PPMC membership and at last voted in as a HertzBeat -PMC member. Being an Apache HertzBeat™ PPMC member does not necessarily mean +candidate for new PMC membership and at last voted in as a HertzBeat +PMC member. Being an Apache HertzBeat™ new PMC member does not necessarily mean you must commit code with your commit privilege to the codebase; it means you are committed to the HertzBeat project and are productively contributing to our community's success. -## PPMC member requirements +## new PMC member requirements -There are no strict rules for becoming a committer or PPMC member. -Candidates for new PPMC member are typically people that are active +There are no strict rules for becoming a committer or new PMC member. +Candidates for new PMC member are typically people that are active contributors and community members. Anyway, if the rules can be clarified a little bit, it can somehow clear the doubts in the minds of contributors and make the community more transparent, reasonable, diff --git a/home/docs/community/how-to-verify.md b/home/docs/community/how-to-verify.md index f24fb77bc9..ad5a8e8b3e 100644 --- a/home/docs/community/how-to-verify.md +++ b/home/docs/community/how-to-verify.md @@ -75,9 +75,9 @@ Please decide how far you trust this user to correctly verify other users' keys Your decision? 5 #choose 5 Do you really want to set this key to ultimate trust? (y/N) y #choose y - + gpg> - + ``` ##### 2.2.3 Check the gpg signature @@ -149,26 +149,14 @@ You can refer to this article: [ASF Third Party License Policy](https://apache.o ### 3. Email reply If you initiate a posting vote, you can refer to this response example to reply to the email after verification - + +:::caution Attention When replying to the email, you must bring the information that you have checked by yourself. Simply replying to `+1 approve` is invalid. -When PPMC votes in the hertzbeat community, Please bring the binding suffix to indicate that it has a binding vote for the vote in the hertzbeat community, and it is convenient to count the voting results. - -When IPMC votes in the incubator community. Please bring the binding suffix to indicate that the voting in the incubator community has a binding vote, which is convenient for counting the voting results. - - -:::caution -If you have already voted on , you can take it directly to the incubator community when you reply to the vote, such as: - -```html -//Incubator community voting, only IPMC members have binding binding,PPMC needs to be aware of binding changes -Forward my +1 from dev@listhertzbeatnkis (non-binding) -Copy my +1 from hertzbeat DEV ML (non-binding) -``` - +When new PMC votes in the hertzbeat community, Please bring the binding suffix to indicate that it has a binding vote for the vote in the hertzbeat community, and it is convenient to count the voting results. ::: -Non-PPMC/Non-IPMC member: +Non-PMC member: ```text +1 (non-binding) @@ -177,20 +165,19 @@ I checked: 2. Checksum and signature are OK 3. LICENSE and NOTICE are exist 4. Build successfully on macOS(Big Sur) - 5. + 5. ``` -PPMC/IPMC member: +PMC member: ```text -//Incubator community voting, only IPMC members have binding binding +1 (binding) I checked: 1. All download links are valid 2. Checksum and signature are OK 3. LICENSE and NOTICE are exist 4. Build successfully on macOS(Big Sur) - 5. + 5. ``` --- diff --git a/home/docs/community/maturity.md b/home/docs/community/maturity.md index a60f7b26b1..d3988fd2d4 100644 --- a/home/docs/community/maturity.md +++ b/home/docs/community/maturity.md @@ -69,7 +69,7 @@ community members are welcome to comment and modify it. | **CO10** | The project has a well-known homepage that points to all the information required to operate according to this maturity model. | **YES** The [official website](https://hertzbeat.apache.org/) includes all information user need to run Apache HertzBeat. | | **CO20** | The community welcomes contributions from anyone who acts in good faith and in a respectful manner, and who adds value to the project. | **Yes** We provide contributing guides for every component. And we also have a [general contributing guide](https://hertzbeat.apache.org/docs/community/contribution) | | **CO30** | Contributions include source code, documentation, constructive bug reports, constructive discussions, marketing and generally anything that adds value to the project. | **YES** All good contributions including code and non-code are welcomed. | -| **CO40** | The community strives to be meritocratic and gives more rights and responsibilities to contributors who, over time, add value to the project. | **YES** The community has elected 3 new PPMC members and 13 new committers so far. | +| **CO40** | The community strives to be meritocratic and gives more rights and responsibilities to contributors who, over time, add value to the project. | **YES** The community has elected 3 new PMC members and 13 new committers so far. | | **CO50** | The project documents how contributors can earn more rights such as commit access or decision power, and applies these principles consistently. | **YES** The community has clear docs on nominating committers and PPMC members | | **CO60** | The community operates based on consensus of its members (see CS10) who have decision power. Dictators, benevolent or not, are not welcome in Apache projects. | **YES** All decisions are made after vote by community members. | | **CO70** | The project strives to answer user questions in a timely manner. | **YES** We use , [GitHub issue](https://github.com/apache/hertzbeat/issues) and [GitHub discussion](https://github.com/apache/hertzbeat/discussions) to do this in a timely manner. | diff --git a/home/docs/community/new_committer_process.md b/home/docs/community/new_committer_process.md index 6670d35719..01a5583ebf 100644 --- a/home/docs/community/new_committer_process.md +++ b/home/docs/community/new_committer_process.md @@ -73,7 +73,7 @@ Subject: [VOTE] New committer: ${NEW_COMMITTER_NAME} ``` ```text -Hi HertzBeat PPMC, +Hi HertzBeat PMC, This is a formal vote about inviting ${NEW_COMMITTER_NAME} as our community new committer. @@ -121,7 +121,7 @@ Subject: [RESULT] [VOTE] New committer: ${NEW_COMMITTER_NAME} ``` ```text -Hi HertzBeat PPMC, +Hi HertzBeat PMC, The vote has now closed. The results are: @@ -145,23 +145,23 @@ Subject: Invitation to become HertzBeat committer: ${NEW_COMMITTER_NAME} ```text Hello ${NEW_COMMITTER_NAME}, -The HertzBeat Project Management Committee (PMC) +The HertzBeat Project Management Committee (PMC) hereby offers you committer privileges to the project. These privileges are offered on the understanding that you'll use them reasonably and with common sense. -We like to work on trust rather than unnecessary constraints. +We like to work on trust rather than unnecessary constraints. -Being a committer enables you to more easily make -changes without needing to go through the patch +Being a committer enables you to more easily make +changes without needing to go through the patch submission process. -Being a committer does not require you to -participate any more than you already do. It does -tend to make one even more committed. You will +Being a committer does not require you to +participate any more than you already do. It does +tend to make one even more committed. You will probably find that you spend more time here. -Of course, you can decline and instead remain as a +Of course, you can decline and instead remain as a contributor, participating as you do now. This personal invitation is a chance for you to accept or decline in private. @@ -235,10 +235,10 @@ You need to choose a preferred ASF user name and alternatives. In order to ensure it is available you can view a list of taken IDs at https://people.apache.org/committer-index.html -Please notify us when you have submitted the CLA and by what means +Please notify us when you have submitted the CLA and by what means you did so. This will enable us to monitor its progress. -We will arrange for your Apache user account when the CLA has +We will arrange for your Apache user account when the CLA has been recorded. After that is done, please make followup replies to the dev@hertzbeat.apache.org list. @@ -260,7 +260,7 @@ requires adherence to the ASF Code of Conduct: https://www.apache.org/foundation/policies/conduct.html Yours, -The Apache HertzBeat PPMC +The Apache HertzBeat new PMC ``` ### Announce New Committer Template @@ -273,7 +273,7 @@ Subject: [ANNOUNCE] New committer: ${NEW_COMMITTER_NAME} ```text Hello Community, -The Podling Project Management Committee (PPMC) for Apache HertzBeat +The Podling Project Management Committee (new PMC) for Apache HertzBeat has invited ${NEW_COMMITTER_NAME} to become a committer and we are pleased to announce that he has accepted. diff --git a/home/docs/community/new_pmc_member_process.md b/home/docs/community/new_pmc_member_process.md index 291efc0da6..c1093dbe30 100644 --- a/home/docs/community/new_pmc_member_process.md +++ b/home/docs/community/new_pmc_member_process.md @@ -1,6 +1,6 @@ --- id: 'new_pmc_ember_process' -title: 'New PPMC Member Process' +title: 'new PMC Member Process' sidebar_position: 5 --- @@ -23,7 +23,7 @@ limitations under the License. [Apache New Committer Guideline](https://community.apache.org/newcommitter.html#new-committer-process) -## The process of new PPMC member +## The process of new PMC member - Call a vote in mailing `private@hertzbeat.apache.org` @@ -33,23 +33,23 @@ limitations under the License. see **Close Vote Template** -- Board Approval of new PPMC member +- Board Approval of new PMC member - see **Board Approval of new PPMC member** + see **Board Approval of new PMC member** -- If the result is positive, invite the new PPMC member +- If the result is positive, invite the new PMC member see **PMC member Invite Template** -- If accept, then: Accept the PPMC member +- If accept, then: Accept the PMC member see **PMC Member Accept Template** -- Notify the PPMC member of completion +- Notify the PMC member of completion see **PMC Member Done Template** -- Announce the new PPMC member +- Announce the new PMC member see **PMC Member Announce Template** @@ -61,17 +61,17 @@ Note that, there are three placeholder in template should be replaced before usi - NEW_PMC_EMAIL - NEW_PMC_APACHE_NAME -### PPMC Member Vote Template +### PMC member Vote Template ```text To: private@hertzbeat.apache.org -Subject: [VOTE] New PPMC member candidate: ${NEW_PMC_NAME} +Subject: [VOTE] new PMC member candidate: ${NEW_PMC_NAME} ``` ```text -Hi HertzBeat PPMC, +Hi HertzBeat PMC, -This is a formal vote about inviting ${NEW_PMC_NAME} as our new PPMC member. +This is a formal vote about inviting ${NEW_PMC_NAME} as our new PMC member. ${Work list}[1] @@ -85,11 +85,11 @@ Note that, Voting ends one week from today, i.e. [midnight UTC on YYYY-MM-DD](ht ```text To: private@hertzbeat.apache.org -Subject: [RESULT] [VOTE] New PPMC member: ${NEW_PMC_NAME} +Subject: [RESULT] [VOTE] new PMC member: ${NEW_PMC_NAME} ``` ```text -Hi HertzBeat PPMC, +Hi HertzBeat PMC, The vote has now closed. The results are: @@ -102,12 +102,12 @@ Binding Votes: The vote is ***successful/not successful*** ``` -### Board Approval of new PPMC member Template +### Board Approval of new PMC member Template ```text To: board@apache.org Cc: private@.apache.org -Subject: [NOTICE] ${NEW_PMC_NAME} for HertzBeat PPMC member +Subject: [NOTICE] ${NEW_PMC_NAME} for HertzBeat PMC member ``` ```text @@ -118,66 +118,66 @@ The vote result is available here: https://lists.apache.org/... [Apache New Pmc Guide](https://www.apache.org/dev/pmc.html#newpmc) -### PPMC Member Invite Template +### PMC member Invite Template ```text To: ${NEW_PMC_EMAIL} Cc: private@hertzbeat.apache.org -Subject: Invitation to become HertzBeat PPMC member: ${NEW_PMC_NAME} +Subject: Invitation to become HertzBeat PMC member: ${NEW_PMC_NAME} ``` ```text Hello ${NEW_PMC_NAME}, -The HertzBeat Project Management Committee (PMC) +The HertzBeat Project Management Committee (PMC) hereby offers you committer privileges to the project as well as membership in the PMC. These privileges are offered on the understanding that you'll use them reasonably and with common sense. -We like to work on trust rather than unnecessary constraints. +We like to work on trust rather than unnecessary constraints. -Being a PPMC member enables you to guide the direction of the project. +Being a PMC member enables you to guide the direction of the project. -Being a PPMC member does not require you to -participate any more than you already do. It does -tend to make one even more committed. You will +Being a PMC member does not require you to +participate any more than you already do. It does +tend to make one even more committed. You will probably find that you spend more time here. -Of course, you can decline and instead remain as a +Of course, you can decline and instead remain as a contributor, participating as you do now. -A. This personal invitation is a chance for you to -accept or decline in private. Either way, please +A. This personal invitation is a chance for you to +accept or decline in private. Either way, please let us know in reply to the private@hertzbeat.apache.org address only. B. If you accept, the next step is to register an iCLA: - 1. Details of the iCLA and the forms are found + 1. Details of the iCLA and the forms are found through this link: https://www.apache.org/licenses/#clas - 2. Instructions for its completion and return to + 2. Instructions for its completion and return to the Secretary of the ASF are found at https://www.apache.org/licenses/#submitting - 3. When you transmit the completed iCLA, request - to notify the Apache HertzBeat and choose a - unique Apache ID. Look to see if your preferred - ID is already taken at + 3. When you transmit the completed iCLA, request + to notify the Apache HertzBeat and choose a + unique Apache ID. Look to see if your preferred + ID is already taken at https://people.apache.org/committer-index.html - This will allow the Secretary to notify the PPMC + This will allow the Secretary to notify the PMC when your iCLA has been recorded. -When recording of your iCLA is noted, you will -receive a follow-up message with the next steps for -establishing you as a PPMC member. +When recording of your iCLA is noted, you will +receive a follow-up message with the next steps for +establishing you as a PMC member. ``` -### PPMC Member Accept Template +### PMC member Accept Template ```text To: ${NEW_PMC_EMAIL} Cc: private@hertzbeat.apache.org -Subject: Re: invitation to become HertzBeat PPMC member +Subject: Re: invitation to become HertzBeat PMC member ``` ```text @@ -194,10 +194,10 @@ You need to choose a preferred ASF user name and alternatives. In order to ensure it is available you can view a list of taken IDs at https://people.apache.org/committer-index.html -Please notify us when you have submitted the CLA and by what means +Please notify us when you have submitted the CLA and by what means you did so. This will enable us to monitor its progress. -We will arrange for your Apache user account when the CLA has +We will arrange for your Apache user account when the CLA has been recorded. After that is done, please make followup replies to the dev@hertzbeat.apache.org list. @@ -214,15 +214,15 @@ in incubating projects: https://incubator.apache.org/guides/committer.html https://incubator.apache.org/guides/ppmc.html -Just as before you became a PPMC member, participation in any ASF community +Just as before you became a PMC member, participation in any ASF community requires adherence to the ASF Code of Conduct: https://www.apache.org/foundation/policies/conduct.html Yours, -The Apache HertzBeat PPMC +The Apache HertzBeat PMC ``` -### PPMC Member Done Template +### PMC member Done Template ```text To: private@hertzbeat.apache.org, ${NEW_PMC_EMAIL} @@ -249,25 +249,25 @@ see the following resources: Apache developer's pages: https://www.apache.org/dev/ Incubator committer guide: https://incubator.apache.org/guides/committer.html -Naturally, if you don't understand anything be sure to ask us on the dev@hertzbeat.apache.org mailing list. +Naturally, if you don't understand anything be sure to ask us on the dev@hertzbeat.apache.org mailing list. Documentation is maintained by volunteers and hence can be out-of-date and incomplete - of course you can now help fix that. -A PPMC member will announce your election to the dev list soon. +A PMC member will announce your election to the dev list soon. ``` -### PPMC Member Announce Template +### PMC member Announce Template ```text To: dev@hertzbeat.apache.org -[ANNONCE] New PPMC member: ${NEW_PMC_NAME} +[ANNONCE] new PMC member: ${NEW_PMC_NAME} ``` ```text Hi HertzBeat Community, -The Podling Project Management Committee (PPMC) for Apache HertzBeat -has invited ${NEW_PMC_NAME} to become our PPMC member and +The Podling Project Management Committee (PMC) for Apache HertzBeat +has invited ${NEW_PMC_NAME} to become our PMC member and we are pleased to announce that he has accepted. ### add specific details here ### @@ -277,8 +277,8 @@ Please join me in congratulating ${NEW_PMC_NAME}! Being a committer enables easier contribution to the project since there is no need to go via the patch submission process. This should enable better productivity. -A PPMC member helps manage and guide the direction of the project. +A PMC member helps manage and guide the direction of the project. Thanks, -On behalf of the Apache HertzBeat PPMC +On behalf of the Apache HertzBeat PMC ``` diff --git a/home/docs/help/mcp_server.md b/home/docs/help/mcp_server.md index 82ec71cefa..963d4a53e3 100644 --- a/home/docs/help/mcp_server.md +++ b/home/docs/help/mcp_server.md @@ -2,21 +2,24 @@ id: mcp_server title: MCP Server sidebar_label: MCP Server -keywords: [MCP, SSE, streaming, server] +keywords: [MCP, StreamableHttp, 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. +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +This page explains how connect to the HertzBeat MCP 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. +- Provides Streamable-Http protocol MCP server. - 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` +- URL: `http://your-hertzbeat-server-host:1157/api/mcp` ### Authentication @@ -31,7 +34,57 @@ Note: You can generate a JWT token from the HertzBeat web UI under the Log Integ - Basic authentication - Header: `Authorization: Basic ` -### Cursor MCP configuration +### Editor MCP configuration + + + + +Claude Code use a global config file `~/.claude.json` to config mcp server. You can add HertzBeat MCP by CLI or edit this file directly. + +Method A: Use the CLI. + +```bash +claude mcp add -s user -t http hertzbeat-mcp http://your-hertzbeat-server-host:1157/api/mcp --header "Authorization: Bearer your_jwt_key" +``` + +Method B: Edit the file directly. + +Basic auth: + +```jsonc +{ + "mcpServers": { + "hertzbeat-mcp": { + "type": "sse", + "url": "http://your-hertzbeat-server-host:1157/api/mcp", + "headers": { + "Authorization": "Basic " + } + } + } +} +``` + +JWT bearer: + +```jsonc +{ + "mcpServers": { + "hertzbeat-mcp": { + "type": "sse", + "url": "http://your-hertzbeat-server-host:1157/api/mcp", + "headers": { + "Authorization": "Bearer " + } + } + } +} +``` + +After saving `~/.claude.json`, restart or reload Claude Code to make the new MCP configuration take effect. + + + Create or edit `.cursor/mcp.json` in your home directory or project root. @@ -39,8 +92,8 @@ Basic auth: ```json { - "Hertzbeat-MCP": { - "url": "http://localhost:1157/api/sse", + "hertzbeat-mcp": { + "url": "http://your-hertzbeat-server-host:1157/api/mcp", "headers": { "Authorization": "Basic " } @@ -52,8 +105,8 @@ JWT bearer: ```json { - "Hertzbeat-MCP": { - "url": "http://localhost:1157/api/sse", + "hertzbeat-mcp": { + "url": "http://your-hertzbeat-server-host:1157/api/mcp", "headers": { "Authorization": "Bearer " } @@ -63,6 +116,9 @@ JWT bearer: After saving, reload MCP in Cursor or restart the editor. + + + ### Tools available #### Monitor Management Tools diff --git a/home/i18n/zh-cn/docusaurus-plugin-content-docs/current/community/become_committer.md b/home/i18n/zh-cn/docusaurus-plugin-content-docs/current/community/become_committer.md index 5245663444..aa7928afa7 100644 --- a/home/i18n/zh-cn/docusaurus-plugin-content-docs/current/community/become_committer.md +++ b/home/i18n/zh-cn/docusaurus-plugin-content-docs/current/community/become_committer.md @@ -34,7 +34,7 @@ Apache HertzBeat™ 社区努力追求基于功绩的原则。因此,一旦有 ## Committer 的要求 -没有成为 Committer 或 PPMC 成员的严格规则。新的 Committer 的候选人通常是积极的贡献者和社区成员。但是,如果能稍微明确一些规则,就可以在一定程度上消除贡献者的疑虑,使社区更加透明、合理和公平。 +没有成为 Committer 或 PMC 成员的严格规则。新的 Committer 的候选人通常是积极的贡献者和社区成员。但是,如果能稍微明确一些规则,就可以在一定程度上消除贡献者的疑虑,使社区更加透明、合理和公平。 ### 持续的贡献 diff --git a/home/i18n/zh-cn/docusaurus-plugin-content-docs/current/community/become_pmc_member.md b/home/i18n/zh-cn/docusaurus-plugin-content-docs/current/community/become_pmc_member.md index 7f6b5c0286..3829bf75b1 100644 --- a/home/i18n/zh-cn/docusaurus-plugin-content-docs/current/community/become_pmc_member.md +++ b/home/i18n/zh-cn/docusaurus-plugin-content-docs/current/community/become_pmc_member.md @@ -21,7 +21,7 @@ See the License for the specific language governing permissions and limitations under the License. --> -## 成为 Apache HertzBeat™ 的 PPMC 成员 +## 成为 Apache HertzBeat™ 的 PMC 成员 任何支持社区并在 CoPDoC 领域中工作的人都可以成为 Apache HertzBeat™ 的PMC 成员。CoPDoC 是 ASF 的缩写,用来描述我们如何不仅仅通过代码来认识到您的贡献。 @@ -30,11 +30,11 @@ limitations under the License. - **Documentation** - 没有它,内容只会停留在作者的头脑中。 - **Code** - 没有代码,讨论就毫无意义。 -Apache HertzBeat™ 社区努力追求基于功绩的原则。因此,一旦有人在 CoPDoC 的任何领域有了足够的贡献,他们就可以成为 PPMC 成员资格的候选人,最终被投票选为 HertzBeat 的 PPMC 成员。成为 Apache HertzBeat™ 的 PPMC 成员并不一定意味着您必须使用您的提交权限向代码库提交代码;它意味着您致力于 HertzBeat 项目并为我们社区的成功做出了积极的贡献。 +Apache HertzBeat™ 社区努力追求基于功绩的原则。因此,一旦有人在 CoPDoC 的任何领域有了足够的贡献,他们就可以成为 PMC 成员资格的候选人,最终被投票选为 HertzBeat 的 PMC 成员。成为 Apache HertzBeat™ 的 PMC 成员并不一定意味着您必须使用您的提交权限向代码库提交代码;它意味着您致力于 HertzBeat 项目并为我们社区的成功做出了积极的贡献。 -## PPMC 成员的要求 +## PMC 成员的要求 -没有成为 Committer 或 PPMC 成员的严格规则。新的 PPMC 成员的候选人通常是积极的贡献者和社区成员。但是,如果能稍微明确一些规则,就可以在一定程度上消除贡献者的疑虑,使社区更加透明、合理和公平。 +没有成为 Committer 或 PMC 成员的严格规则。新的 PMC 成员的候选人通常是积极的贡献者和社区成员。但是,如果能稍微明确一些规则,就可以在一定程度上消除贡献者的疑虑,使社区更加透明、合理和公平。 ### 持续的贡献 diff --git a/home/i18n/zh-cn/docusaurus-plugin-content-docs/current/community/how-to-verify.md b/home/i18n/zh-cn/docusaurus-plugin-content-docs/current/community/how-to-verify.md index b9ad926263..cde6606c57 100644 --- a/home/i18n/zh-cn/docusaurus-plugin-content-docs/current/community/how-to-verify.md +++ b/home/i18n/zh-cn/docusaurus-plugin-content-docs/current/community/how-to-verify.md @@ -15,7 +15,7 @@ sidebar_position: 4 > 需要依赖gpg工具,如果没有,建议安装gpg2 ```shell -#如果本地有svn,可以clone到本地 +#如果本地有svn,可以clone到本地 svn co https://dist.apache.org/repos/dist/dev/incubator/hertzbeat/${release_version}-${rc_version}/ #或者 直接下载物料文件 wget https://dist.apache.org/repos/dist/dev/incubator/hertzbeat/${release_version}-${rc_version}/xxx.xxx @@ -70,9 +70,9 @@ Please decide how far you trust this user to correctly verify other users' keys Your decision? 5 #选择5 Do you really want to set this key to ultimate trust? (y/N) y #选择y - -gpg> - + +gpg> + ``` ##### 2.2.3 检查签名 @@ -150,23 +150,11 @@ cd apache-hertzbeat-${release_version}-incubating-src :::caution 注意 回复的邮件一定要带上自己检查了那些项信息,仅仅回复`+1 approve`,是无效的。 -PPMC 在 HertzBeat 的社区投票时,请带上 binding 后缀,表示对 HertzBeat 社区中的投票具有约束性投票,方便统计投票结果。 - -IPMC 在 incubator 社区投票,请带上 binding 后缀,表示对 incubator 社区中的投票具有约束性投票,方便统计投票结果。 -::: - -:::caution 注意 -如果在已经投过票,在incubator社区进行投票回复时,可以直接带过去,需要注意约束性 如: - -```html -//incubator社区 投票时,只有IPMC成员才具有约束性 binding,PPMC需要注意约束性的变化 -Forward my +1 from dev@hertzbeat (non-binding) -Copy my +1 from hertzbeat DEV ML (non-binding) -``` +PMC 在 HertzBeat 的社区投票时,请带上 binding 后缀,表示对 HertzBeat 社区中的投票具有约束性投票,方便统计投票结果。 ::: -非PPMC/IPMC成员 +非PMC成员 ```html +1 (non-binding) @@ -174,20 +162,19 @@ I checked: 1. All download links are valid 2. Checksum and signature are OK 3. LICENSE and NOTICE are exist - 4. Build successfully on macOS(Big Sur) + 4. Build successfully on macOS(Big Sur) 5. .... ``` -PPMC/IPMC成员 +PMC成员 ```html -//incubator社区 投票时,只有IPMC成员才具有约束性 binding +1 (binding) I checked: 1. All download links are valid 2. Checksum and signature are OK 3. LICENSE and NOTICE are exist - 4. Build successfully on macOS(Big Sur) + 4. Build successfully on macOS(Big Sur) 5. .... ``` diff --git a/home/i18n/zh-cn/docusaurus-plugin-content-docs/current/community/new_committer_process.md b/home/i18n/zh-cn/docusaurus-plugin-content-docs/current/community/new_committer_process.md index a4b0f9ea41..ce3ef12ee5 100644 --- a/home/i18n/zh-cn/docusaurus-plugin-content-docs/current/community/new_committer_process.md +++ b/home/i18n/zh-cn/docusaurus-plugin-content-docs/current/community/new_committer_process.md @@ -73,7 +73,7 @@ Subject: [VOTE] New committer: ${NEW_COMMITTER_NAME} ``` ```text -Hi HertzBeat PPMC, +Hi HertzBeat PMC, This is a formal vote about inviting ${NEW_COMMITTER_NAME} as our community new committer. @@ -118,7 +118,7 @@ Subject: [RESULT] [VOTE] New committer: ${NEW_COMMITTER_NAME} ``` ```text -Hi HertzBeat PPMC, +Hi HertzBeat PMC, The vote has now closed. The results are: @@ -142,23 +142,23 @@ Subject: Invitation to become HertzBeat committer: ${NEW_COMMITTER_NAME} ```text Hello ${NEW_COMMITTER_NAME}, -The HertzBeat Project Management Committee (PMC) +The HertzBeat Project Management Committee (PMC) hereby offers you committer privileges to the project. These privileges are offered on the understanding that you'll use them reasonably and with common sense. -We like to work on trust rather than unnecessary constraints. +We like to work on trust rather than unnecessary constraints. -Being a committer enables you to more easily make -changes without needing to go through the patch +Being a committer enables you to more easily make +changes without needing to go through the patch submission process. -Being a committer does not require you to -participate any more than you already do. It does -tend to make one even more committed. You will +Being a committer does not require you to +participate any more than you already do. It does +tend to make one even more committed. You will probably find that you spend more time here. -Of course, you can decline and instead remain as a +Of course, you can decline and instead remain as a contributor, participating as you do now. This personal invitation is a chance for you to accept or decline in private. @@ -232,10 +232,10 @@ You need to choose a preferred ASF user name and alternatives. In order to ensure it is available you can view a list of taken IDs at https://people.apache.org/committer-index.html -Please notify us when you have submitted the CLA and by what means +Please notify us when you have submitted the CLA and by what means you did so. This will enable us to monitor its progress. -We will arrange for your Apache user account when the CLA has +We will arrange for your Apache user account when the CLA has been recorded. After that is done, please make followup replies to the dev@hertzbeat.apache.org list. @@ -257,7 +257,7 @@ requires adherence to the ASF Code of Conduct: https://www.apache.org/foundation/policies/conduct.html Yours, -The Apache HertzBeat PPMC +The Apache HertzBeat PMC ``` ### Announce New Committer Template @@ -270,7 +270,7 @@ Subject: [ANNOUNCE] New committer: ${NEW_COMMITTER_NAME} ```text Hello Community, -The Podling Project Management Committee (PPMC) for Apache HertzBeat +The Podling Project Management Committee (PMC) for Apache HertzBeat has invited ${NEW_COMMITTER_NAME} to become a committer and we are pleased to announce that he has accepted. @@ -290,7 +290,7 @@ Best Wishes! ```text Hello xxxx, -The HertzBeat Project Management Committee (PPMC) +The HertzBeat Project Management Committee (PMC) hereby offers you committer privileges to the project. These privileges are offered on the understanding that you'll use them reasonably and with common sense. diff --git a/home/i18n/zh-cn/docusaurus-plugin-content-docs/current/community/new_pmc_member_process.md b/home/i18n/zh-cn/docusaurus-plugin-content-docs/current/community/new_pmc_member_process.md index 9b92fdb5a0..b7cd8435e7 100644 --- a/home/i18n/zh-cn/docusaurus-plugin-content-docs/current/community/new_pmc_member_process.md +++ b/home/i18n/zh-cn/docusaurus-plugin-content-docs/current/community/new_pmc_member_process.md @@ -65,13 +65,13 @@ limitations under the License. ```text To: private@hertzbeat.apache.org -Subject: [VOTE] New PPMC member candidate: ${NEW_PMC_NAME} +Subject: [VOTE] New PMC member candidate: ${NEW_PMC_NAME} ``` ```text -Hi HertzBeat PPMC, +Hi HertzBeat PMC, -This is a formal vote about inviting ${NEW_PMC_NAME} as our new PPMC member. +This is a formal vote about inviting ${NEW_PMC_NAME} as our new PMC member. ${Work list}[1] @@ -86,11 +86,11 @@ ${Work list}[1] ```text To: private@hertzbeat.apache.org -Subject: [RESULT] [VOTE] New PPMC member: ${NEW_PMC_NAME} +Subject: [RESULT] [VOTE] New PMC member: ${NEW_PMC_NAME} ``` ```text -Hi HertzBeat PPMC, +Hi HertzBeat PMC, The vote has now closed. The results are: @@ -103,12 +103,12 @@ Binding Votes: The vote is ***successful/not successful*** ``` -### Board Approval of new PPMC member Template +### Board Approval of new PMC member Template ```text To: board@apache.org Cc: private@.apache.org -Subject: [NOTICE] ${NEW_PMC_NAME} for HertzBeat PPMC member +Subject: [NOTICE] ${NEW_PMC_NAME} for HertzBeat PMC member ``` ```text @@ -124,53 +124,53 @@ The vote result is available here: https://lists.apache.org/... ```text To: ${NEW_PMC_EMAIL} Cc: private@hertzbeat.apache.org -Subject: Invitation to become HertzBeat PPMC member: ${NEW_PMC_NAME} +Subject: Invitation to become HertzBeat PMC member: ${NEW_PMC_NAME} ``` ```text Hello ${NEW_PMC_NAME}, -The HertzBeat Project Management Committee (PMC) +The HertzBeat Project Management Committee (PMC) hereby offers you committer privileges to the project as well as membership in the PMC. These privileges are offered on the understanding that you'll use them reasonably and with common sense. -We like to work on trust rather than unnecessary constraints. +We like to work on trust rather than unnecessary constraints. -Being a PPMC member enables you to guide the direction of the project. +Being a PMC member enables you to guide the direction of the project. -Being a PPMC member does not require you to -participate any more than you already do. It does -tend to make one even more committed. You will +Being a PMC member does not require you to +participate any more than you already do. It does +tend to make one even more committed. You will probably find that you spend more time here. -Of course, you can decline and instead remain as a +Of course, you can decline and instead remain as a contributor, participating as you do now. -A. This personal invitation is a chance for you to -accept or decline in private. Either way, please +A. This personal invitation is a chance for you to +accept or decline in private. Either way, please let us know in reply to the private@hertzbeat.apache.org address only. B. If you accept, the next step is to register an iCLA: - 1. Details of the iCLA and the forms are found + 1. Details of the iCLA and the forms are found through this link: https://www.apache.org/licenses/#clas - 2. Instructions for its completion and return to + 2. Instructions for its completion and return to the Secretary of the ASF are found at https://www.apache.org/licenses/#submitting - 3. When you transmit the completed iCLA, request - to notify the Apache HertzBeat and choose a - unique Apache ID. Look to see if your preferred - ID is already taken at + 3. When you transmit the completed iCLA, request + to notify the Apache HertzBeat and choose a + unique Apache ID. Look to see if your preferred + ID is already taken at https://people.apache.org/committer-index.html - This will allow the Secretary to notify the PPMC + This will allow the Secretary to notify the PMC when your iCLA has been recorded. -When recording of your iCLA is noted, you will -receive a follow-up message with the next steps for -establishing you as a PPMC member. +When recording of your iCLA is noted, you will +receive a follow-up message with the next steps for +establishing you as a PMC member. ``` ### PMC成员接受模板 @@ -178,7 +178,7 @@ establishing you as a PPMC member. ```text To: ${NEW_PMC_EMAIL} Cc: private@hertzbeatv.apache.org -Subject: Re: invitation to become HertzBeat PPMC member +Subject: Re: invitation to become HertzBeat PMC member ``` ```text @@ -195,10 +195,10 @@ You need to choose a preferred ASF user name and alternatives. In order to ensure it is available you can view a list of taken IDs at https://people.apache.org/committer-index.html -Please notify us when you have submitted the CLA and by what means +Please notify us when you have submitted the CLA and by what means you did so. This will enable us to monitor its progress. -We will arrange for your Apache user account when the CLA has +We will arrange for your Apache user account when the CLA has been recorded. After that is done, please make followup replies to the dev@hertzbeat.apache.org list. @@ -215,12 +215,12 @@ in incubating projects: https://incubator.apache.org/guides/committer.html https://incubator.apache.org/guides/ppmc.html -Just as before you became a PPMC member, participation in any ASF community +Just as before you became a PMC member, participation in any ASF community requires adherence to the ASF Code of Conduct: https://www.apache.org/foundation/policies/conduct.html Yours, -The Apache HertzBeat PPMC +The Apache HertzBeat PMC ``` ### PMC成员完成模板 @@ -250,25 +250,25 @@ see the following resources: Apache developer's pages: https://www.apache.org/dev/ Incubator committer guide: https://incubator.apache.org/guides/committer.html -Naturally, if you don't understand anything be sure to ask us on the dev@hertzbeat.apache.org mailing list. +Naturally, if you don't understand anything be sure to ask us on the dev@hertzbeat.apache.org mailing list. Documentation is maintained by volunteers and hence can be out-of-date and incomplete - of course you can now help fix that. -A PPMC member will announce your election to the dev list soon. +A PMC member will announce your election to the dev list soon. ``` ### PMC成员通知模板 ```text To: dev@hertzbeat.apache.org -[ANNONCE] New PPMC member: ${NEW_PMC_NAME} +[ANNONCE] New PMC member: ${NEW_PMC_NAME} ``` ```text Hi HertzBeat Community, -The Podling Project Management Committee (PPMC) for Apache HertzBeat -has invited ${NEW_PMC_NAME} to become our PPMC member and +The Podling Project Management Committee (PMC) for Apache HertzBeat +has invited ${NEW_PMC_NAME} to become our PMC member and we are pleased to announce that he has accepted. ### add specific details here ### @@ -278,8 +278,8 @@ Please join me in congratulating ${NEW_PMC_NAME}! Being a committer enables easier contribution to the project since there is no need to go via the patch submission process. This should enable better productivity. -A PPMC member helps manage and guide the direction of the project. +A PMC member helps manage and guide the direction of the project. Thanks, -On behalf of the Apache HertzBeat PPMC +On behalf of the Apache HertzBeat PMC ``` diff --git a/home/i18n/zh-cn/docusaurus-plugin-content-docs/current/help/mcp_server.md b/home/i18n/zh-cn/docusaurus-plugin-content-docs/current/help/mcp_server.md index 0e04620e19..1680655c7d 100644 --- a/home/i18n/zh-cn/docusaurus-plugin-content-docs/current/help/mcp_server.md +++ b/home/i18n/zh-cn/docusaurus-plugin-content-docs/current/help/mcp_server.md @@ -2,21 +2,24 @@ id: mcp_server title: MCP 服务器 sidebar_label: MCP 服务器 -keywords: [MCP, SSE, 流式传输, 服务器] +keywords: [MCP, StreamabelHttp, 流式传输, 服务器] --- -本页面介绍如何连接到 HertzBeat MCP SSE 服务器。当您启动 HertzBeat 服务器时,MCP 服务器会自动在默认端口 1157 上启动。 +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +本页面介绍如何连接到 HertzBeat MCP 服务器。当您启动 HertzBeat 服务器时,MCP 服务器会自动在默认端口 1157 上启动。 ### 概述 -- 提供用于工具调用的服务器发送事件 (SSE) 流。 +- 提供 StreamabelHttp 协议的 MCP服务器。 - 专为 MCP 集成和消费流式事件的客户端而设计。 ### 连接到 MCP 服务器 确保 HertzBeat 服务器已启动并正在运行。如果您使用的端口不是 1157,请相应地替换以下内容: -- URL: `http://localhost:1157/api/sse` +- URL: `http://your-hertzbeat-server-host:1157/api/mcp` ### 身份验证 @@ -31,16 +34,66 @@ keywords: [MCP, SSE, 流式传输, 服务器] - 基本身份验证 - 请求头: `Authorization: Basic ` -### Cursor MCP 配置 +### MCP 配置 -在您的主目录或项目根目录中创建或编辑 `.cursor/mcp.json`。 + + -基本身份验证: +Claude Code 使用一个全局配置文件 `~/.claude.json` 来管理 MCP 服务器,你可以通过 CLI 或直接编辑这个文件来添加 HertzBeat MCP: + +方式一: 使用 CLI 快速创建: + +```bash +claude mcp add -s user -t http hertzbeat-mcp http://your-hertzbeat-server-host:1157/api/mcp --header "Authorization: Bearer your_jwt_key" +``` + +方式二:打开 `~/.claude.json`,在顶层的 `mcpServers` 下添加 HertzBeat MCP 配置。 + +Basic auth: + +```jsonc +{ + "mcpServers": { + "hertzbeat-mcp": { + "type": "sse", + "url": "http://your-hertzbeat-server-host:1157/api/mcp", + "headers": { + "Authorization": "Basic " + } + } + } +} +``` + +JWT bearer: + +```jsonc +{ + "mcpServers": { + "hertzbeat-mcp": { + "type": "sse", + "url": "http://your-hertzbeat-server-host:1157/api/mcp", + "headers": { + "Authorization": "Bearer " + } + } + } +} +``` + +保存 `~/.claude.json` 后,重启或重新加载 Claude Code,让新的 MCP 配置生效。 + + + + +Cursor 使用配置文件 `.cursor/mcp.json` 来管理 MCP 服务器,在用户目录或者项目目录下创建或者编辑这个文件: + +Basic auth: ```json { - "Hertzbeat-MCP": { - "url": "http://localhost:1157/api/sse", + "hertzbeat-mcp": { + "url": "http://your-hertzbeat-server-host:1157/api/mcp", "headers": { "Authorization": "Basic " } @@ -48,12 +101,12 @@ keywords: [MCP, SSE, 流式传输, 服务器] } ``` -JWT Bearer: +JWT bearer: ```json { - "Hertzbeat-MCP": { - "url": "http://localhost:1157/api/sse", + "hertzbeat-mcp": { + "url": "http://your-hertzbeat-server-host:1157/api/mcp", "headers": { "Authorization": "Bearer " } @@ -61,7 +114,10 @@ JWT Bearer: } ``` -保存后,在 Cursor 中重新加载 MCP 或重启编辑器。 +After saving, reload MCP in Cursor or restart the editor. + + + ### 可用工具 diff --git a/script/application.yml b/script/application.yml index ea661edf93..c5d00079ed 100644 --- a/script/application.yml +++ b/script/application.yml @@ -24,19 +24,12 @@ spring: server: enabled: true stdio: false - name: sse-mcp-server + protocol: streamable + streamable-http: + mcp-endpoint: /api/mcp + name: hertzbeat-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 mvc: static-path-pattern: /** diff --git a/script/docker-compose/hertzbeat-mysql-iotdb/conf/application.yml b/script/docker-compose/hertzbeat-mysql-iotdb/conf/application.yml index 1e847ec970..feceab674d 100644 --- a/script/docker-compose/hertzbeat-mysql-iotdb/conf/application.yml +++ b/script/docker-compose/hertzbeat-mysql-iotdb/conf/application.yml @@ -25,19 +25,12 @@ spring: server: enabled: true stdio: false - name: sse-mcp-server + protocol: streamable + streamable-http: + mcp-endpoint: /api/mcp + name: hertzbeat-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 mvc: static-path-pattern: /** jackson: diff --git a/script/docker-compose/hertzbeat-mysql-iotdb/conf/sureness.yml b/script/docker-compose/hertzbeat-mysql-iotdb/conf/sureness.yml index b547db30c4..282e93928a 100644 --- a/script/docker-compose/hertzbeat-mysql-iotdb/conf/sureness.yml +++ b/script/docker-compose/hertzbeat-mysql-iotdb/conf/sureness.yml @@ -66,10 +66,10 @@ 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] + - /api/mcp/**===get===[admin] + - /api/mcp/**===post===[admin] - /api/chat/**===get===[admin,user] - - /api/chat/**===post===[admin,user] + - /api/chat/**===post===[admin] - /api/logs/ingest/**===post===[admin,user] # config the resource restful api that need bypass auth protection @@ -114,7 +114,7 @@ excludedResource: # h2 database - /h2-console/**===* -# account info config +# account info config refer the https://hertzbeat.apache.org/docs/start/account-modify # eg: admin has role [admin,user], password is hertzbeat # eg: tom has role [user], password is hertzbeat # eg: lili has role [guest], plain password is lili, salt is 123, salted password is 1A676730B0C7F54654B0E09184448289 @@ -122,16 +122,3 @@ account: - appId: admin credential: hertzbeat role: [admin] - - appId: tom - credential: hertzbeat - role: [user] - - appId: guest - credential: hertzbeat - role: [guest] - - appId: lili - # credential = MD5(password + salt) - # plain password: hertzbeat - # attention: digest authentication does not support salted encrypted password accounts - credential: 94C6B34E7A199A9F9D4E1F208093B489 - salt: 123 - role: [user] diff --git a/script/docker-compose/hertzbeat-mysql-tdengine/conf/application.yml b/script/docker-compose/hertzbeat-mysql-tdengine/conf/application.yml index 8f55b03994..31776c5218 100644 --- a/script/docker-compose/hertzbeat-mysql-tdengine/conf/application.yml +++ b/script/docker-compose/hertzbeat-mysql-tdengine/conf/application.yml @@ -25,19 +25,12 @@ spring: server: enabled: true stdio: false - name: sse-mcp-server + protocol: streamable + streamable-http: + mcp-endpoint: /api/mcp + name: hertzbeat-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 mvc: static-path-pattern: /** jackson: diff --git a/script/docker-compose/hertzbeat-mysql-tdengine/conf/sureness.yml b/script/docker-compose/hertzbeat-mysql-tdengine/conf/sureness.yml index b547db30c4..282e93928a 100644 --- a/script/docker-compose/hertzbeat-mysql-tdengine/conf/sureness.yml +++ b/script/docker-compose/hertzbeat-mysql-tdengine/conf/sureness.yml @@ -66,10 +66,10 @@ 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] + - /api/mcp/**===get===[admin] + - /api/mcp/**===post===[admin] - /api/chat/**===get===[admin,user] - - /api/chat/**===post===[admin,user] + - /api/chat/**===post===[admin] - /api/logs/ingest/**===post===[admin,user] # config the resource restful api that need bypass auth protection @@ -114,7 +114,7 @@ excludedResource: # h2 database - /h2-console/**===* -# account info config +# account info config refer the https://hertzbeat.apache.org/docs/start/account-modify # eg: admin has role [admin,user], password is hertzbeat # eg: tom has role [user], password is hertzbeat # eg: lili has role [guest], plain password is lili, salt is 123, salted password is 1A676730B0C7F54654B0E09184448289 @@ -122,16 +122,3 @@ account: - appId: admin credential: hertzbeat role: [admin] - - appId: tom - credential: hertzbeat - role: [user] - - appId: guest - credential: hertzbeat - role: [guest] - - appId: lili - # credential = MD5(password + salt) - # plain password: hertzbeat - # attention: digest authentication does not support salted encrypted password accounts - credential: 94C6B34E7A199A9F9D4E1F208093B489 - salt: 123 - role: [user] diff --git a/script/docker-compose/hertzbeat-mysql-victoria-metrics/conf/application.yml b/script/docker-compose/hertzbeat-mysql-victoria-metrics/conf/application.yml index b85eadbce1..30137ac088 100644 --- a/script/docker-compose/hertzbeat-mysql-victoria-metrics/conf/application.yml +++ b/script/docker-compose/hertzbeat-mysql-victoria-metrics/conf/application.yml @@ -25,19 +25,12 @@ spring: server: enabled: true stdio: false - name: sse-mcp-server + protocol: streamable + streamable-http: + mcp-endpoint: /api/mcp + name: hertzbeat-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 mvc: static-path-pattern: /** jackson: diff --git a/script/docker-compose/hertzbeat-mysql-victoria-metrics/conf/sureness.yml b/script/docker-compose/hertzbeat-mysql-victoria-metrics/conf/sureness.yml index b547db30c4..282e93928a 100644 --- a/script/docker-compose/hertzbeat-mysql-victoria-metrics/conf/sureness.yml +++ b/script/docker-compose/hertzbeat-mysql-victoria-metrics/conf/sureness.yml @@ -66,10 +66,10 @@ 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] + - /api/mcp/**===get===[admin] + - /api/mcp/**===post===[admin] - /api/chat/**===get===[admin,user] - - /api/chat/**===post===[admin,user] + - /api/chat/**===post===[admin] - /api/logs/ingest/**===post===[admin,user] # config the resource restful api that need bypass auth protection @@ -114,7 +114,7 @@ excludedResource: # h2 database - /h2-console/**===* -# account info config +# account info config refer the https://hertzbeat.apache.org/docs/start/account-modify # eg: admin has role [admin,user], password is hertzbeat # eg: tom has role [user], password is hertzbeat # eg: lili has role [guest], plain password is lili, salt is 123, salted password is 1A676730B0C7F54654B0E09184448289 @@ -122,16 +122,3 @@ account: - appId: admin credential: hertzbeat role: [admin] - - appId: tom - credential: hertzbeat - role: [user] - - appId: guest - credential: hertzbeat - role: [guest] - - appId: lili - # credential = MD5(password + salt) - # plain password: hertzbeat - # attention: digest authentication does not support salted encrypted password accounts - credential: 94C6B34E7A199A9F9D4E1F208093B489 - salt: 123 - role: [user] diff --git a/script/docker-compose/hertzbeat-postgresql-greptimedb/conf/application.yml b/script/docker-compose/hertzbeat-postgresql-greptimedb/conf/application.yml index 1bfe96d105..bd4fa8f29b 100644 --- a/script/docker-compose/hertzbeat-postgresql-greptimedb/conf/application.yml +++ b/script/docker-compose/hertzbeat-postgresql-greptimedb/conf/application.yml @@ -25,19 +25,12 @@ spring: server: enabled: true stdio: false - name: sse-mcp-server + protocol: streamable + streamable-http: + mcp-endpoint: /api/mcp + name: hertzbeat-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 mvc: static-path-pattern: /** jackson: diff --git a/script/docker-compose/hertzbeat-postgresql-greptimedb/conf/sureness.yml b/script/docker-compose/hertzbeat-postgresql-greptimedb/conf/sureness.yml index b547db30c4..282e93928a 100644 --- a/script/docker-compose/hertzbeat-postgresql-greptimedb/conf/sureness.yml +++ b/script/docker-compose/hertzbeat-postgresql-greptimedb/conf/sureness.yml @@ -66,10 +66,10 @@ 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] + - /api/mcp/**===get===[admin] + - /api/mcp/**===post===[admin] - /api/chat/**===get===[admin,user] - - /api/chat/**===post===[admin,user] + - /api/chat/**===post===[admin] - /api/logs/ingest/**===post===[admin,user] # config the resource restful api that need bypass auth protection @@ -114,7 +114,7 @@ excludedResource: # h2 database - /h2-console/**===* -# account info config +# account info config refer the https://hertzbeat.apache.org/docs/start/account-modify # eg: admin has role [admin,user], password is hertzbeat # eg: tom has role [user], password is hertzbeat # eg: lili has role [guest], plain password is lili, salt is 123, salted password is 1A676730B0C7F54654B0E09184448289 @@ -122,16 +122,3 @@ account: - appId: admin credential: hertzbeat role: [admin] - - appId: tom - credential: hertzbeat - role: [user] - - appId: guest - credential: hertzbeat - role: [guest] - - appId: lili - # credential = MD5(password + salt) - # plain password: hertzbeat - # attention: digest authentication does not support salted encrypted password accounts - credential: 94C6B34E7A199A9F9D4E1F208093B489 - salt: 123 - role: [user] diff --git a/script/docker-compose/hertzbeat-postgresql-victoria-metrics/conf/application.yml b/script/docker-compose/hertzbeat-postgresql-victoria-metrics/conf/application.yml index 6d3ad23061..24fc28223d 100644 --- a/script/docker-compose/hertzbeat-postgresql-victoria-metrics/conf/application.yml +++ b/script/docker-compose/hertzbeat-postgresql-victoria-metrics/conf/application.yml @@ -25,19 +25,12 @@ spring: server: enabled: true stdio: false - name: sse-mcp-server + protocol: streamable + streamable-http: + mcp-endpoint: /api/mcp + name: hertzbeat-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 mvc: static-path-pattern: /** jackson: diff --git a/script/docker-compose/hertzbeat-postgresql-victoria-metrics/conf/sureness.yml b/script/docker-compose/hertzbeat-postgresql-victoria-metrics/conf/sureness.yml index b547db30c4..282e93928a 100644 --- a/script/docker-compose/hertzbeat-postgresql-victoria-metrics/conf/sureness.yml +++ b/script/docker-compose/hertzbeat-postgresql-victoria-metrics/conf/sureness.yml @@ -66,10 +66,10 @@ 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] + - /api/mcp/**===get===[admin] + - /api/mcp/**===post===[admin] - /api/chat/**===get===[admin,user] - - /api/chat/**===post===[admin,user] + - /api/chat/**===post===[admin] - /api/logs/ingest/**===post===[admin,user] # config the resource restful api that need bypass auth protection @@ -114,7 +114,7 @@ excludedResource: # h2 database - /h2-console/**===* -# account info config +# account info config refer the https://hertzbeat.apache.org/docs/start/account-modify # eg: admin has role [admin,user], password is hertzbeat # eg: tom has role [user], password is hertzbeat # eg: lili has role [guest], plain password is lili, salt is 123, salted password is 1A676730B0C7F54654B0E09184448289 @@ -122,16 +122,3 @@ account: - appId: admin credential: hertzbeat role: [admin] - - appId: tom - credential: hertzbeat - role: [user] - - appId: guest - credential: hertzbeat - role: [guest] - - appId: lili - # credential = MD5(password + salt) - # plain password: hertzbeat - # attention: digest authentication does not support salted encrypted password accounts - credential: 94C6B34E7A199A9F9D4E1F208093B489 - salt: 123 - role: [user] diff --git a/script/helm/hertzbeat-helm-chart b/script/helm/hertzbeat-helm-chart index 2906774fe8..4a4fbb796e 160000 --- a/script/helm/hertzbeat-helm-chart +++ b/script/helm/hertzbeat-helm-chart @@ -1 +1 @@ -Subproject commit 2906774fe83b0f1475f8c467fee9f9c331cb36a9 +Subproject commit 4a4fbb796e9485862c10e38b28a43e604d02284f diff --git a/script/sureness.yml b/script/sureness.yml index b547db30c4..282e93928a 100644 --- a/script/sureness.yml +++ b/script/sureness.yml @@ -66,10 +66,10 @@ 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] + - /api/mcp/**===get===[admin] + - /api/mcp/**===post===[admin] - /api/chat/**===get===[admin,user] - - /api/chat/**===post===[admin,user] + - /api/chat/**===post===[admin] - /api/logs/ingest/**===post===[admin,user] # config the resource restful api that need bypass auth protection @@ -114,7 +114,7 @@ excludedResource: # h2 database - /h2-console/**===* -# account info config +# account info config refer the https://hertzbeat.apache.org/docs/start/account-modify # eg: admin has role [admin,user], password is hertzbeat # eg: tom has role [user], password is hertzbeat # eg: lili has role [guest], plain password is lili, salt is 123, salted password is 1A676730B0C7F54654B0E09184448289 @@ -122,16 +122,3 @@ account: - appId: admin credential: hertzbeat role: [admin] - - appId: tom - credential: hertzbeat - role: [user] - - appId: guest - credential: hertzbeat - role: [guest] - - appId: lili - # credential = MD5(password + salt) - # plain password: hertzbeat - # attention: digest authentication does not support salted encrypted password accounts - credential: 94C6B34E7A199A9F9D4E1F208093B489 - salt: 123 - role: [user]