Skip to content

Commit 618d957

Browse files
authored
Sift tools (#74)
Add sift tools for error pattern logs and slow requests.
1 parent 8864030 commit 618d957

File tree

8 files changed

+727
-58
lines changed

8 files changed

+727
-58
lines changed

README.md

+14-2
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,14 @@ This provides access to your Grafana instance and the surrounding ecosystem.
2626
- [x] Label values
2727
- [x] Stats
2828
- [x] Search, create, update and close incidents
29-
- [ ] Start Sift investigations and view the results
29+
- [x] Start Sift investigations and view the results
30+
- [x] Create Investigations
31+
- [x] List Investigations with a limit parameter
32+
- [x] Get Investigation
33+
- [x] Get Analyses
34+
- [x] Find error patterns in logs using Sift
35+
- [x] Find slow requests using Sift
36+
- [ ] Add tools on the other Sift Checks
3037
- [ ] Alerting
3138
- [x] List and fetch alert rule information
3239
- [x] Get alert rule statuses (firing/normal/error/etc.)
@@ -70,10 +77,15 @@ the OnCall tools, use `--disable-oncall`.
7077
| `list_alert_rules` | Alerting | List alert rules |
7178
| `get_alert_rule_by_uid` | Alerting | Get alert rule by UID |
7279
| `list_oncall_schedules` | OnCall | List schedules from Grafana OnCall |
73-
| `get_oncall_shift` | OnCall | Get details for a specific OnCall shift |
80+
| `get_oncall_shift` | OnCall | Get details for a specific OnCall shift |
7481
| `get_current_oncall_users` | OnCall | Get users currently on-call for a specific schedule |
7582
| `list_oncall_teams` | OnCall | List teams from Grafana OnCall |
7683
| `list_oncall_users` | OnCall | List users from Grafana OnCall |
84+
| `get_investigation` | Sift | Retrieve an existing Sift investigation by its UUID |
85+
| `get_analysis` | Sift | Retrieve a specific analysis from a Sift investigation |
86+
| `list_investigations` | Sift | Retrieve a list of Sift investigations with an optional limit |
87+
| `find_error_pattern_logs` | Sift | Finds elevated error patterns in Loki logs. |
88+
| `find_slow_requests` | Sift | Finds slow requests from the relevant tempo datasources. |
7789

7890
## Usage
7991

cmd/mcp-grafana/main.go

+3-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ type disabledTools struct {
3434

3535
search, datasource, incident,
3636
prometheus, loki, alerting,
37-
dashboard, oncall bool
37+
dashboard, oncall, sift bool
3838
}
3939

4040
// Configuration for the Grafana client.
@@ -54,6 +54,7 @@ func (dt *disabledTools) addFlags() {
5454
flag.BoolVar(&dt.alerting, "disable-alerting", false, "Disable alerting tools")
5555
flag.BoolVar(&dt.dashboard, "disable-dashboard", false, "Disable dashboard tools")
5656
flag.BoolVar(&dt.oncall, "disable-oncall", false, "Disable oncall tools")
57+
flag.BoolVar(&dt.sift, "disable-sift", false, "Disable sift tools")
5758
}
5859

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

7577
func newServer(dt disabledTools) *server.MCPServer {

go.mod

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ go 1.24.0
44

55
require (
66
github.com/go-openapi/strfmt v0.23.0
7+
github.com/google/uuid v1.6.0
78
github.com/grafana/amixr-api-go-client v0.0.21
89
github.com/grafana/grafana-openapi-client-go v0.0.0-20250108132429-8d7e1f158f65
910
github.com/grafana/incident-go v0.0.0-20250211094540-dc6a98fdae43
@@ -34,7 +35,6 @@ require (
3435
github.com/go-openapi/swag v0.23.0 // indirect
3536
github.com/go-openapi/validate v0.24.0 // indirect
3637
github.com/google/go-querystring v1.1.0 // indirect
37-
github.com/google/uuid v1.6.0 // indirect
3838
github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc // indirect
3939
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
4040
github.com/hashicorp/go-retryablehttp v0.7.7 // indirect

tools/cloud_testing_utils.go

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
//go:build cloud
2+
// +build cloud
3+
4+
package tools
5+
6+
import (
7+
"context"
8+
"os"
9+
"testing"
10+
11+
mcpgrafana "github.com/grafana/mcp-grafana"
12+
)
13+
14+
// createCloudTestContext creates a context with Grafana URL and API key for cloud integration tests.
15+
// The test will be skipped if required environment variables are not set.
16+
// testName is used to customize the skip message (e.g. "OnCall", "Sift", "Incident")
17+
func createCloudTestContext(t *testing.T, testName string) context.Context {
18+
grafanaURL := os.Getenv("GRAFANA_URL")
19+
if grafanaURL == "" {
20+
t.Skipf("GRAFANA_URL environment variable not set, skipping cloud %s integration tests", testName)
21+
}
22+
23+
grafanaApiKey := os.Getenv("GRAFANA_API_KEY")
24+
if grafanaApiKey == "" {
25+
t.Skipf("GRAFANA_API_KEY environment variable not set, skipping cloud %s integration tests", testName)
26+
}
27+
28+
ctx := context.Background()
29+
ctx = mcpgrafana.WithGrafanaURL(ctx, grafanaURL)
30+
ctx = mcpgrafana.WithGrafanaAPIKey(ctx, grafanaApiKey)
31+
32+
return ctx
33+
}

tools/incident_integration_test.go

+11-28
Original file line numberDiff line numberDiff line change
@@ -3,45 +3,27 @@
33
//go:build cloud
44
// +build cloud
55

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

814
import (
9-
"context"
10-
"os"
1115
"testing"
1216

1317
mcpgrafana "github.com/grafana/mcp-grafana"
1418
"github.com/stretchr/testify/assert"
1519
"github.com/stretchr/testify/require"
1620
)
1721

18-
// This file contains cloud integration tests that run against a dedicated test instance
19-
// at mcptests.grafana-dev.net. This instance is configured with a minimal setup on the Incident side
20-
// with two incidents created, one minor and one major, and both of them resolved.
21-
// These tests expect this configuration to exist and will skip if the required
22-
// environment variables (GRAFANA_URL, GRAFANA_API_KEY) are not set.
23-
24-
func createCloudTestContext(t *testing.T) context.Context {
25-
grafanaURL := os.Getenv("GRAFANA_URL")
26-
if grafanaURL == "" {
27-
t.Skip("GRAFANA_URL environment variable not set, skipping cloud Incident integration tests")
28-
}
29-
30-
grafanaApiKey := os.Getenv("GRAFANA_API_KEY")
31-
if grafanaApiKey == "" {
32-
t.Skip("GRAFANA_API_KEY environment variable not set, skipping cloud Incident integration tests")
33-
}
34-
35-
ctx := context.Background()
36-
ctx = mcpgrafana.WithGrafanaURL(ctx, grafanaURL)
37-
ctx = mcpgrafana.WithGrafanaAPIKey(ctx, grafanaApiKey)
38-
ctx = mcpgrafana.ExtractIncidentClientFromEnv(ctx)
39-
return ctx
40-
}
41-
4222
func TestCloudIncidentTools(t *testing.T) {
4323
t.Run("list incidents", func(t *testing.T) {
44-
ctx := createCloudTestContext(t)
24+
ctx := createCloudTestContext(t, "Incident")
25+
ctx = mcpgrafana.ExtractIncidentClientFromEnv(ctx)
26+
4527
result, err := listIncidents(ctx, ListIncidentsParams{
4628
Limit: 1,
4729
})
@@ -52,7 +34,8 @@ func TestCloudIncidentTools(t *testing.T) {
5234
})
5335

5436
t.Run("get incident by ID", func(t *testing.T) {
55-
ctx := createCloudTestContext(t)
37+
ctx := createCloudTestContext(t, "Incident")
38+
ctx = mcpgrafana.ExtractIncidentClientFromEnv(ctx)
5639
result, err := getIncident(ctx, GetIncidentParams{
5740
ID: "1",
5841
})

tools/oncall_cloud_test.go

+5-26
Original file line numberDiff line numberDiff line change
@@ -13,35 +13,14 @@
1313
package tools
1414

1515
import (
16-
"context"
17-
"os"
1816
"testing"
1917

20-
mcpgrafana "github.com/grafana/mcp-grafana"
2118
"github.com/stretchr/testify/assert"
2219
"github.com/stretchr/testify/require"
2320
)
2421

25-
func createOnCallCloudTestContext(t *testing.T) context.Context {
26-
grafanaURL := os.Getenv("GRAFANA_URL")
27-
if grafanaURL == "" {
28-
t.Skip("GRAFANA_URL environment variable not set, skipping cloud OnCall integration tests")
29-
}
30-
31-
grafanaApiKey := os.Getenv("GRAFANA_API_KEY")
32-
if grafanaApiKey == "" {
33-
t.Skip("GRAFANA_API_KEY environment variable not set, skipping cloud OnCall integration tests")
34-
}
35-
36-
ctx := context.Background()
37-
ctx = mcpgrafana.WithGrafanaURL(ctx, grafanaURL)
38-
ctx = mcpgrafana.WithGrafanaAPIKey(ctx, grafanaApiKey)
39-
40-
return ctx
41-
}
42-
4322
func TestCloudOnCallSchedules(t *testing.T) {
44-
ctx := createOnCallCloudTestContext(t)
23+
ctx := createCloudTestContext(t, "OnCall")
4524

4625
// Test listing all schedules
4726
t.Run("list all schedules", func(t *testing.T) {
@@ -104,7 +83,7 @@ func TestCloudOnCallSchedules(t *testing.T) {
10483
}
10584

10685
func TestCloudOnCallShift(t *testing.T) {
107-
ctx := createOnCallCloudTestContext(t)
86+
ctx := createCloudTestContext(t, "OnCall")
10887

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

136115
func TestCloudGetCurrentOnCallUsers(t *testing.T) {
137-
ctx := createOnCallCloudTestContext(t)
116+
ctx := createCloudTestContext(t, "OnCall")
138117

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

173152
func TestCloudOnCallTeams(t *testing.T) {
174-
ctx := createOnCallCloudTestContext(t)
153+
ctx := createCloudTestContext(t, "OnCall")
175154

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

202181
func TestCloudOnCallUsers(t *testing.T) {
203-
ctx := createOnCallCloudTestContext(t)
182+
ctx := createCloudTestContext(t, "OnCall")
204183

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

0 commit comments

Comments
 (0)