feat: include active alert count in MCP server instructions
Add InstructionsFunc callback to ServerConfig, called during each initialize handshake to generate dynamic instructions. The lab-monitoring server uses this to query Alertmanager and include a count of active non-silenced alerts, so the LLM can proactively inform the user. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -168,10 +168,15 @@ func runServe(c *cli.Context) error {
|
|||||||
|
|
||||||
logger := log.New(os.Stderr, "[mcp] ", log.LstdFlags)
|
logger := log.New(os.Stderr, "[mcp] ", log.LstdFlags)
|
||||||
config := mcp.DefaultMonitoringConfig()
|
config := mcp.DefaultMonitoringConfig()
|
||||||
server := mcp.NewGenericServer(logger, config)
|
|
||||||
|
|
||||||
prom := monitoring.NewPrometheusClient(c.String("prometheus-url"))
|
prom := monitoring.NewPrometheusClient(c.String("prometheus-url"))
|
||||||
am := monitoring.NewAlertmanagerClient(c.String("alertmanager-url"))
|
am := monitoring.NewAlertmanagerClient(c.String("alertmanager-url"))
|
||||||
|
|
||||||
|
config.InstructionsFunc = func() string {
|
||||||
|
return monitoring.AlertSummary(am)
|
||||||
|
}
|
||||||
|
|
||||||
|
server := mcp.NewGenericServer(logger, config)
|
||||||
monitoring.RegisterHandlers(server, prom, am)
|
monitoring.RegisterHandlers(server, prom, am)
|
||||||
|
|
||||||
transport := c.String("transport")
|
transport := c.String("transport")
|
||||||
|
|||||||
@@ -30,6 +30,9 @@ type ServerConfig struct {
|
|||||||
Version string
|
Version string
|
||||||
// Instructions are the server instructions sent to clients.
|
// Instructions are the server instructions sent to clients.
|
||||||
Instructions string
|
Instructions string
|
||||||
|
// InstructionsFunc, if set, is called during initialization to generate
|
||||||
|
// dynamic instructions. Its return value is appended to Instructions.
|
||||||
|
InstructionsFunc func() string
|
||||||
// DefaultChannel is the default channel to use when no revision is specified.
|
// DefaultChannel is the default channel to use when no revision is specified.
|
||||||
DefaultChannel string
|
DefaultChannel string
|
||||||
// SourceName is the name of the source repository (e.g., "nixpkgs", "home-manager").
|
// SourceName is the name of the source repository (e.g., "nixpkgs", "home-manager").
|
||||||
@@ -244,6 +247,13 @@ func (s *Server) handleInitialize(req *Request) *Response {
|
|||||||
s.logger.Printf("Client: %s %s, protocol: %s",
|
s.logger.Printf("Client: %s %s, protocol: %s",
|
||||||
params.ClientInfo.Name, params.ClientInfo.Version, params.ProtocolVersion)
|
params.ClientInfo.Name, params.ClientInfo.Version, params.ProtocolVersion)
|
||||||
|
|
||||||
|
instructions := s.config.Instructions
|
||||||
|
if s.config.InstructionsFunc != nil {
|
||||||
|
if extra := s.config.InstructionsFunc(); extra != "" {
|
||||||
|
instructions += "\n\n" + extra
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
result := InitializeResult{
|
result := InitializeResult{
|
||||||
ProtocolVersion: ProtocolVersion,
|
ProtocolVersion: ProtocolVersion,
|
||||||
Capabilities: Capabilities{
|
Capabilities: Capabilities{
|
||||||
@@ -255,7 +265,7 @@ func (s *Server) handleInitialize(req *Request) *Response {
|
|||||||
Name: s.config.Name,
|
Name: s.config.Name,
|
||||||
Version: s.config.Version,
|
Version: s.config.Version,
|
||||||
},
|
},
|
||||||
Instructions: s.config.Instructions,
|
Instructions: instructions,
|
||||||
}
|
}
|
||||||
|
|
||||||
return &Response{
|
return &Response{
|
||||||
|
|||||||
@@ -10,6 +10,50 @@ import (
|
|||||||
"git.t-juice.club/torjus/labmcp/internal/mcp"
|
"git.t-juice.club/torjus/labmcp/internal/mcp"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// AlertSummary queries Alertmanager for active (non-silenced) alerts and returns
|
||||||
|
// a short summary string. Returns empty string if there are no alerts or if
|
||||||
|
// Alertmanager is unreachable.
|
||||||
|
func AlertSummary(am *AlertmanagerClient) string {
|
||||||
|
active := true
|
||||||
|
silenced := false
|
||||||
|
inhibited := false
|
||||||
|
alerts, err := am.ListAlerts(context.Background(), AlertFilters{
|
||||||
|
Active: &active,
|
||||||
|
Silenced: &silenced,
|
||||||
|
Inhibited: &inhibited,
|
||||||
|
})
|
||||||
|
if err != nil || len(alerts) == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// Count by severity
|
||||||
|
severities := make(map[string]int)
|
||||||
|
for _, a := range alerts {
|
||||||
|
sev := a.Labels["severity"]
|
||||||
|
if sev == "" {
|
||||||
|
sev = "unknown"
|
||||||
|
}
|
||||||
|
severities[sev]++
|
||||||
|
}
|
||||||
|
|
||||||
|
var parts []string
|
||||||
|
// Show critical first if present
|
||||||
|
if n, ok := severities["critical"]; ok {
|
||||||
|
parts = append(parts, fmt.Sprintf("%d critical", n))
|
||||||
|
delete(severities, "critical")
|
||||||
|
}
|
||||||
|
if n, ok := severities["warning"]; ok {
|
||||||
|
parts = append(parts, fmt.Sprintf("%d warning", n))
|
||||||
|
delete(severities, "warning")
|
||||||
|
}
|
||||||
|
for sev, n := range severities {
|
||||||
|
parts = append(parts, fmt.Sprintf("%d %s", n, sev))
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf("ALERT STATUS: %d active alert(s) firing (%s). Use list_alerts for details. Let the user know about this.",
|
||||||
|
len(alerts), strings.Join(parts, ", "))
|
||||||
|
}
|
||||||
|
|
||||||
// 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) {
|
||||||
server.RegisterTool(listAlertsTool(), makeListAlertsHandler(am))
|
server.RegisterTool(listAlertsTool(), makeListAlertsHandler(am))
|
||||||
|
|||||||
Reference in New Issue
Block a user