@mgcrea/mcp-grafana
v0.2.0
Published
Model Context Protocol server for the Grafana HTTP API
Maintainers
Readme
@mgcrea/mcp-grafana
A Model Context Protocol server for the Grafana HTTP API: dashboards and the data their panels show, PromQL and LogQL through Grafana's datasources, alerts, alert rules, silences and annotations, shaped to fit a context window.
The server is read-only by default. The write tools are not merely refused when writes are off — they are never registered, so an agent cannot call them at all.
What it does that the API does not
- Runs a panel the way the dashboard does.
grafana_run_panel_queryfills in template variables before sending the query, which/api/ds/querywill not do for you. It takes them fromvars, else the dashboard's saved value, else what the browser would pick: All, or the first value of alabel_values(...)query, or""("None") when that query has no values. That last case is common, not an edge: provisioned dashboards (every kube-prometheus-stack mixin) are saved with no current values, and on a single-cluster install the mixins'clustervariable has no values at all. Mixed panels, datasource variables, hidden queries, expressions and library panels are handled. The result says which values were used and what query was sent. - Shapes data frames. Grafana answers every datasource in columnar frames: one frame per
Prometheus series, each repeating its schema, and Loki log rows that each repeat the full
stream label set alongside three UI-only columns. Here, time series come back on one step grid
with min/max/avg/last, labels shared by every series or line come back once as
common_labels, log lines are cut atmax_line_chars, and anything else is a table. - Summarizes dashboards.
grafana_get_dashboardflattens rows (collapsed, expanded and pre-schema-16), and lists each panel's id, type, unit and queries without the layout and styling: about half the JSON on the kubernetes mixins.view=fullis there for editing. - Reads alerting from everywhere it lives. Grafana-managed rules and Grafana's own
Alertmanager, plus every Prometheus/Loki datasource's ruler and every Alertmanager
datasource. A kube-prometheus-stack install usually has everything in the datasources and
nothing in Grafana. A source that does not answer (a Loki with no ruler) shows up in
unavailableand does not fail the call. - Says which org it is reading. A service account token is bound to one org and takes no
org in the request, so a token copied from the wrong org answers everything with plausible
data. The org is shown in the startup banner and in
grafana_auth_status, and stamped on every data response. WithGRAFANA_ORG_IDset, every data tool refuses on a mismatch.
Tools
| Tool | What it does | Writes |
| --------------------------- | ------------------------------------------------------------------------------------ | ------ |
| grafana_auth_status | URL, version, org, login, effective role and permissions; setup steps when unset | no |
| grafana_search | Dashboards and folders by title, tag, folder, starred | no |
| grafana_get_dashboard | Summary (variables, panels, queries), one panel's JSON, or the full JSON | no |
| grafana_run_panel_query | A panel's queries with variables filled in, and the data they return | no |
| grafana_query_prometheus | PromQL, instant or range, through a Prometheus datasource | no |
| grafana_query_loki | LogQL log lines or metric queries through a Loki datasource | no |
| grafana_query | Raw queries against any datasource, including server-side expressions | no |
| grafana_list_labels | Label names or values for Prometheus/Loki, restricted by selector, last hour | no |
| grafana_list_datasources | uid, name, type, default — never credentials | no |
| grafana_list_alerts | Active alerts from every Alertmanager, most severe first | no |
| grafana_list_alert_rules | Rules and their state from Grafana and every datasource ruler | no |
| grafana_list_silences | Silences with readable matchers | no |
| grafana_list_annotations | Deploys, incidents and alert state changes in a window | no |
| grafana_request | GET any /api/ or /apis/ endpoint; any method (with confirm) when writes are on | gated |
| grafana_save_dashboard | Create or update a dashboard; refuses to overwrite a concurrent edit | yes |
| grafana_delete_dashboard | Delete a dashboard (confirm: true) | yes |
| grafana_create_annotation | Mark a moment or region | yes |
| grafana_create_silence | Silence alerts matching matchers, for a duration | yes |
| grafana_expire_silence | End a silence (confirm: true) | yes |
Configure
| Variable | Required | Description |
| ------------------------------- | -------- | ------------------------------------------------------------------------------------------- |
| GRAFANA_URL | yes | Grafana root URL, including any sub-path, e.g. https://grafana.example.com. |
| GRAFANA_SERVICE_ACCOUNT_TOKEN | one of | A service account token (glsa_…). GRAFANA_API_KEY is accepted as an alias. |
| GRAFANA_USERNAME / PASSWORD | one of | Basic auth instead of a token. |
| GRAFANA_ORG_ID | no | The org this server must read. Sent as X-Grafana-Org-Id; data tools refuse on a mismatch. |
| GRAFANA_ALLOW_WRITES | no | 1 to register the write tools. |
| GRAFANA_HEADERS | no | Extra headers as JSON, e.g. a Cloudflare Access service token. |
| GRAFANA_TIMEOUT | no | Seconds per request. Defaults to 30. |
| GRAFANA_MAX_RETRIES | no | Retries on 429/502/503/504 for reads. Defaults to 2. Writes are never retried. |
| GRAFANA_DEBUG | no | 1 to log each request to stderr. |
The names match Grafana's own mcp-grafana, so swapping is a change of command in an
.mcp.json.
A token needs Viewer for every read tool (with Viewer, the datasource list comes from the
frontend settings, since /api/datasources needs more), Editor for dashboards and
annotations, and whatever your alerting setup requires for silences. grafana_auth_status reports
the role and the permissions the tools depend on.
Security
- Read-only by default.
GRAFANA_ALLOW_WRITES=1adds the write tools; the destructive ones then additionally require an explicitconfirm: trueon every call, andgrafana_requestrequires it for any method but GET. - Saves never overwrite.
grafana_save_dashboardsendsoverwrite: false, so a dashboard changed since it was read is refused, not clobbered; every save stays in the version history. - No runtime dependencies beyond the MCP SDK and Zod — native
fetchthroughout. - Credentials stay in your environment, are never echoed back (
grafana_auth_statuslists header names, not values), and are refused when embedded inGRAFANA_URL.
Quick start
pnpm install
pnpm build{
"mcpServers": {
"grafana": {
"command": "npx",
"args": ["-y", "@mgcrea/mcp-grafana"],
"env": {
"GRAFANA_URL": "https://grafana.example.com",
"GRAFANA_SERVICE_ACCOUNT_TOKEN": "glsa_..."
}
}
}
}Inspect the tools
printf '%s\n' \
'{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"cli","version":"0"}}}' \
'{"jsonrpc":"2.0","method":"notifications/initialized"}' \
'{"jsonrpc":"2.0","id":2,"method":"tools/list"}' \
| node dist/cli.js 2>/dev/null | jq -r '.result.tools[]?.name'Develop
pnpm dev # tsdown --watch
pnpm test # vitest
pnpm typecheck
pnpm lint
pnpm formatPublish
pnpm dlx release-it # bump, commit, tag
git push --follow-tags # CI publishes to npm + GHCR from the tagLicense
MIT
