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:
2026-02-04 23:23:46 +01:00
parent 2a08cdaf2e
commit 06e62eb6ad
4 changed files with 85 additions and 14 deletions

View File

@@ -88,6 +88,10 @@ func serveCommand() *cli.Command {
Usage: "Session TTL for HTTP transport",
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 {
return runServe(c)
@@ -177,7 +181,10 @@ func runServe(c *cli.Context) error {
}
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")
switch transport {

View File

@@ -18,8 +18,8 @@ func AlertSummary(am *AlertmanagerClient) string {
silenced := false
inhibited := false
alerts, err := am.ListAlerts(context.Background(), AlertFilters{
Active: &active,
Silenced: &silenced,
Active: &active,
Silenced: &silenced,
Inhibited: &inhibited,
})
if err != nil || len(alerts) == 0 {
@@ -54,8 +54,15 @@ func AlertSummary(am *AlertmanagerClient) string {
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.
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(getAlertTool(), makeGetAlertHandler(am))
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(listTargetsTool(), makeListTargetsHandler(prom))
server.RegisterTool(listSilencesTool(), makeListSilencesHandler(am))
server.RegisterTool(createSilenceTool(), makeCreateSilenceHandler(am))
if opts.EnableSilences {
server.RegisterTool(createSilenceTool(), makeCreateSilenceHandler(am))
}
}
// Tool definitions

View File

@@ -26,7 +26,7 @@ func setupTestServer(t *testing.T, promHandler, amHandler http.HandlerFunc) (*mc
prom := NewPrometheusClient(promSrv.URL)
am := NewAlertmanagerClient(amSrv.URL)
RegisterHandlers(server, prom, am)
RegisterHandlers(server, prom, am, HandlerOptions{EnableSilences: true})
cleanup := func() {
promSrv.Close()
@@ -304,7 +304,60 @@ func TestHandler_ToolCount(t *testing.T) {
)
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{
JSONRPC: "2.0",
ID: 1,
@@ -328,12 +381,7 @@ func TestHandler_ToolCount(t *testing.T) {
t.Fatalf("failed to unmarshal result: %v", err)
}
if len(listResult.Tools) != 8 {
t.Errorf("expected 8 tools, got %d", len(listResult.Tools))
for _, tool := range listResult.Tools {
t.Logf(" tool: %s", tool.Name)
}
}
return listResult.Tools
}
// callTool is a test helper that calls a tool through the MCP server.

View File

@@ -33,6 +33,12 @@ in
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 = {
address = lib.mkOption {
type = lib.types.str;
@@ -102,8 +108,9 @@ in
script = let
httpFlags = mkHttpFlags cfg.http;
silenceFlag = lib.optionalString cfg.enableSilences "--enable-silences";
in ''
exec ${cfg.package}/bin/lab-monitoring serve ${httpFlags}
exec ${cfg.package}/bin/lab-monitoring serve ${httpFlags} ${silenceFlag}
'';
serviceConfig = {