Back to Article List

How to set up the Grafana MCP server

How to set up the Grafana MCP server - How to set up the Grafana MCP server

The Model Context Protocol lets an AI client call tools on your behalf. Grafana maintains its own MCP server, mcp-grafana, which exposes your Grafana instance as a set of callable tools: search dashboards, run PromQL and LogQL queries, read alert rules, list data sources, manage incidents.

The practical result is that you can ask an assistant "what fired last night on the api dashboard" and it goes and looks. Setting it up takes about ten minutes. Deciding what token to give it takes longer, which is why the security section at the end runs as long as it does.

What the Grafana MCP server exposes

The mcp-grafana repository ships well over a hundred tools grouped into categories. The ones enabled out of the box cover most of what you would want:

CategoryWhat it can do
Search and DashboardFind dashboards by title, fetch one by UID, get a compact summary or a single JSONPath property, read every panel's query and data source, create or patch dashboards
DatasourcesList configured data sources and fetch details for one by UID or name
PrometheusRun instant and range PromQL queries, list metric names, label names and label values, read metric metadata, compute histogram percentiles
LokiRun log and metric LogQL queries, read label names and values, retrieve detected log patterns
AlertingList alert rules and their firing state, create and update rules, read notification policies and contact points
Incident, OnCall and SiftSearch and create incidents, read on-call schedules and shifts, run Sift investigations for error patterns and slow requests
Annotations, Snapshots, NavigationRead and write annotations, create snapshots, generate deeplinks into the Grafana UI

A second set ships disabled by default and has to be opted into: admin, agento11y, assistant, athena, clickhouse, cloudwatch, elasticsearch, examples, graphite, quickwit, runpanelquery and snowflake. The specialised data source ones are off because most instances do not have those plugins. admin is off because team and user management is not something you want an assistant reaching for by accident.

The README notes Grafana 9.0 or later is required for full functionality, since some data source endpoints do not exist before that. Starting from nothing, a package install of Grafana on Ubuntu puts you on a current release by default. Anything on 12.x or 13.x is well clear of that line, so the version question rarely comes up.

Create the service account and token

The server authenticates to Grafana with a service account token, so make that first. Under Administration you want Users and access, and the Service accounts page inside it. Create one called mcp-readonly and set the role.

Naming is worth the thirty seconds. Six months from now somebody doing an audit will find a token called test and have no way to tell what breaks if they revoke it, except by revoking it and waiting to see who complains. That is the whole digression. Back to the role.

Set the role to Viewer. The repository is candid that assigning the built-in Editor role is the quicker path and says to use it "only when convenience is more important than strict least-privilege access". Viewer covers every read tool, which is every tool most people use an assistant for. Add write permissions later if you find yourself needing them.

Now generate a token on that account and put an expiry date on it. Grafana displays the value one time only. It begins glsa_, and every request the MCP server makes carries it in an Authorization: Bearer header, which is the same mechanism the Grafana API and service accounts guide describes for scripts and CI jobs. Paste it somewhere safe before you close the dialog.

Expiry can be enforced from /etc/grafana/grafana.ini:

[service_accounts]
token_expiration_day_limit = 30

That key caps how far out an expiry date can be set when somebody creates a token. Its value in conf/defaults.ini is empty, no cap, which is why most people's tokens never expire.

Install mcp-grafana

Five install paths, listed in the official Grafana MCP documentation as well as the repository. Which one you want depends mostly on where the client runs.

uvx

The repository calls this the quick start and it needs nothing installed except uv. The client downloads and runs the server on demand:

uvx mcp-grafana

Docker

docker pull grafana/mcp-grafana
docker run --rm -i \
  -e GRAFANA_URL=http://localhost:3000 \
  -e GRAFANA_SERVICE_ACCOUNT_TOKEN=<your service account token> \
  grafana/mcp-grafana -t stdio

Read that -t stdio carefully, because the image's entrypoint defaults to SSE mode. Wiring it to a desktop client over stdio means overriding that default and passing -i to keep stdin open. Leave either out and you get a container that starts, binds a port nobody is talking to and a client that reports the server as unavailable. Why the image defaults to one transport while the binary defaults to another, I do not know.

Binary

Grab a build from the releases page and drop it somewhere on your $PATH. That's the route I'd take on a server. A single static binary under systemd has fewer moving parts when the thing it talks to is already on the same host.

go install

GOBIN="$HOME/go/bin" go install github.com/grafana/mcp-grafana/cmd/mcp-grafana@latest

