Skip to content

Commit e6c58d7

Browse files
sd2kclaude
andauthored
feat: accept X-Grafana-Service-Account-Token in headers too (#280)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 820a303 commit e6c58d7

2 files changed

Lines changed: 43 additions & 3 deletions

File tree

mcpgrafana.go

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,9 @@ const (
4141

4242
grafanaExtraHeadersEnvVar = "GRAFANA_EXTRA_HEADERS"
4343

44-
grafanaURLHeader = "X-Grafana-URL"
45-
grafanaAPIKeyHeader = "X-Grafana-API-Key"
44+
grafanaURLHeader = "X-Grafana-URL"
45+
grafanaServiceAccountTokenHeader = "X-Grafana-Service-Account-Token"
46+
grafanaAPIKeyHeader = "X-Grafana-API-Key" // Deprecated: use X-Grafana-Service-Account-Token instead
4647
)
4748

4849
func urlAndAPIKeyFromEnv() (string, string) {
@@ -116,7 +117,15 @@ func orgIdFromHeaders(req *http.Request) int64 {
116117

117118
func urlAndAPIKeyFromHeaders(req *http.Request) (string, string) {
118119
u := strings.TrimRight(req.Header.Get(grafanaURLHeader), "/")
119-
apiKey := req.Header.Get(grafanaAPIKeyHeader)
120+
121+
// Check for the new service account token header first
122+
apiKey := req.Header.Get(grafanaServiceAccountTokenHeader)
123+
if apiKey != "" {
124+
return u, apiKey
125+
}
126+
127+
// Fall back to the deprecated API key header
128+
apiKey = req.Header.Get(grafanaAPIKeyHeader)
120129
return u, apiKey
121130
}
122131

mcpgrafana_test.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,37 @@ func TestExtractGrafanaInfoFromHeaders(t *testing.T) {
158158
assert.Equal(t, "my-test-api-key", config.APIKey)
159159
})
160160

161+
t.Run("with service account token header", func(t *testing.T) {
162+
t.Setenv("GRAFANA_URL", "")
163+
t.Setenv("GRAFANA_API_KEY", "")
164+
t.Setenv("GRAFANA_SERVICE_ACCOUNT_TOKEN", "")
165+
166+
req, err := http.NewRequest("GET", "http://example.com", nil)
167+
require.NoError(t, err)
168+
req.Header.Set(grafanaURLHeader, "http://my-test-url.grafana.com")
169+
req.Header.Set(grafanaServiceAccountTokenHeader, "my-service-account-token")
170+
ctx := ExtractGrafanaInfoFromHeaders(context.Background(), req)
171+
config := GrafanaConfigFromContext(ctx)
172+
assert.Equal(t, "http://my-test-url.grafana.com", config.URL)
173+
assert.Equal(t, "my-service-account-token", config.APIKey)
174+
})
175+
176+
t.Run("service account token header takes precedence over api key header", func(t *testing.T) {
177+
t.Setenv("GRAFANA_URL", "")
178+
t.Setenv("GRAFANA_API_KEY", "")
179+
t.Setenv("GRAFANA_SERVICE_ACCOUNT_TOKEN", "")
180+
181+
req, err := http.NewRequest("GET", "http://example.com", nil)
182+
require.NoError(t, err)
183+
req.Header.Set(grafanaURLHeader, "http://my-test-url.grafana.com")
184+
req.Header.Set(grafanaServiceAccountTokenHeader, "my-service-account-token")
185+
req.Header.Set(grafanaAPIKeyHeader, "my-deprecated-api-key")
186+
ctx := ExtractGrafanaInfoFromHeaders(context.Background(), req)
187+
config := GrafanaConfigFromContext(ctx)
188+
assert.Equal(t, "http://my-test-url.grafana.com", config.URL)
189+
assert.Equal(t, "my-service-account-token", config.APIKey)
190+
})
191+
161192
t.Run("no headers, with env", func(t *testing.T) {
162193
t.Setenv("GRAFANA_USERNAME", "foo")
163194
t.Setenv("GRAFANA_PASSWORD", "bar")

0 commit comments

Comments
 (0)