Skip to main content
Glama

Last9 Observability MCP

Official
Apache 2.0
122
45
  • Apple
alerts.go13.7 kB
package alerting import ( "context" "encoding/json" "fmt" "io" "last9-mcp/internal/models" "net/http" "time" "github.com/modelcontextprotocol/go-sdk/mcp" ) // AlertRule represents an alert configuration from Last9 API type AlertRule struct { ID string `json:"id"` OrganizationID string `json:"organization_id"` EntityID string `json:"entity_id"` PrimaryIndicator string `json:"primary_indicator"` CreatedAt int64 `json:"created_at"` UpdatedAt int64 `json:"updated_at"` DeletedAt *int64 `json:"deleted_at"` ErrorSince *int64 `json:"error_since"` State string `json:"state"` ExternalRef string `json:"external_ref"` Severity string `json:"severity"` Algorithm string `json:"algorithm"` RuleName string `json:"rule_name"` MuteUntil int64 `json:"mute_until"` Properties map[string]interface{} `json:"properties"` GroupTimeseriesNotifications bool `json:"group_timeseries_notifications"` } // Alert represents an active alert instance type Alert struct { ID string `json:"id"` RuleID string `json:"rule_id"` RuleName string `json:"rule_name"` State string `json:"state"` Severity string `json:"severity"` StartsAt string `json:"starts_at"` EndsAt string `json:"ends_at"` Labels map[string]string `json:"labels"` Annotations map[string]string `json:"annotations"` GeneratorURL string `json:"generator_url"` Fingerprint string `json:"fingerprint"` } // AlertConfigResponse represents the response from alert rules API (direct array) type AlertConfigResponse []AlertRule // AlertsResponse represents the response from alerts monitoring API type AlertsResponse struct { Timestamp int64 `json:"timestamp"` Window int64 `json:"window"` AlertRules []AlertRuleData `json:"alert_rules"` } type AlertRuleData struct { AlertGroupID string `json:"alert_group_id"` AlertGroupName string `json:"alert_group_name"` RuleID string `json:"rule_id"` RuleName string `json:"rule_name"` State string `json:"state"` Severity string `json:"severity"` RuleType string `json:"rule_type"` LastFiredAt int64 `json:"last_fired_at"` Since int64 `json:"since"` Alerts []AlertInstance `json:"alerts"` RuleProperties map[string]interface{} `json:"rule_properties"` } type AlertInstance struct { State string `json:"state"` LabelHash string `json:"label_hash"` Annotations map[string]interface{} `json:"annotations"` GroupLabels map[string]interface{} `json:"group_labels"` MetricDegradation float64 `json:"metric_degradation"` CurrentValue float64 `json:"current_value"` LastFiredAt int64 `json:"last_fired_at"` Since int64 `json:"since"` } const GetAlertConfigDescription = ` Get alert configurations (alert rules) from Last9. Returns all configured alert rules including their conditions, labels, and annotations. Each alert rule includes: - id: Unique identifier for the alert rule - name: Human-readable name of the alert - description: Detailed description of what the alert monitors - state: Current state of the alert rule (active, inactive, etc.) - severity: Alert severity level (critical, warning, info) - query: PromQL query used for the alert condition - for: Duration threshold before alert fires - labels: Key-value pairs for alert routing and grouping - annotations: Additional metadata and descriptions - group_name: Alert group this rule belongs to - condition: Alert condition configuration (thresholds, operators) - created_at: When the alert rule was created - updated_at: When the alert rule was last modified ` const GetAlertsDescription = ` Get currently active alerts from Last9 monitoring system. Returns all alerts that are currently firing or have fired recently within the specified time window. Parameters: - timestamp: Unix timestamp for the query time (defaults to current time) - window: Time window in seconds to look back for alerts (defaults to 900 seconds = 15 minutes) Each alert includes: - id: Unique identifier for this alert instance - rule_id: ID of the alert rule that triggered this alert - rule_name: Name of the alert rule - state: Current state (firing, resolved, pending) - severity: Alert severity level - starts_at: When this alert instance started firing - ends_at: When this alert instance was resolved (if resolved) - labels: Key-value pairs for alert identification and routing - annotations: Additional context and descriptions - generator_url: URL to the source of the alert - fingerprint: Unique fingerprint for this alert instance ` type GetAlertConfigArgs struct{} func NewGetAlertConfigHandler(client *http.Client, cfg models.Config) func(context.Context, *mcp.CallToolRequest, GetAlertConfigArgs) (*mcp.CallToolResult, any, error) { return func(ctx context.Context, req *mcp.CallToolRequest, args GetAlertConfigArgs) (*mcp.CallToolResult, any, error) { // Build the URL for alert rules API url := fmt.Sprintf("%s/alert-rules", cfg.APIBaseURL) // Create HTTP request httpReq, err := http.NewRequestWithContext(ctx, "GET", url, nil) if err != nil { return nil, nil, fmt.Errorf("failed to create request: %w", err) } // Set headers httpReq.Header.Set("Accept", "application/json") httpReq.Header.Set("X-LAST9-API-TOKEN", "Bearer "+cfg.AccessToken) // Make the request resp, err := client.Do(httpReq) if err != nil { return nil, nil, fmt.Errorf("failed to make request: %w", err) } defer resp.Body.Close() // Check response status if resp.StatusCode != http.StatusOK { body, _ := io.ReadAll(resp.Body) return nil, nil, fmt.Errorf("API request failed with status %d: %s", resp.StatusCode, string(body)) } // Read response body body, err := io.ReadAll(resp.Body) if err != nil { return nil, nil, fmt.Errorf("failed to read response: %w", err) } // Parse JSON response var alertConfig AlertConfigResponse if err := json.Unmarshal(body, &alertConfig); err != nil { return nil, nil, fmt.Errorf("failed to parse response: %w", err) } // Format the response formattedResponse := fmt.Sprintf("Found %d alert rules:\n\n", len(alertConfig)) for i, rule := range alertConfig { formattedResponse += fmt.Sprintf("Alert Rule %d:\n", i+1) formattedResponse += fmt.Sprintf(" ID: %s\n", rule.ID) formattedResponse += fmt.Sprintf(" Rule Name: %s\n", rule.RuleName) formattedResponse += fmt.Sprintf(" Primary Indicator: %s\n", rule.PrimaryIndicator) formattedResponse += fmt.Sprintf(" State: %s\n", rule.State) formattedResponse += fmt.Sprintf(" Severity: %s\n", rule.Severity) formattedResponse += fmt.Sprintf(" Algorithm: %s\n", rule.Algorithm) formattedResponse += fmt.Sprintf(" Entity ID: %s\n", rule.EntityID) if rule.ErrorSince != nil { errorTime := time.Unix(*rule.ErrorSince, 0).UTC().Format("2006-01-02 15:04:05 UTC") formattedResponse += fmt.Sprintf(" Error Since: %s\n", errorTime) } if rule.Properties != nil && len(rule.Properties) > 0 { formattedResponse += " Properties:\n" for k, v := range rule.Properties { formattedResponse += fmt.Sprintf(" %s: %v\n", k, v) } } createdTime := time.Unix(rule.CreatedAt, 0).UTC().Format("2006-01-02 15:04:05 UTC") updatedTime := time.Unix(rule.UpdatedAt, 0).UTC().Format("2006-01-02 15:04:05 UTC") formattedResponse += fmt.Sprintf(" Created: %s\n", createdTime) formattedResponse += fmt.Sprintf(" Updated: %s\n", updatedTime) formattedResponse += fmt.Sprintf(" Group Timeseries Notifications: %v\n", rule.GroupTimeseriesNotifications) formattedResponse += "\n" } return &mcp.CallToolResult{ Content: []mcp.Content{ &mcp.TextContent{ Text: formattedResponse, }, }, }, nil, nil } } type GetAlertsArgs struct { Timestamp float64 `json:"timestamp,omitempty" jsonschema:"Unix timestamp for query time (defaults to current time)"` Window float64 `json:"window,omitempty" jsonschema:"Time window in seconds (default: 900, range: 60-86400)"` } func NewGetAlertsHandler(client *http.Client, cfg models.Config) func(context.Context, *mcp.CallToolRequest, GetAlertsArgs) (*mcp.CallToolResult, any, error) { return func(ctx context.Context, req *mcp.CallToolRequest, args GetAlertsArgs) (*mcp.CallToolResult, any, error) { // Parse timestamp parameter (defaults to current time) timestamp := time.Now().Unix() if args.Timestamp != 0 { timestamp = int64(args.Timestamp) } // Parse window parameter (defaults to 900 seconds = 15 minutes) window := int64(900) if args.Window != 0 { window = int64(args.Window) } // Build the URL for alerts monitoring API url := fmt.Sprintf("%s/alerts/monitor?timestamp=%d&window=%d", cfg.APIBaseURL, timestamp, window) // Create HTTP request httpReq, err := http.NewRequestWithContext(ctx, "GET", url, nil) if err != nil { return nil, nil, fmt.Errorf("failed to create request: %w", err) } // Set headers httpReq.Header.Set("Accept", "application/json") httpReq.Header.Set("X-LAST9-API-TOKEN", "Bearer "+cfg.AccessToken) // Make the request resp, err := client.Do(httpReq) if err != nil { return nil, nil, fmt.Errorf("failed to make request: %w", err) } defer resp.Body.Close() // Check response status if resp.StatusCode != http.StatusOK { body, _ := io.ReadAll(resp.Body) return nil, nil, fmt.Errorf("API request failed with status %d: %s", resp.StatusCode, string(body)) } // Read response body body, err := io.ReadAll(resp.Body) if err != nil { return nil, nil, fmt.Errorf("failed to read response: %w", err) } // Parse JSON response var alertsResp AlertsResponse if err := json.Unmarshal(body, &alertsResp); err != nil { return nil, nil, fmt.Errorf("failed to parse response: %w", err) } // Format the response timeStr := time.Unix(alertsResp.Timestamp, 0).UTC().Format("2006-01-02 15:04:05 UTC") formattedResponse := fmt.Sprintf("Alerts for timestamp %s (window: %d seconds):\n", timeStr, alertsResp.Window) totalAlertInstances := 0 for _, rule := range alertsResp.AlertRules { totalAlertInstances += len(rule.Alerts) } formattedResponse += fmt.Sprintf("Found %d alert rule(s) with %d alert instance(s):\n\n", len(alertsResp.AlertRules), totalAlertInstances) if len(alertsResp.AlertRules) == 0 { formattedResponse += "No alerts found in the specified time window.\n" } else { for i, rule := range alertsResp.AlertRules { formattedResponse += fmt.Sprintf("Alert Rule %d:\n", i+1) formattedResponse += fmt.Sprintf(" Rule ID: %s\n", rule.RuleID) formattedResponse += fmt.Sprintf(" Rule Name: %s\n", rule.RuleName) formattedResponse += fmt.Sprintf(" Alert Group: %s\n", rule.AlertGroupName) formattedResponse += fmt.Sprintf(" State: %s\n", rule.State) formattedResponse += fmt.Sprintf(" Severity: %s\n", rule.Severity) formattedResponse += fmt.Sprintf(" Rule Type: %s\n", rule.RuleType) if rule.LastFiredAt > 0 { lastFired := time.Unix(rule.LastFiredAt, 0).UTC().Format("2006-01-02 15:04:05 UTC") formattedResponse += fmt.Sprintf(" Last Fired At: %s\n", lastFired) } if rule.Since > 0 { since := time.Unix(rule.Since, 0).UTC().Format("2006-01-02 15:04:05 UTC") formattedResponse += fmt.Sprintf(" Since: %s\n", since) } if len(rule.RuleProperties) > 0 { formattedResponse += " Rule Properties:\n" for k, v := range rule.RuleProperties { formattedResponse += fmt.Sprintf(" %s: %v\n", k, v) } } if len(rule.Alerts) > 0 { formattedResponse += fmt.Sprintf(" Alert Instances (%d):\n", len(rule.Alerts)) for j, alert := range rule.Alerts { formattedResponse += fmt.Sprintf(" Instance %d:\n", j+1) formattedResponse += fmt.Sprintf(" State: %s\n", alert.State) formattedResponse += fmt.Sprintf(" Current Value: %.4f\n", alert.CurrentValue) formattedResponse += fmt.Sprintf(" Metric Degradation: %.4f\n", alert.MetricDegradation) if len(alert.GroupLabels) > 0 { formattedResponse += " Group Labels:\n" for k, v := range alert.GroupLabels { formattedResponse += fmt.Sprintf(" %s: %v\n", k, v) } } if len(alert.Annotations) > 0 { formattedResponse += " Annotations:\n" for k, v := range alert.Annotations { formattedResponse += fmt.Sprintf(" %s: %v\n", k, v) } } } } formattedResponse += "\n" } } return &mcp.CallToolResult{ Content: []mcp.Content{ &mcp.TextContent{ Text: formattedResponse, }, }, }, nil, nil } }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/last9/last9-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server