Helm

helm repo add grafana https://grafana.github.io/helm-charts
helm install --set grafana.apiKey=<Grafana_ApiKey> --set grafana.url=<GrafanaUrl> my-release grafana/grafana-mcp

stdio, SSE and streamable HTTP transports

The -t flag (long form --transport) takes stdio, sse or streamable-http, and the binary defaults to stdio.

stdio is a local pipe. The client launches the server as a child process and they talk over standard input and output. There is no port and no listener, so there is nothing to authenticate because nothing else can reach it. For a desktop client on the same machine, use it.

sse and streamable-http both bind a listener. Relevant flags:

FlagDefaultPurpose
--addresslocalhost:8000Host and port for the HTTP transports
--endpoint-path/mcpEndpoint path for streamable HTTP
--base-pathemptyBase path prefix for the server
--grafana-timeout10sRequest timeout against Grafana, Go duration format
--log-levelinfodebug, info, warn or error
--session-idle-timeout-minutes30Idle sessions are reaped. 0 disables reaping

Defaults, gathered in one place. The transport is stdio. The address is localhost:8000, the endpoint path is /mcp and the base path is empty. The Grafana request timeout is 10s, the log level is info and idle sessions are reaped after 30 minutes.

Both HTTP transports expose GET /healthz, which returns 200 OK with a body of ok. That is what you point a load balancer or a container health check at. Stdio has no HTTP server so it has no health endpoint.

Client configuration

Almost every MCP client uses the same JSON shape: a top-level mcpServers object, one key per server, and under that key a command, an args array and an env object. Here is the concrete form for Claude Desktop, using the binary:

{
  "mcpServers": {
    "grafana": {
      "command": "mcp-grafana",
      "args": [],
      "env": {
        "GRAFANA_URL": "http://localhost:3000",
        "GRAFANA_SERVICE_ACCOUNT_TOKEN": "<your service account token>"
      }
    }
  }
}

Swap "command": "mcp-grafana" for "command": "uvx", "args": ["mcp-grafana"] if you went the uvx route, or point command at docker with the run arguments in args. Cursor, VS Code and the rest use the same three fields, sometimes under a different filename and occasionally with the block nested one level deeper. Client config formats move around, so treat the shape as the thing to remember and check your client's own docs for where the file lives.

With an HTTP transport the client config carries a URL, plus headers if you have enabled caller authentication. The environment variables:

VariableWhat it sets
GRAFANA_URLYour instance, for example http://localhost:3000 or https://myinstance.grafana.net
GRAFANA_SERVICE_ACCOUNT_TOKENThe token. Replaces the deprecated GRAFANA_API_KEY
GRAFANA_SERVICE_ACCOUNT_TOKEN_FILEPath to a file holding the token, re-read on every request so rotation needs no restart
MCP_GRAFANA_SERVER_TOKENBearer token callers must present. Env fallback for --server-auth-token
GRAFANA_ORG_IDNumeric org ID for multi-org instances

Use the token file variable on anything long-running. The file is read fresh on every request, so a rotated secret is picked up with no restart and no gap, which starts to matter once the token has a thirty day expiry.

Tool categories and turning them off

--enabled-tools takes a comma-separated list of categories and replaces the default set, so use it to switch on something that ships disabled:

mcp-grafana --enabled-tools "search,dashboard,datasource,prometheus,loki,alerting"

Individual --disable-<category> flags subtract from whatever is enabled. --disable-oncall, --disable-navigation, --disable-snapshot and so on, one per category.

--disable-write turns off every mutating tool in one go: dashboard updates, folder creation, incident creation, alert rule and silence management, annotation writes, snapshot creation and the Sift tools that create investigations. Every read tool keeps working. Combine it with a Viewer service account and you have two independent things saying no.

There is a context window argument for trimming tools too. A hundred-plus tool definitions is a lot of tokens before the conversation even starts, and cutting the categories you do not use makes the assistant faster and more accurate at picking the right tool. If you only ever ask about dashboards and Prometheus, enable those two and leave the rest off.

Running it next to a self-hosted Grafana

If Grafana is already in Docker, the MCP server goes on the same user-defined network and reaches it by container name:

services:
  mcp-grafana:
    image: grafana/mcp-grafana
    command: ["-t", "streamable-http", "--address", "127.0.0.1:8000", "--disable-write"]
    environment:
      GRAFANA_URL: "http://grafana:3000"
      GRAFANA_SERVICE_ACCOUNT_TOKEN: "${GRAFANA_MCP_TOKEN}"
      MCP_GRAFANA_SERVER_TOKEN: "${MCP_CALLER_TOKEN}"
    ports:
      - "127.0.0.1:8000:8000"
    restart: unless-stopped

