feat: gate create_silence behind --enable-silences flag
The create_silence tool is a write operation that can suppress alerts. Disable it by default and require explicit opt-in via --enable-silences CLI flag (or enableSilences NixOS option) as a safety measure. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -88,6 +88,10 @@ func serveCommand() *cli.Command {
|
|||||||
Usage: "Session TTL for HTTP transport",
|
Usage: "Session TTL for HTTP transport",
|
||||||
Value: 30 * time.Minute,
|
Value: 30 * time.Minute,
|
||||||
},
|
},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "enable-silences",
|
||||||
|
Usage: "Enable the create_silence tool (write operation, disabled by default)",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
Action: func(c *cli.Context) error {
|
Action: func(c *cli.Context) error {
|
||||||
return runServe(c)
|
return runServe(c)
|
||||||
@@ -177,7 +181,10 @@ func runServe(c *cli.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
server := mcp.NewGenericServer(logger, config)
|
server := mcp.NewGenericServer(logger, config)
|
||||||
monitoring.RegisterHandlers(server, prom, am)
|
opts := monitoring.HandlerOptions{
|
||||||
|
EnableSilences: c.Bool("enable-silences"),
|
||||||
|
}
|
||||||
|
monitoring.RegisterHandlers(server, prom, am, opts)
|
||||||
|
|
||||||
transport := c.String("transport")
|
transport := c.String("transport")
|
||||||
switch transport {
|
switch transport {
|
||||||
|
|||||||
@@ -54,8 +54,15 @@ func AlertSummary(am *AlertmanagerClient) string {
|
|||||||
len(alerts), strings.Join(parts, ", "))
|
len(alerts), strings.Join(parts, ", "))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HandlerOptions configures which handlers are registered.
|
||||||
|
type HandlerOptions struct {
|
||||||
|
// EnableSilences enables the create_silence tool, which is a write operation.
|
||||||
|
// Disabled by default as a safety measure.
|
||||||
|
EnableSilences bool
|
||||||
|
}
|
||||||
|
|
||||||
// RegisterHandlers registers all monitoring tool handlers on the MCP server.
|
// RegisterHandlers registers all monitoring tool handlers on the MCP server.
|
||||||
func RegisterHandlers(server *mcp.Server, prom *PrometheusClient, am *AlertmanagerClient) {
|
func RegisterHandlers(server *mcp.Server, prom *PrometheusClient, am *AlertmanagerClient, opts HandlerOptions) {
|
||||||
server.RegisterTool(listAlertsTool(), makeListAlertsHandler(am))
|
server.RegisterTool(listAlertsTool(), makeListAlertsHandler(am))
|
||||||
server.RegisterTool(getAlertTool(), makeGetAlertHandler(am))
|
server.RegisterTool(getAlertTool(), makeGetAlertHandler(am))
|
||||||
server.RegisterTool(searchMetricsTool(), makeSearchMetricsHandler(prom))
|
server.RegisterTool(searchMetricsTool(), makeSearchMetricsHandler(prom))
|
||||||
@@ -63,7 +70,9 @@ func RegisterHandlers(server *mcp.Server, prom *PrometheusClient, am *Alertmanag
|
|||||||
server.RegisterTool(queryTool(), makeQueryHandler(prom))
|
server.RegisterTool(queryTool(), makeQueryHandler(prom))
|
||||||
server.RegisterTool(listTargetsTool(), makeListTargetsHandler(prom))
|
server.RegisterTool(listTargetsTool(), makeListTargetsHandler(prom))
|
||||||
server.RegisterTool(listSilencesTool(), makeListSilencesHandler(am))
|
server.RegisterTool(listSilencesTool(), makeListSilencesHandler(am))
|
||||||
|
if opts.EnableSilences {
|
||||||
server.RegisterTool(createSilenceTool(), makeCreateSilenceHandler(am))
|
server.RegisterTool(createSilenceTool(), makeCreateSilenceHandler(am))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tool definitions
|
// Tool definitions
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ func setupTestServer(t *testing.T, promHandler, amHandler http.HandlerFunc) (*mc
|
|||||||
|
|
||||||
prom := NewPrometheusClient(promSrv.URL)
|
prom := NewPrometheusClient(promSrv.URL)
|
||||||
am := NewAlertmanagerClient(amSrv.URL)
|
am := NewAlertmanagerClient(amSrv.URL)
|
||||||
RegisterHandlers(server, prom, am)
|
RegisterHandlers(server, prom, am, HandlerOptions{EnableSilences: true})
|
||||||
|
|
||||||
cleanup := func() {
|
cleanup := func() {
|
||||||
promSrv.Close()
|
promSrv.Close()
|
||||||
@@ -304,7 +304,60 @@ func TestHandler_ToolCount(t *testing.T) {
|
|||||||
)
|
)
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
|
|
||||||
// Send a tools/list request
|
tools := listTools(t, server)
|
||||||
|
if len(tools) != 8 {
|
||||||
|
t.Errorf("expected 8 tools with silences enabled, got %d", len(tools))
|
||||||
|
for _, tool := range tools {
|
||||||
|
t.Logf(" tool: %s", tool.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify create_silence is present
|
||||||
|
found := false
|
||||||
|
for _, tool := range tools {
|
||||||
|
if tool.Name == "create_silence" {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
t.Error("expected create_silence tool when silences enabled")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHandler_ToolCountWithoutSilences(t *testing.T) {
|
||||||
|
promSrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
|
||||||
|
amSrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
|
||||||
|
defer promSrv.Close()
|
||||||
|
defer amSrv.Close()
|
||||||
|
|
||||||
|
logger := log.New(io.Discard, "", 0)
|
||||||
|
config := mcp.DefaultMonitoringConfig()
|
||||||
|
server := mcp.NewGenericServer(logger, config)
|
||||||
|
|
||||||
|
prom := NewPrometheusClient(promSrv.URL)
|
||||||
|
am := NewAlertmanagerClient(amSrv.URL)
|
||||||
|
RegisterHandlers(server, prom, am, HandlerOptions{EnableSilences: false})
|
||||||
|
|
||||||
|
tools := listTools(t, server)
|
||||||
|
if len(tools) != 7 {
|
||||||
|
t.Errorf("expected 7 tools without silences, got %d", len(tools))
|
||||||
|
for _, tool := range tools {
|
||||||
|
t.Logf(" tool: %s", tool.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify create_silence is NOT present
|
||||||
|
for _, tool := range tools {
|
||||||
|
if tool.Name == "create_silence" {
|
||||||
|
t.Error("expected create_silence tool to be absent when silences disabled")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func listTools(t *testing.T, server *mcp.Server) []mcp.Tool {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
req := &mcp.Request{
|
req := &mcp.Request{
|
||||||
JSONRPC: "2.0",
|
JSONRPC: "2.0",
|
||||||
ID: 1,
|
ID: 1,
|
||||||
@@ -328,12 +381,7 @@ func TestHandler_ToolCount(t *testing.T) {
|
|||||||
t.Fatalf("failed to unmarshal result: %v", err)
|
t.Fatalf("failed to unmarshal result: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(listResult.Tools) != 8 {
|
return listResult.Tools
|
||||||
t.Errorf("expected 8 tools, got %d", len(listResult.Tools))
|
|
||||||
for _, tool := range listResult.Tools {
|
|
||||||
t.Logf(" tool: %s", tool.Name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// callTool is a test helper that calls a tool through the MCP server.
|
// callTool is a test helper that calls a tool through the MCP server.
|
||||||
|
|||||||
@@ -33,6 +33,12 @@ in
|
|||||||
description = "Alertmanager base URL.";
|
description = "Alertmanager base URL.";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enableSilences = lib.mkOption {
|
||||||
|
type = lib.types.bool;
|
||||||
|
default = false;
|
||||||
|
description = "Enable the create_silence tool (write operation, disabled by default).";
|
||||||
|
};
|
||||||
|
|
||||||
http = {
|
http = {
|
||||||
address = lib.mkOption {
|
address = lib.mkOption {
|
||||||
type = lib.types.str;
|
type = lib.types.str;
|
||||||
@@ -102,8 +108,9 @@ in
|
|||||||
|
|
||||||
script = let
|
script = let
|
||||||
httpFlags = mkHttpFlags cfg.http;
|
httpFlags = mkHttpFlags cfg.http;
|
||||||
|
silenceFlag = lib.optionalString cfg.enableSilences "--enable-silences";
|
||||||
in ''
|
in ''
|
||||||
exec ${cfg.package}/bin/lab-monitoring serve ${httpFlags}
|
exec ${cfg.package}/bin/lab-monitoring serve ${httpFlags} ${silenceFlag}
|
||||||
'';
|
'';
|
||||||
|
|
||||||
serviceConfig = {
|
serviceConfig = {
|
||||||
|
|||||||
Reference in New Issue
Block a user