# Connect Grafana to Prodpeek Profile: `grafana/read-only` · connection kind: `grafana` · tier 2 Credential: A Grafana service account token (starts `glsa_`) Suggested URL: `https://your-stack.grafana.net` Dashboards, panel queries, alert rules, what is firing right now, and PromQL or LogQL through your datasources. One URL and one token — Prodpeek speaks Grafana's own API, so there is nothing to install. ## What you must not do You cannot create this credential on the user's behalf — it needs their login and, usually, an approval step. Walk them through it and verify the result. Never ask them to paste the credential into the chat; it goes straight into Prodpeek's console, which encrypts it at rest. There is one token and it is the user's Grafana service account token. Do not suggest https://mcp.grafana.com (OAuth-only, will 401) and do not suggest running mcp-grafana — Prodpeek has a native Grafana adapter and needs neither. Create the connection with `add_service` and no credential, then hand over the drop link; you cannot open it, which is the point. ## Grant exactly these permissions - `A Grafana service account with the Viewer role` — Viewer can read dashboards, alert rules and query through datasources, and cannot edit any of them. It is the narrowest Grafana role that lets this profile's allowed tools work. - `A token on that service account, with an expiry` — Grafana lets a service account token be permanent. Set an expiry anyway — you will rotate it on a schedule you choose rather than one an incident chooses for you. - `Optionally, `datasources:read` on that service account` — Only needed for the `list_datasources` tool. A plain Viewer cannot call it, which is Grafana's choice and not a bug. You can skip this: `get_panel_queries` returns datasource UIDs too, which is all `query` needs. Grant the permission itself if you want the listing — do NOT make the account an Admin to get it. ## Refuse these, and say why if the user asks for them - `https://mcp.grafana.com as the upstream` — Grafana Cloud's hosted MCP authenticates with OAuth 2.1 browser authorization scoped to a signed-in user. It does not accept a service account token and answers one with HTTP 401. You do not need it: Prodpeek talks to Grafana directly. - `Editor or Admin on the service account` — Editor can change dashboards and alert rules; Admin can mint more service accounts and read contact point settings in full. The gateway denies those tools either way, but a token that could do them means the two fences disagree about more than they need to — and `prove` will report it, because Grafana tells us what the token may do. - `A user's personal API token instead of a service account` — It carries that person's role, disappears when they leave, and attributes every call to them rather than to the integration. ## Verify before the credential is used - The service account's role reads Viewer. - The token starts `glsa_` and has an expiry. - Test connection shows search_dashboards and query under Allowed. - Calling `whoami` returns a permission map with no `:write` or `:create` key. ## Then, in Prodpeek 1. Services → Add a service → choose the profile `grafana/read-only`. 2. Connection kind `grafana`, URL `https://your-stack.grafana.net`. 3. Paste the credential. It is encrypted in the store and never shown again. 4. Run **Test connection**. It lists every tool the upstream advertises and how the profile classifies each one. Anything under "not in the policy" is denied by default — report that list rather than assuming it is fine. ## Full human walkthrough ## The whole setup Two things, and you already have one of them. ### 1. Mint the token (about two minutes) 1. In Grafana: **Administration → Users and access → Service accounts**. 2. **Add service account**. Name it `prodpeek`. Set the role to **Viewer**. 3. **Add service account token**. Name it, **set an expiry**, generate. 4. Copy it. It starts `glsa_`. ### 2. Add it in Prodpeek **Services → Add a service**, pick `grafana/read-only`: | Field | Value | |---|---| | Upstream | `https://your-stack.grafana.net` — just the origin | | Credential | the `glsa_…` token | Press **Test connection**. That is the whole thing. !!! tip "Use the origin, not the page you are looking at" `https://ejendomstorvet.grafana.net` — not the URL of a dashboard. Prodpeek will tell you if you paste a `/d/abc123/...` link rather than failing mysteriously on every call. From an agent, with no credential, then hand over the drop link: ```json {"name": "ejd-grafana", "profile": "grafana/read-only", "url": "https://ejendomstorvet.grafana.net"} ``` ## Why there is no MCP server to install If you searched for "Grafana MCP" you found `https://mcp.grafana.com/mcp`, and it is the wrong address for Prodpeek — not misconfigured, wrong in kind. That endpoint authenticates with **OAuth 2.1 browser authorization, scoped to the signed-in Grafana user**. Your editor opens a browser, you approve, and the connection acts as *you*. Notice the config you find for it has no token in it. Send a service account token and you get `HTTP 401`, always. Prodpeek is a server-side gateway holding a stored credential for a team. It has no browser and no signed-in user, and it should not have one — a gateway whose authority is "whichever human last clicked approve" is not a gateway. The other option was to have you run Grafana's own `mcp-grafana` in a sidecar, with your Grafana token in its environment and a *second* token for Prodpeek to authenticate to it. That works. It is also a container, a private network and two tokens to read some dashboards. So Prodpeek speaks Grafana's HTTP API directly, with a fixed menu, the way it already does for SSH, Postgres, Git and Prometheus. Your `glsa_` token is exactly what that API wants. ## What your agent can then do ``` ejd-grafana__search_dashboards find the dashboard somebody already built ejd-grafana__get_panel_queries learn the right PromQL for a system it has not seen ejd-grafana__query PromQL or LogQL through a datasource ejd-grafana__list_firing_alerts what is on fire right now ejd-grafana__list_alert_rules what is being watched, and at what threshold ejd-grafana__list_annotations deploys and incidents, as the team marked them ejd-grafana__whoami what this token is actually permitted to do ``` `get_panel_queries` is the one people do not expect to want. An agent that has never seen your stack does not know your metric names. The queries behind a dashboard your team already built are the fastest way for it to learn them — and they come with the datasource UIDs that `query` needs, so the two tools chain. ## Grafana tells you what the token can do Most vendors will not answer "what is this credential allowed to do?". Grafana will, at `/api/access-control/user/permissions`, and it needs no permission to ask. So this profile carries a scope probe, and every [`prove`](../use/prove.md) run checks that your token holds no permission whose name contains `:write`, `:create` or `:delete`. A token that gains one demotes the service and says so. That is the difference between a Tier 2 you are asked to trust and a Tier 2 that is re-checked on a schedule. Call `whoami` yourself any time a call is refused. A `403` means the token reached Grafana and was not allowed — the answer is a permission, not a role upgrade. ## What it refuses, and the two that surprise people **There is no tool that exports contact points.** `GET /api/v1/provisioning/contact-points/export?decrypt=true` returns your Slack webhook URLs, PagerDuty integration keys and SMTP passwords in full. Grafana documents it as a read. It is the most dangerous endpoint in the Grafana API, and the adapter has no code path that builds it. **`list_contact_points` returns names and types only.** "Where do alerts go" is a real incident question. "And with which credential" is not one an agent gets to ask, so the `settings` object is stripped before the answer is returned. Likewise `list_datasources` gives you UID, name and type — not the URL, not the auth configuration. A query needs the UID and nothing else. ## Checking the token is really Viewer ```bash STACK=https://your-stack.grafana.net # Should succeed: reading dashboards curl -sS -H "Authorization: Bearer $GLSA_TOKEN" "$STACK/api/search?limit=1" | head -c 200 # Should FAIL with 403: creating one curl -sS -o /dev/null -w '%{http_code}\n' -X POST \ -H "Authorization: Bearer $GLSA_TOKEN" -H 'Content-Type: application/json' \ -d '{"dashboard":{"title":"prodpeek-permission-test"},"overwrite":false}' \ "$STACK/api/dashboards/db" # What Grafana thinks this token may do curl -sS -H "Authorization: Bearer $GLSA_TOKEN" \ "$STACK/api/access-control/user/permissions" | python3 -m json.tool ``` A `403` on the second is the part of the Tier 1 story Grafana *can* give you. A `200` means the token is Editor or better — delete it and make a Viewer one. The gateway would still refuse the call, but you would be relying on one fence. ## One connection per stack Production and staging are two Grafana stacks, so two connections. Name them for the stack, because the service name is the namespace an agent sees: `ejd-grafana-prod__query` reads unambiguously in a transcript, and `grafana__query` does not.