The URL is http://grafana:3000, because localhost inside a container is that container. The port mapping is written as 127.0.0.1:8000:8000 rather than 8000:8000 so Docker publishes it on loopback only, which matters because Docker's port publishing writes iptables rules that bypass ufw. The Grafana side of that file is in the Grafana Docker Compose stack, where the two services already share a network.

Add the health check while you are in there, since the endpoint exists and costs nothing:

curl -s http://127.0.0.1:8000/healthz

Grafana Cloud's hosted MCP endpoint

Grafana Cloud runs a hosted server, so there is nothing to install. It lives at https://mcp.grafana.com/mcp and authenticates with an OAuth 2.1 flow in the browser. The client block is a URL with an optional header naming your stack:

{
  "mcpServers": {
    "grafana": {
      "url": "https://mcp.grafana.com/mcp",
      "headers": {
        "X-Grafana-URL": "https://<your-stack>.grafana.net"
      }
    }
  }
}

The Cloud MCP documentation describes the header as optional but recommended, since it skips the URL entry step during authorization. Cloud MCP access is user-scoped through Grafana RBAC, so it inherits the permissions of whoever authorized it. That is a better security model than a shared service account, and it is the main reason to prefer it if Cloud is where your Grafana already lives.

Security: What a read-everything token exposes

A service account token that can read every dashboard is a bigger exposure than it sounds. Specifically:

Dashboards contain queries. Queries contain hostnames, internal service names, database names, Kubernetes namespaces, customer identifiers in label values and sometimes credentials in a URL somebody pasted into a panel description. A read-only token over the dashboard API is a fairly complete map of your infrastructure. Add datasources:query and it can also run arbitrary PromQL and LogQL, which means it can read log lines, and log lines contain whatever your applications log.

So, four things I would do on any deployment that outlives an afternoon.

Scope the service account to Viewer, and to specific resources where you can. RBAC scopes accept UIDs, so datasources:uid:prometheus-prod with datasources:query grants exactly one data source rather than all of them. It is more work than assigning Editor. It also decides how bad a day a leaked token turns into.

Keep the listener on loopback. The defaults help here. --allowed-hosts defaults to the loopback variants of --address and rejects anything else with a 403, which blocks DNS rebinding from a browser. --allowed-origins is empty by default, so any request carrying an Origin header is rejected outright. Both accept * to switch the check off, which is only sane behind a reverse proxy that rewrites Host. There is no reason for this server to be reachable from the internet at all.

Set a caller token if it binds anything other than loopback. --server-auth-token, or better MCP_GRAFANA_SERVER_TOKEN so the secret does not sit in the process arguments where ps can read it. Without one, the server starts anyway and logs a security error at error level, and the README says a future major release will make that a startup failure. Treat the warning as the failure it is going to become.

Run with --disable-write unless you have a specific reason not to. The tools that create incidents and rewrite dashboards belong in a workflow somebody designed on purpose. An assistant that can silence an alert rule is an assistant that can silence the alert rule you needed.

Rotation is the other half. Give the token a 30 day expiry, point GRAFANA_SERVICE_ACCOUNT_TOKEN_FILE at a file and replace the file on a schedule. GET /api/serviceaccounts/:id/tokens lists what a service account is carrying, expiry included, so an audit is a one-line script. Everything else on the same instance sits in the Grafana hardening checklist, which is a considerably longer list than this one.

One operational note to finish on. Turn on --debug the first time you connect a client and watch the HTTP request and response log go past. It shows you exactly which Grafana endpoints the assistant is hitting and with what, which is the fastest way to understand what a tool call really does, and it is the log you will want when a permission error comes back as a generic failure in the client UI. Then turn it off, because it prints request bodies.

Answers to common questions...

Can I point the MCP server at a Grafana behind basic auth or a reverse proxy?

Yes. GRAFANA_URL takes the externally reachable URL, so a proxied instance at https://grafana.example.com works as long as the service account token authenticates there. For extra headers your proxy needs, GRAFANA_EXTRA_HEADERS takes a JSON object mapping header names to values and applies them to every Grafana request. There are also TLS client flags (--tls-cert-file, --tls-key-file, --tls-ca-file) for mutual TLS, and a --tls-skip-verify which does what it says and should not survive past a test.