Skip to content

Sift tools #74

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 25 commits into from
Apr 23, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 14 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,14 @@ This provides access to your Grafana instance and the surrounding ecosystem.
- [x] Label values
- [x] Stats
- [x] Search, create, update and close incidents
- [ ] Start Sift investigations and view the results
- [x] Start Sift investigations and view the results
- [x] Create Investigations
- [x] List Investigations with a limit parameter
- [x] Get Investigation
- [x] Get Analyses
- [x] Find error patterns in logs using Sift
- [x] Find slow requests using Sift
- [ ] Add tools on the other Sift Checks
- [ ] Alerting
- [x] List and fetch alert rule information
- [x] Get alert rule statuses (firing/normal/error/etc.)
Expand Down Expand Up @@ -70,10 +77,15 @@ the OnCall tools, use `--disable-oncall`.
| `list_alert_rules` | Alerting | List alert rules |
| `get_alert_rule_by_uid` | Alerting | Get alert rule by UID |
| `list_oncall_schedules` | OnCall | List schedules from Grafana OnCall |
| `get_oncall_shift` | OnCall | Get details for a specific OnCall shift |
| `get_oncall_shift` | OnCall | Get details for a specific OnCall shift |
| `get_current_oncall_users` | OnCall | Get users currently on-call for a specific schedule |
| `list_oncall_teams` | OnCall | List teams from Grafana OnCall |
| `list_oncall_users` | OnCall | List users from Grafana OnCall |
| `get_investigation` | Sift | Retrieve an existing Sift investigation by its UUID |
| `get_analysis` | Sift | Retrieve a specific analysis from a Sift investigation |
| `list_investigations` | Sift | Retrieve a list of Sift investigations with an optional limit |
| `find_error_pattern_logs` | Sift | Finds elevated error patterns in Loki logs. |
| `find_slow_requests` | Sift | Finds slow requests from the relevant tempo datasources. |

## Usage

Expand Down
4 changes: 3 additions & 1 deletion cmd/mcp-grafana/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ type disabledTools struct {

search, datasource, incident,
prometheus, loki, alerting,
dashboard, oncall bool
dashboard, oncall, sift bool
}

// Configuration for the Grafana client.
Expand All @@ -54,6 +54,7 @@ func (dt *disabledTools) addFlags() {
flag.BoolVar(&dt.alerting, "disable-alerting", false, "Disable alerting tools")
flag.BoolVar(&dt.dashboard, "disable-dashboard", false, "Disable dashboard tools")
flag.BoolVar(&dt.oncall, "disable-oncall", false, "Disable oncall tools")
flag.BoolVar(&dt.sift, "disable-sift", false, "Disable sift tools")
}

