Skip to content

Commit 2dbb53e

Browse files
authored
fix: fall back to Grafana URL/API key from env vars in SSE mode (#94)
If there are no relevant headers, fall back to the Grafana URL and API key from the environment variables, then the defaults. Fixes #91.
1 parent c53a364 commit 2dbb53e

File tree

2 files changed

+125
-0
lines changed

2 files changed

+125
-0
lines changed

mcpgrafana.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,13 @@ var ExtractGrafanaClientFromHeaders server.SSEContextFunc = func(ctx context.Con
146146
cfg := client.DefaultTransportConfig()
147147
// Extract transport config from request headers, and set it on the context.
148148
u, apiKey := urlAndAPIKeyFromHeaders(req)
149+
uEnv, apiKeyEnv := urlAndAPIKeyFromEnv()
150+
if u == "" {
151+
u = uEnv
152+
}
153+
if apiKey == "" {
154+
apiKey = apiKeyEnv
155+
}
149156
if u != "" {
150157
if url, err := url.Parse(u); err == nil {
151158
cfg.Host = url.Host
@@ -197,6 +204,16 @@ var ExtractIncidentClientFromEnv server.StdioContextFunc = func(ctx context.Cont
197204

198205
var ExtractIncidentClientFromHeaders server.SSEContextFunc = func(ctx context.Context, req *http.Request) context.Context {
199206
grafanaURL, apiKey := urlAndAPIKeyFromHeaders(req)
207+
grafanaURLEnv, apiKeyEnv := urlAndAPIKeyFromEnv()
208+
if grafanaURL == "" {
209+
grafanaURL = grafanaURLEnv
210+
}
211+
if grafanaURL == "" {
212+
grafanaURL = defaultGrafanaURL
213+
}
214+
if apiKey == "" {
215+
apiKey = apiKeyEnv
216+
}
200217
incidentURL := fmt.Sprintf("%s/api/plugins/grafana-irm-app/resources/api/v1/", grafanaURL)
201218
client := incident.NewClient(incidentURL, apiKey)
202219
return context.WithValue(ctx, incidentClientKey{}, client)

mcpgrafana_test.go

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"testing"
1010

1111
"github.com/go-openapi/runtime/client"
12+
grafana_client "github.com/grafana/grafana-openapi-client-go/client"
1213
"github.com/stretchr/testify/assert"
1314
"github.com/stretchr/testify/require"
1415
)
@@ -22,6 +23,52 @@ func TestExtractIncidentClientFromEnv(t *testing.T) {
2223
assert.Equal(t, "http://my-test-url.grafana.com/api/plugins/grafana-irm-app/resources/api/v1/", client.RemoteHost)
2324
}
2425

26+
func TestExtractIncidentClientFromHeaders(t *testing.T) {
27+
t.Run("no headers, no env", func(t *testing.T) {
28+
req, err := http.NewRequest("GET", "http://example.com", nil)
29+
require.NoError(t, err)
30+
ctx := ExtractIncidentClientFromHeaders(context.Background(), req)
31+
32+
client := IncidentClientFromContext(ctx)
33+
require.NotNil(t, client)
34+
assert.Equal(t, "http://localhost:3000/api/plugins/grafana-irm-app/resources/api/v1/", client.RemoteHost)
35+
})
36+
37+
t.Run("no headers, with env", func(t *testing.T) {
38+
t.Setenv("GRAFANA_URL", "http://my-test-url.grafana.com/")
39+
req, err := http.NewRequest("GET", "http://example.com", nil)
40+
require.NoError(t, err)
41+
ctx := ExtractIncidentClientFromHeaders(context.Background(), req)
42+
43+
client := IncidentClientFromContext(ctx)
44+
require.NotNil(t, client)
45+
assert.Equal(t, "http://my-test-url.grafana.com/api/plugins/grafana-irm-app/resources/api/v1/", client.RemoteHost)
46+
})
47+
48+
t.Run("with headers, no env", func(t *testing.T) {
49+
req, err := http.NewRequest("GET", "http://example.com", nil)
50+
req.Header.Set(grafanaURLHeader, "http://my-test-url.grafana.com")
51+
require.NoError(t, err)
52+
ctx := ExtractIncidentClientFromHeaders(context.Background(), req)
53+
54+
client := IncidentClientFromContext(ctx)
55+
require.NotNil(t, client)
56+
assert.Equal(t, "http://my-test-url.grafana.com/api/plugins/grafana-irm-app/resources/api/v1/", client.RemoteHost)
57+
})
58+
59+
t.Run("with headers, with env", func(t *testing.T) {
60+
t.Setenv("GRAFANA_URL", "will-not-be-used")
61+
req, err := http.NewRequest("GET", "http://example.com", nil)
62+
req.Header.Set(grafanaURLHeader, "http://my-test-url.grafana.com")
63+
require.NoError(t, err)
64+
ctx := ExtractIncidentClientFromHeaders(context.Background(), req)
65+
66+
client := IncidentClientFromContext(ctx)
67+
require.NotNil(t, client)
68+
assert.Equal(t, "http://my-test-url.grafana.com/api/plugins/grafana-irm-app/resources/api/v1/", client.RemoteHost)
69+
})
70+
}
71+
2572
func TestExtractGrafanaInfoFromHeaders(t *testing.T) {
2673
t.Run("no headers, no env", func(t *testing.T) {
2774
req, err := http.NewRequest("GET", "http://example.com", nil)
@@ -106,3 +153,64 @@ func TestExtractGrafanaClientPath(t *testing.T) {
106153
assert.Equal(t, "/grafana/api", rt.BasePath)
107154
})
108155
}
156+
157+
// minURL is a helper struct representing what we can extract from a constructed
158+
// Grafana client.
159+
type minURL struct {
160+
host, basePath string
161+
}
162+
163+
// minURLFromClient extracts some minimal amount of URL info from a Grafana client.
164+
func minURLFromClient(c *grafana_client.GrafanaHTTPAPI) minURL {
165+
rt := c.Transport.(*client.Runtime)
166+
return minURL{rt.Host, rt.BasePath}
167+
}
168+
169+
func TestExtractGrafanaClientFromHeaders(t *testing.T) {
170+
t.Run("no headers, no env", func(t *testing.T) {
171+
req, err := http.NewRequest("GET", "http://example.com", nil)
172+
require.NoError(t, err)
173+
ctx := ExtractGrafanaClientFromHeaders(context.Background(), req)
174+
c := GrafanaClientFromContext(ctx)
175+
url := minURLFromClient(c)
176+
assert.Equal(t, "localhost", url.host)
177+
assert.Equal(t, "/api", url.basePath)
178+
})
179+
180+
t.Run("no headers, with env", func(t *testing.T) {
181+
t.Setenv("GRAFANA_URL", "http://my-test-url.grafana.com")
182+
183+
req, err := http.NewRequest("GET", "http://example.com", nil)
184+
require.NoError(t, err)
185+
ctx := ExtractGrafanaClientFromHeaders(context.Background(), req)
186+
c := GrafanaClientFromContext(ctx)
187+
url := minURLFromClient(c)
188+
assert.Equal(t, "my-test-url.grafana.com", url.host)
189+
assert.Equal(t, "/api", url.basePath)
190+
})
191+
192+
t.Run("with headers, no env", func(t *testing.T) {
193+
req, err := http.NewRequest("GET", "http://example.com", nil)
194+
require.NoError(t, err)
195+
req.Header.Set(grafanaURLHeader, "http://my-test-url.grafana.com")
196+
ctx := ExtractGrafanaClientFromHeaders(context.Background(), req)
197+
c := GrafanaClientFromContext(ctx)
198+
url := minURLFromClient(c)
199+
assert.Equal(t, "my-test-url.grafana.com", url.host)
200+
assert.Equal(t, "/api", url.basePath)
201+
})
202+
203+
t.Run("with headers, with env", func(t *testing.T) {
204+
// Env vars should be ignored if headers are present.
205+
t.Setenv("GRAFANA_URL", "will-not-be-used")
206+
207+
req, err := http.NewRequest("GET", "http://example.com", nil)
208+
require.NoError(t, err)
209+
req.Header.Set(grafanaURLHeader, "http://my-test-url.grafana.com")
210+
ctx := ExtractGrafanaClientFromHeaders(context.Background(), req)
211+
c := GrafanaClientFromContext(ctx)
212+
url := minURLFromClient(c)
213+
assert.Equal(t, "my-test-url.grafana.com", url.host)
214+
assert.Equal(t, "/api", url.basePath)
215+
})
216+
}

0 commit comments

Comments
 (0)