feat: support mcp streamable http protocol and upgrade spring ai version (#3890)

Signed-off-by: tomsun28 <tomsun28@outlook.com>
Co-authored-by: shown <yuluo08290126@gmail.com>
This commit is contained in:
Tomsun28
2025-12-08 22:12:35 +08:00
committed by GitHub
co-authored by shown
parent 6b03e4bb5f
commit c88c232889
42 changed files with 578 additions and 908 deletions
+1 -1
View File
@@ -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,
+1 -1
View File
@@ -26,7 +26,7 @@
<artifactId>hertzbeat-ai</artifactId>
<version>${hertzbeat.version}</version>
<properties>
<spring-ai.version>1.0.3</spring-ai.version>
<spring-ai.version>1.1.1</spring-ai.version>
<java.version>17</java.version>
</properties>
@@ -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<ServerResponse> routerFunction;
@Setter
private McpServerSession.Factory sessionFactory;
private final Map<String, Object> sessionRequest = new HashMap<>();
private final ConcurrentHashMap<String, McpServerSession> sessions;
private volatile boolean isClosing;
public CustomSseServerTransport(ObjectMapper objectMapper, String messageEndpoint) {
this(objectMapper, messageEndpoint, "/sse");
}
public CustomSseServerTransport(ObjectMapper objectMapper, String messageEndpoint, String sseEndpoint) {
this(objectMapper, "", messageEndpoint, sseEndpoint);
}
public CustomSseServerTransport(ObjectMapper objectMapper, String baseUrl, String messageEndpoint, String sseEndpoint) {
this.sessions = new ConcurrentHashMap();
this.isClosing = false;
Assert.notNull(objectMapper, "ObjectMapper must not be null");
Assert.notNull(baseUrl, "Message base URL must not be null");
Assert.notNull(messageEndpoint, "Message endpoint must not be null");
Assert.notNull(sseEndpoint, "SSE endpoint must not be null");
this.objectMapper = objectMapper;
this.baseUrl = baseUrl;
this.messageEndpoint = messageEndpoint;
this.sseEndpoint = sseEndpoint;
this.routerFunction = RouterFunctions.route().GET(this.sseEndpoint, this::handleSseConnection).POST(this.messageEndpoint, this::handleMessage).build();
}
public Mono<Void> notifyClients(String method, Object params) {
if (this.sessions.isEmpty()) {
log.debug("No active sessions to broadcast message to");
return Mono.empty();
} else {
log.debug("Attempting to broadcast message to {} active sessions", this.sessions.size());
return Flux.fromIterable(this.sessions.values())
.flatMap((session) -> session.sendNotification(method, params)
.doOnError((e) -> log.error("Failed to send message to session {}: {}", session.getId(), e.getMessage()))
.onErrorComplete())
.then();
}
}
public Mono<Void> closeGracefully() {
return Flux.fromIterable(this.sessions.values()).doFirst(() -> {
this.isClosing = true;
log.debug("Initiating graceful shutdown with {} active sessions", this.sessions.size());
}).flatMap(McpServerSession::closeGracefully).then().doOnSuccess((v) -> log.debug("Graceful shutdown completed"));
}
private ServerResponse handleSseConnection(ServerRequest request) {
log.debug("Handling SSE connection for request: {}", request);
HttpServletRequest servletRequest = request.servletRequest();
try {
log.debug("Processing SSE connection for servlet request: {}", servletRequest);
log.debug("Authorization header: {}", servletRequest.getHeader("Authorization"));
} catch (Exception e) {
log.error("Authentication failed for SSE connection: {}", e.getMessage());
return ServerResponse.status(HttpStatus.UNAUTHORIZED).body("Unauthorized: " + e.getMessage());
}
if (this.isClosing) {
return ServerResponse.status(HttpStatus.SERVICE_UNAVAILABLE).body("Server is shutting down");
} else {
String sessionId = UUID.randomUUID().toString();
log.debug("Generated session ID for SSE connection: {}", sessionId);
log.debug("Creating new SSE connection for session: {}", sessionId);
return ServerResponse.sse((sseBuilder) -> {
sseBuilder.onComplete(() -> {
log.debug("SSE connection completed for session: {}", sessionId);
this.sessions.remove(sessionId);
});
sseBuilder.onTimeout(() -> {
log.debug("SSE connection timed out for session: {}", sessionId);
this.sessions.remove(sessionId);
});
CustomSseServerTransport.WebMvcMcpSessionTransport sessionTransport = new CustomSseServerTransport.WebMvcMcpSessionTransport(sessionId, sseBuilder);
McpServerSession session = this.sessionFactory.create(sessionTransport);
this.sessionRequest.put(sessionId, request.servletRequest());
this.sessions.put(sessionId, session);
try {
sseBuilder.id(sessionId).event("endpoint").data(this.baseUrl + this.messageEndpoint + "?sessionId=" + sessionId);
} catch (Exception e) {
log.error("Failed to send initial endpoint event: {}", e.getMessage());
sseBuilder.error(e);
}
}, Duration.ZERO);
}
}
private ServerResponse handleMessage(ServerRequest request) {
if (this.isClosing) {
return ServerResponse.status(HttpStatus.SERVICE_UNAVAILABLE).body("Server is shutting down");
} else if (request.param("sessionId").isEmpty()) {
return ServerResponse.badRequest().body(new McpError("Session ID missing in message endpoint"));
} else {
String sessionId = (String) request.param("sessionId").get();
McpServerSession session = (McpServerSession) this.sessions.get(sessionId);
log.debug("Authorization header for message request: {}", request.servletRequest().getHeader("Authorization"));
SubjectSum subject = SurenessSecurityManager.getInstance().checkIn(sessionRequest.get(sessionId));
McpContextHolder.setSubject(subject);
if (session == null) {
return ServerResponse.status(HttpStatus.NOT_FOUND).body(new McpError("Session not found: " + sessionId));
} else {
try {
String body = request.body(String.class);
McpSchema.JSONRPCMessage message = McpSchema.deserializeJsonRpcMessage(this.objectMapper, body);
session.handle(message).block();
return ServerResponse.ok().build();
} catch (IOException | IllegalArgumentException e) {
log.error("Failed to deserialize message: {}", ((Exception) e).getMessage());
return ServerResponse.badRequest().body(new McpError("Invalid message format"));
} catch (Exception e) {
log.error("Error handling message: {}", e.getMessage());
return ServerResponse.status(HttpStatus.INTERNAL_SERVER_ERROR).body(new McpError(e.getMessage()));
}
}
}
}
private class WebMvcMcpSessionTransport implements McpServerTransport {
private final String sessionId;
private final ServerResponse.SseBuilder sseBuilder;
WebMvcMcpSessionTransport(String sessionId, ServerResponse.SseBuilder sseBuilder) {
this.sessionId = sessionId;
this.sseBuilder = sseBuilder;
log.debug("Session transport {} initialized with SSE builder", sessionId);
}
public Mono<Void> sendMessage(McpSchema.JSONRPCMessage message) {
return Mono.fromRunnable(() -> {
try {
String jsonText = CustomSseServerTransport.this.objectMapper.writeValueAsString(message);
this.sseBuilder.id(this.sessionId).event("message").data(jsonText);
log.debug("Message sent to session {}", this.sessionId);
} catch (Exception e) {
log.error("Failed to send message to session {}: {}", this.sessionId, e.getMessage());
this.sseBuilder.error(e);
}
});
}
public <T> T unmarshalFrom(Object data, TypeReference<T> typeRef) {
return (T) CustomSseServerTransport.this.objectMapper.convertValue(data, typeRef);
}
public Mono<Void> closeGracefully() {
return Mono.fromRunnable(() -> {
log.debug("Closing session transport: {}", this.sessionId);
try {
this.sseBuilder.complete();
log.debug("Successfully completed SSE builder for session {}", this.sessionId);
} catch (Exception e) {
log.warn("Failed to complete SSE builder for session {}: {}", this.sessionId, e.getMessage());
}
});
}
public void close() {
try {
this.sseBuilder.complete();
log.debug("Successfully completed SSE builder for session {}", this.sessionId);
} catch (Exception e) {
log.warn("Failed to complete SSE builder for session {}: {}", this.sessionId, e.getMessage());
}
}
}
}
@@ -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<ServerResponse> mvcMcpRouterFunction(CustomSseServerTransport transport);
}
@@ -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;
@@ -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<ServerResponse> mvcMcpRouterFunction(CustomSseServerTransport transport) {
return transport.getRouterFunction();
}
}
@@ -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);
}
@@ -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.
@@ -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() {
@@ -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() {
@@ -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<Monitor> 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<Param> 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<Param> 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<ParamDefine> requiredParams = appService.getAppParamDefines(app.toLowerCase().trim());
log.info("Checking required parameters for monitor type '{}': {}", app, requiredParams);
List<String> 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<Param> createBasicParams(String host, Integer port, String username,
String password, String database, String additionalParams) {
List<Param> 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<Param> parseParams(String params) {
List<Param> 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<String> 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<String> splitJsonPairs(String json) {
List<String> 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<String, String> 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<Map.Entry<String, String>> sortedTypes = monitorTypes.entrySet().stream()
.sorted(Map.Entry.comparingByKey())
.toList();
for (Map.Entry<String, String> 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<ParamDefine> 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();
@@ -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;
}
@@ -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<Param> params;
@Schema(description = "Monitor Metrics", accessMode = READ_ONLY)
private List<MetricsInfo> metrics;
@Schema(description = "pinned collector, default null if system dispatch", accessMode = READ_WRITE)
private String collector;
@Schema(description = "grafana dashboard")
private GrafanaDashboard grafanaDashboard;
}
@@ -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);
@@ -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: /**
@@ -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]
+1 -1
View File
@@ -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
+8 -8
View File
@@ -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,
+9 -22
View File
@@ -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
<font color="red">
:::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 <dev@hertzbeat.apache.org> 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 <general@incubator.apache.org> 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.
</font>
:::caution
If you have already voted on <dev@hertzbeat.apache.org>, 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 bindingPPMC 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 <dev@hertzbeat.apache.org> 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.
```
---
+1 -1
View File
@@ -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 <dev@hertzbeat.apache.org>, [GitHub issue](https://github.com/apache/hertzbeat/issues) and [GitHub discussion](https://github.com/apache/hertzbeat/discussions) to do this in a timely manner. |
+14 -14
View File
@@ -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.
+52 -52
View File
@@ -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@<project>.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
```
+65 -9
View File
@@ -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 ServerSent 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 <base64(username:password)>`
### Cursor MCP configuration
### Editor MCP configuration
<Tabs>
<TabItem value="claude-code" label="Claude Code MCP">
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 <base64(username:password)>"
}
}
}
}
```
JWT bearer:
```jsonc
{
"mcpServers": {
"hertzbeat-mcp": {
"type": "sse",
"url": "http://your-hertzbeat-server-host:1157/api/mcp",
"headers": {
"Authorization": "Bearer <your-jwt-token>"
}
}
}
}
```
After saving `~/.claude.json`, restart or reload Claude Code to make the new MCP configuration take effect.
</TabItem>
<TabItem value="cursor" label="Cursor MCP" default>
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 <base64(username:password)>"
}
@@ -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 <your-jwt-token>"
}
@@ -63,6 +116,9 @@ JWT bearer:
After saving, reload MCP in Cursor or restart the editor.
</TabItem>
</Tabs>
### Tools available
#### Monitor Management Tools
@@ -34,7 +34,7 @@ Apache HertzBeat™ 社区努力追求基于功绩的原则。因此,一旦有
## Committer 的要求
没有成为 Committer 或 PPMC 成员的严格规则。新的 Committer 的候选人通常是积极的贡献者和社区成员。但是,如果能稍微明确一些规则,就可以在一定程度上消除贡献者的疑虑,使社区更加透明、合理和公平。
没有成为 Committer 或 PMC 成员的严格规则。新的 Committer 的候选人通常是积极的贡献者和社区成员。但是,如果能稍微明确一些规则,就可以在一定程度上消除贡献者的疑虑,使社区更加透明、合理和公平。
### 持续的贡献
@@ -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 成员的候选人通常是积极的贡献者和社区成员。但是,如果能稍微明确一些规则,就可以在一定程度上消除贡献者的疑虑,使社区更加透明、合理和公平。
### 持续的贡献
@@ -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 在 <dev@hertzbeat.apache.org> HertzBeat 的社区投票时,请带上 binding 后缀,表示对 HertzBeat 社区中的投票具有约束性投票,方便统计投票结果。
IPMC 在 <general@incubator.apache.org> incubator 社区投票,请带上 binding 后缀,表示对 incubator 社区中的投票具有约束性投票,方便统计投票结果。
:::
:::caution 注意
如果在<dev@hertzbeat.apache.org>已经投过票,在incubator社区进行投票回复时,可以直接带过去,<font color="red">需要注意约束性</font> 如:
```html
//incubator社区 投票时,只有IPMC成员才具有约束性 binding,PPMC需要注意约束性的变化
Forward my +1 from dev@hertzbeat (non-binding)
Copy my +1 from hertzbeat DEV ML (non-binding)
```
PMC 在 <dev@hertzbeat.apache.org> 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. ....
```
@@ -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.
@@ -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@<project>.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
```
@@ -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 <base64(username:password)>`
### Cursor MCP 配置
### MCP 配置
在您的主目录或项目根目录中创建或编辑 `.cursor/mcp.json`
<Tabs>
<TabItem value="claude-code" label="Claude Code MCP">
基本身份验证
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 <base64(username:password)>"
}
}
}
}
```
JWT bearer:
```jsonc
{
"mcpServers": {
"hertzbeat-mcp": {
"type": "sse",
"url": "http://your-hertzbeat-server-host:1157/api/mcp",
"headers": {
"Authorization": "Bearer <your-jwt-token>"
}
}
}
}
```
保存 `~/.claude.json` 后,重启或重新加载 Claude Code,让新的 MCP 配置生效。
</TabItem>
<TabItem value="cursor" label="Cursor MCP" default>
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 <base64(username:password)>"
}
@@ -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 <your-jwt-token>"
}
@@ -61,7 +114,10 @@ JWT Bearer
}
```
保存后,在 Cursor 中重新加载 MCP 或重启编辑器。
After saving, reload MCP in Cursor or restart the editor.
</TabItem>
</Tabs>
### 可用工具
+4 -11
View File
@@ -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: /**
@@ -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:
@@ -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]
@@ -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:
@@ -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]
@@ -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:
@@ -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]
@@ -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:
@@ -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]
@@ -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:
@@ -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]
+4 -17
View File
@@ -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]