func (gc *grafanaConfig) addFlags() {
Expand All @@ -70,6 +71,7 @@ func (dt *disabledTools) addTools(s *server.MCPServer) {
maybeAddTools(s, tools.AddAlertingTools, enabledTools, dt.alerting, "alerting")
maybeAddTools(s, tools.AddDashboardTools, enabledTools, dt.dashboard, "dashboard")
maybeAddTools(s, tools.AddOnCallTools, enabledTools, dt.oncall, "oncall")
maybeAddTools(s, tools.AddSiftTools, enabledTools, dt.sift, "sift")
}

func newServer(dt disabledTools) *server.MCPServer {
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ go 1.24.0

require (
github.com/go-openapi/strfmt v0.23.0
github.com/google/uuid v1.6.0
github.com/grafana/amixr-api-go-client v0.0.21
github.com/grafana/grafana-openapi-client-go v0.0.0-20250108132429-8d7e1f158f65
github.com/grafana/incident-go v0.0.0-20250211094540-dc6a98fdae43
Expand Down Expand Up @@ -34,7 +35,6 @@ require (
github.com/go-openapi/swag v0.23.0 // indirect
github.com/go-openapi/validate v0.24.0 // indirect
github.com/google/go-querystring v1.1.0 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-retryablehttp v0.7.7 // indirect
Expand Down
33 changes: 33 additions & 0 deletions tools/cloud_testing_utils.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
//go:build cloud
// +build cloud

package tools

import (
"context"
"os"
"testing"

mcpgrafana "github.com/grafana/mcp-grafana"
)

// createCloudTestContext creates a context with Grafana URL and API key for cloud integration tests.
// The test will be skipped if required environment variables are not set.
// testName is used to customize the skip message (e.g. "OnCall", "Sift", "Incident")
func createCloudTestContext(t *testing.T, testName string) context.Context {
grafanaURL := os.Getenv("GRAFANA_URL")
if grafanaURL == "" {
t.Skipf("GRAFANA_URL environment variable not set, skipping cloud %s integration tests", testName)
}

grafanaApiKey := os.Getenv("GRAFANA_API_KEY")
if grafanaApiKey == "" {
t.Skipf("GRAFANA_API_KEY environment variable not set, skipping cloud %s integration tests", testName)
}

ctx := context.Background()
ctx = mcpgrafana.WithGrafanaURL(ctx, grafanaURL)
ctx = mcpgrafana.WithGrafanaAPIKey(ctx, grafanaApiKey)

return ctx
}
39 changes: 11 additions & 28 deletions tools/incident_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,45 +3,27 @@
//go:build cloud
// +build cloud

// This file contains cloud integration tests that run against a dedicated test instance
// at mcptests.grafana-dev.net. This instance is configured with a minimal setup on the Incident side
// with two incidents created, one minor and one major, and both of them resolved.
// These tests expect this configuration to exist and will skip if the required
// environment variables (GRAFANA_URL, GRAFANA_API_KEY) are not set.

package tools

import (
"context"
"os"
"testing"

mcpgrafana "github.com/grafana/mcp-grafana"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

// This file contains cloud integration tests that run against a dedicated test instance
// at mcptests.grafana-dev.net. This instance is configured with a minimal setup on the Incident side
// with two incidents created, one minor and one major, and both of them resolved.
// These tests expect this configuration to exist and will skip if the required
// environment variables (GRAFANA_URL, GRAFANA_API_KEY) are not set.

func createCloudTestContext(t *testing.T) context.Context {
grafanaURL := os.Getenv("GRAFANA_URL")
if grafanaURL == "" {
t.Skip("GRAFANA_URL environment variable not set, skipping cloud Incident integration tests")
}

grafanaApiKey := os.Getenv("GRAFANA_API_KEY")
if grafanaApiKey == "" {
t.Skip("GRAFANA_API_KEY environment variable not set, skipping cloud Incident integration tests")
}

ctx := context.Background()
ctx = mcpgrafana.WithGrafanaURL(ctx, grafanaURL)
ctx = mcpgrafana.WithGrafanaAPIKey(ctx, grafanaApiKey)
ctx = mcpgrafana.ExtractIncidentClientFromEnv(ctx)
return ctx
}

func TestCloudIncidentTools(t *testing.T) {
t.Run("list incidents", func(t *testing.T) {
ctx := createCloudTestContext(t)
ctx := createCloudTestContext(t, "Incident")
ctx = mcpgrafana.ExtractIncidentClientFromEnv(ctx)

result, err := listIncidents(ctx, ListIncidentsParams{
Limit: 1,
})
Expand All @@ -52,7 +34,8 @@ func TestCloudIncidentTools(t *testing.T) {
})

t.Run("get incident by ID", func(t *testing.T) {
ctx := createCloudTestContext(t)
ctx := createCloudTestContext(t, "Incident")
ctx = mcpgrafana.ExtractIncidentClientFromEnv(ctx)
result, err := getIncident(ctx, GetIncidentParams{
ID: "1",
})
Expand Down
31 changes: 5 additions & 26 deletions tools/oncall_cloud_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,35 +13,14 @@
package tools

import (
"context"
"os"
"testing"

mcpgrafana "github.com/grafana/mcp-grafana"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func createOnCallCloudTestContext(t *testing.T) context.Context {
grafanaURL := os.Getenv("GRAFANA_URL")
if grafanaURL == "" {
t.Skip("GRAFANA_URL environment variable not set, skipping cloud OnCall integration tests")
}

grafanaApiKey := os.Getenv("GRAFANA_API_KEY")
if grafanaApiKey == "" {
t.Skip("GRAFANA_API_KEY environment variable not set, skipping cloud OnCall integration tests")
}

ctx := context.Background()
ctx = mcpgrafana.WithGrafanaURL(ctx, grafanaURL)
ctx = mcpgrafana.WithGrafanaAPIKey(ctx, grafanaApiKey)

return ctx
}

func TestCloudOnCallSchedules(t *testing.T) {
ctx := createOnCallCloudTestContext(t)
ctx := createCloudTestContext(t, "OnCall")

// Test listing all schedules
t.Run("list all schedules", func(t *testing.T) {
Expand Down Expand Up @@ -104,7 +83,7 @@ func TestCloudOnCallSchedules(t *testing.T) {
}

func TestCloudOnCallShift(t *testing.T) {
ctx := createOnCallCloudTestContext(t)
ctx := createCloudTestContext(t, "OnCall")

// First get a schedule to find a valid shift
schedules, err := listOnCallSchedules(ctx, ListOnCallSchedulesParams{})
Expand Down Expand Up @@ -134,7 +113,7 @@ func TestCloudOnCallShift(t *testing.T) {
}

func TestCloudGetCurrentOnCallUsers(t *testing.T) {
ctx := createOnCallCloudTestContext(t)
ctx := createCloudTestContext(t, "OnCall")

// First get a schedule to use for testing
schedules, err := listOnCallSchedules(ctx, ListOnCallSchedulesParams{})
Expand Down Expand Up @@ -171,7 +150,7 @@ func TestCloudGetCurrentOnCallUsers(t *testing.T) {
}

func TestCloudOnCallTeams(t *testing.T) {
ctx := createOnCallCloudTestContext(t)
ctx := createCloudTestContext(t, "OnCall")

t.Run("list teams", func(t *testing.T) {
result, err := listOnCallTeams(ctx, ListOnCallTeamsParams{})
Expand Down Expand Up @@ -200,7 +179,7 @@ func TestCloudOnCallTeams(t *testing.T) {
}

func TestCloudOnCallUsers(t *testing.T) {
ctx := createOnCallCloudTestContext(t)
ctx := createCloudTestContext(t, "OnCall")

t.Run("list all users", func(t *testing.T) {
result, err := listOnCallUsers(ctx, ListOnCallUsersParams{})
Expand Down
Loading