Files
sherlock/docs/grafana-mcp.md
T
amacocian 28dcd5b8e6
CI / build (push) Failing after 1s
Grafana MCP
2026-06-13 11:43:26 +02:00

5.6 KiB

grafana-mcp

Sherlock's Grafana MCP imports Grafana Labs' upstream mcp-grafana as a Go package and serves a read-only subset of its tools in-process. There is no separate mcp-grafana binary, no uvx, and no exec.

It authenticates as the operator the same way gitea-mcp and gssh-mcp do — OAuth + PKCE against Authentik, token stored in the OS keyring — so the agent never sees a Grafana service-account token.

How auth works

grafana-mcp does not use GRAFANA_SERVICE_ACCOUNT_TOKEN, GRAFANA_API_KEY, or Grafana username/password auth.

At startup it calls authn.NewTokenSource(...).Start(ctx) to obtain an Authentik access token (browser flow on first use). Every Grafana API request then carries that token as Authorization: Bearer <jwt>, injected by a custom GrafanaConfig.BaseTransport. The token is kept fresh by the sherlock TokenSource renewer (see auth-model.md) — Authentik access tokens live only ~5 minutes, so a captured-once token would 401 mid-session.

Only read-only tool categories are registered (search, datasource, prometheus, loki, alerting, dashboard, folder, navigation, annotations); upstream write tools are disabled.

Why generic OAuth is not enough (and what is)

Grafana already federates with Authentik via GF_AUTH_GENERIC_OAUTH_*. That is not the same mechanism and does not make this MCP work:

GF_AUTH_GENERIC_OAUTH_* grafana-mcp → API
OAuth client Grafana itself sherlock (sherlock-cli provider)
Purpose interactive browser login programmatic API call
Credential a Grafana session cookie an Authentik JWT bearer
Grafana's job requests the token, drops it for a cookie validate a token it never issued

Generic OAuth logs humans into the UI; it never accepts an externally minted bearer on /api/*. Presenting our bearer there makes Grafana treat it as a service-account token and return Invalid API key.

The mechanism that makes Grafana accept the Authentik JWT on its API is a separate integration: [auth.jwt]. It must be enabled on the Grafana deployment (it is missing from the Charlie/victoriametrics stack today). auth.jwt and generic_oauth coexist fine — UI users keep using SSO; sherlock uses JWT on the API.

Required Grafana server config

Add to the grafana service environment in Charlie/victoriametrics/docker-compose.yml:

      GF_AUTH_JWT_ENABLED: "true"
      GF_AUTH_JWT_HEADER_NAME: "Authorization"          # Grafana strips the "Bearer " prefix
      GF_AUTH_JWT_USERNAME_CLAIM: "preferred_username"  # matches the generic_oauth login attr → same user
      GF_AUTH_JWT_EMAIL_CLAIM: "email"
      GF_AUTH_JWT_JWK_SET_URL: "https://id.alexandru.macocian.me/application/o/sherlock-cli/jwks/"
      GF_AUTH_JWT_AUTO_SIGN_UP: "true"
      GF_AUTH_JWT_ROLE_ATTRIBUTE_PATH: "contains(groups, 'admins') && 'Admin' || 'Viewer'"
      GF_AUTH_JWT_ROLE_ATTRIBUTE_STRICT: "true"
      # All Authentik providers share one signing key, so pin the issuer
      # to sherlock-cli — otherwise any Authentik JWT would authenticate.
      GF_AUTH_JWT_EXPECT_CLAIMS: '{"iss":"https://id.alexandru.macocian.me/application/o/sherlock-cli/"}'

The claim names above are verified against an actual sherlock-cli access token, which is a full RS256 JWT carrying: iss, aud, preferred_username (= the operator's username, same value as sub), email, name, and groups (including admins). Using preferred_username aligns with GF_AUTH_GENERIC_OAUTH_LOGIN_ATTRIBUTE_PATH, so JWT auth maps to the same Grafana account as browser SSO.

Grafana must be able to reach GF_AUTH_JWT_JWK_SET_URL from inside its container.

Charlie defaults

Variable / -X main.… Charlie default
grafanaIssuer https://id.alexandru.macocian.me/application/o/sherlock-cli/
grafanaBaseURL https://grafana.alexandru.macocian.me
grafanaClientID same Public sherlock-cli client as gssh-mcp
grafanaClientSecret "" (Public, PKCE-only)
Version 0.0.0-dev

Build & install

cd ~/Dev/charlie/sherlock
go install ./cmd/grafana-mcp

That's it — the upstream tool set is compiled in. For other deployments:

go build -tags noembed \
  -ldflags "-X main.grafanaIssuer=https://id.example/application/o/sherlock-cli/ \
            -X main.grafanaClientID=<CLIENT_ID>                                  \
            -X main.grafanaBaseURL=https://grafana.example"                      \
  ./cmd/grafana-mcp

Verify without an agent

grafana-mcp --probe

Expected once [auth.jwt] is configured:

OK: logged in to https://grafana.alexandru.macocian.me as <login> <<email>>

A 401/403 (e.g. Invalid API key) means OAuth itself succeeded but Grafana is not yet validating the Authentik JWT for API access — apply the server config above. Force a re-login with sherlock logout grafana.

Use with an agent

sherlock copilot / sherlock claude automatically render an MCP config listing grafana-mcp when the binary is installed. All exposed tools are read-only (subject to Grafana RBAC): dashboard search/inspection, datasource listing/querying (Prometheus, Loki, …), alert-rule and notification reads, annotations, and navigation deeplinks.

This relies on Grafana-side [auth.jwt] being configured (above). On a deployment where that isn't in place, the MCP still starts but every tool call 401s; verify with grafana-mcp --probe first.