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.