Skip to main content
Glama

Last9 Observability MCP

Official
Apache 2.0
122
45
  • Apple
attributes.go6.49 kB
package logs import ( "bytes" "context" "encoding/json" "fmt" "net/http" "net/url" "time" "last9-mcp/internal/models" "last9-mcp/internal/utils" "github.com/modelcontextprotocol/go-sdk/mcp" ) // GetLogAttributesDescription describes the log attributes tool const GetLogAttributesDescription = ` Fetches available log attributes (labels) for a specified time window. This tool queries the Last9 logs API to retrieve all available attribute names that can be used for filtering and querying logs within the specified time range. The attributes returned are field names that exist in the logs during the specified time window, which can then be used in log queries and filters. Returns a list of attribute names like "service", "severity", "body", "level", etc. ` // GetLogAttributesArgs represents the input arguments for the get_log_attributes tool type GetLogAttributesArgs struct { LookbackMinutes int `json:"lookback_minutes,omitempty"` StartTimeISO string `json:"start_time_iso,omitempty"` EndTimeISO string `json:"end_time_iso,omitempty"` Region string `json:"region,omitempty"` } // NewGetLogAttributesHandler creates a handler for fetching log attributes func NewGetLogAttributesHandler(client *http.Client, cfg models.Config) func(context.Context, *mcp.CallToolRequest, GetLogAttributesArgs) (*mcp.CallToolResult, any, error) { return func(ctx context.Context, req *mcp.CallToolRequest, args GetLogAttributesArgs) (*mcp.CallToolResult, any, error) { // Parse time range parameters now := time.Now() // Default to 15 minutes window startTime := now.Add(-15 * time.Minute).Unix() endTime := now.Unix() // Check for lookback_minutes parameter if args.LookbackMinutes > 0 { startTime = now.Add(-time.Duration(args.LookbackMinutes) * time.Minute).Unix() } // Check for explicit start_time_iso if args.StartTimeISO != "" { if parsed, err := time.Parse("2006-01-02 15:04:05", args.StartTimeISO); err == nil { startTime = parsed.Unix() } } // Check for explicit end_time_iso if args.EndTimeISO != "" { if parsed, err := time.Parse("2006-01-02 15:04:05", args.EndTimeISO); err == nil { endTime = parsed.Unix() } } // Get region parameter or use default from base URL region := utils.GetDefaultRegion(cfg.BaseURL) if args.Region != "" { region = args.Region } // Calculate time range duration in minutes durationMinutes := (endTime - startTime) / 60 // Common query parameters queryParams := url.Values{} queryParams.Set("region", region) queryParams.Set("start", fmt.Sprintf("%d", startTime)) queryParams.Set("end", fmt.Sprintf("%d", endTime)) var httpReq *http.Request var err error if durationMinutes > 20 { // Use GET /logs/api/v1/labels for time ranges > 20 minutes apiURL := fmt.Sprintf("%s/logs/api/v1/labels?%s", cfg.APIBaseURL, queryParams.Encode()) httpReq, err = http.NewRequestWithContext(ctx, "GET", apiURL, nil) } else { // Use POST /logs/api/v2/series/json for time ranges <= 20 minutes apiURL := fmt.Sprintf("%s/logs/api/v2/series/json?%s", cfg.APIBaseURL, queryParams.Encode()) // Create JSON pipeline body pipeline := map[string]interface{}{ "pipeline": []interface{}{}, } jsonBody, _ := json.Marshal(pipeline) httpReq, err = http.NewRequestWithContext(ctx, "POST", apiURL, bytes.NewBuffer(jsonBody)) } if err != nil { return nil, nil, fmt.Errorf("failed to create request: %v", err) } // Set headers httpReq.Header.Set("Accept", "application/json") httpReq.Header.Set("X-LAST9-API-TOKEN", "Bearer "+cfg.AccessToken) httpReq.Header.Set("User-Agent", "Last9-MCP-Server/1.0") httpReq.Header.Set("Content-Type", "application/json") // Execute the request resp, err := client.Do(httpReq) if err != nil { return nil, nil, fmt.Errorf("failed to execute request: %v", err) } defer resp.Body.Close() // Check response status if resp.StatusCode != http.StatusOK { var errorBody map[string]interface{} json.NewDecoder(resp.Body).Decode(&errorBody) return nil, nil, fmt.Errorf("API returned status %d: %v", resp.StatusCode, errorBody) } // Parse the response based on which API was used var result struct { Data []string `json:"data"` Status string `json:"status"` } if durationMinutes > 20 { // Labels API returns array of strings directly if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { return nil, nil, fmt.Errorf("failed to decode response for labels api: %+v", err) } } else { // Series API returns array of objects, extract keys from first object var seriesResponse struct { Data []map[string]interface{} `json:"data"` Status string `json:"status"` } if err := json.NewDecoder(resp.Body).Decode(&seriesResponse); err != nil { return nil, nil, fmt.Errorf("failed to decode response: %v", err) } result.Status = seriesResponse.Status if len(seriesResponse.Data) > 0 { // Extract attribute names (keys) from the first object for key := range seriesResponse.Data[0] { result.Data = append(result.Data, key) } } } // Check API status if result.Status != "success" { return nil, nil, fmt.Errorf("API returned non-success status: %s", result.Status) } // Format the response for display summary := fmt.Sprintf("Found %d attributes in the time window (%s to %s):\n\n", len(result.Data), time.Unix(startTime, 0).Format("2006-01-02 15:04:05"), time.Unix(endTime, 0).Format("2006-01-02 15:04:05")) // Group attributes by category logAttributes := []string{} resourceAttributes := []string{} for _, attr := range result.Data { if len(attr) > 9 && attr[:9] == "resource_" { resourceAttributes = append(resourceAttributes, attr) } else { logAttributes = append(logAttributes, attr) } } // Build formatted output if len(logAttributes) > 0 { summary += fmt.Sprintf("Log Attributes (%d):\n", len(logAttributes)) for _, attr := range logAttributes { summary += fmt.Sprintf(" • %s\n", attr) } } if len(resourceAttributes) > 0 { summary += fmt.Sprintf("\nResource Attributes (%d):\n", len(resourceAttributes)) for _, attr := range resourceAttributes { summary += fmt.Sprintf(" • %s\n", attr) } } // Return the result return &mcp.CallToolResult{ Content: []mcp.Content{ &mcp.TextContent{ Text: summary, }, }, }, 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