Quick Start
Requires uv. Add the following to your MCP client configuration (e.g. Claude Desktop, Cursor):
{
"mcpServers": {
"grafana": {
"command": "uvx",
"args": ["mcp-grafana"],
"env": {
"GRAFANA_URL": "http://localhost:3000",
"GRAFANA_SERVICE_ACCOUNT_TOKEN": "<your service account token>"
}
}
}
}
For Grafana Cloud, replace GRAFANA_URL with your instance URL (e.g. https://myinstance.grafana.net). See Usage for more installation options including Docker, binary, and Helm.
Usage
This MCP server works with both local Grafana instances and Grafana Cloud. For Grafana Cloud, use your instance URL (e.g., https://myinstance.grafana.net) instead of http://localhost:3000 in the configuration examples below.
-
If using service account token authentication, create a service account in Grafana with enough permissions to use the tools you want to use,
generate a service account token, and copy it to the clipboard for use in the configuration file.
Follow the [Grafana service account documentation][service-account] for details on creating service account tokens.
Tip: If you're not comfortable configuring fine-grained RBAC scopes, a simpler (but less restrictive) option is to assign the built-in Editor role to the service account. This grants broad read/write access that covers most MCP server operations — use it when convenience outweighs strict least-privilege requirements.
Note: The environment variable GRAFANA_API_KEY is deprecated and will be removed in a future version. Please migrate to using GRAFANA_SERVICE_ACCOUNT_TOKEN instead. The old variable name will continue to work for backward compatibility but will show deprecation warnings.
Reading the service account token from a file
Instead of passing the token inline via GRAFANA_SERVICE_ACCOUNT_TOKEN, you can point GRAFANA_SERVICE_ACCOUNT_TOKEN_FILE at a file path that contains the token. The file is read fresh on every request, so rotated tokens are picked up automatically without restarting the server.
This is particularly useful in Kubernetes, where a Secret mounted as a volume is updated in place when the underlying Secret changes (typically within ~1 minute). Combined with the per-request client cache — which is keyed on the token value — a rotated token transparently produces a new client with no pod restart and no downtime:
env:
- name: GRAFANA_SERVICE_ACCOUNT_TOKEN_FILE
value: /var/run/secrets/grafana/token
volumeMounts:
- name: grafana-token
mountPath: /var/run/secrets/grafana
readOnly: true
volumes:
- name: grafana-token
secret:
secretName: grafana-mcp-token
Surrounding whitespace (including a trailing newline) is trimmed from the file contents. If both GRAFANA_SERVICE_ACCOUNT_TOKEN and GRAFANA_SERVICE_ACCOUNT_TOKEN_FILE are set, the inline token takes precedence.
Multi-Organization Support
You can specify which organization to interact with using either:
- Environment variable: Set
GRAFANA_ORG_ID to the numeric organization ID
- HTTP header: Set
X-Grafana-Org-Id when using SSE or streamable HTTP transports (header takes precedence over environment variable - meaning you can set a default org as well).
When an organization ID is provided, the MCP server will set the X-Grafana-Org-Id header on all requests to Grafana, ensuring that operations are performed within the specified organization context.
Dynamic (per-call) organization selection
The options above fix the organization for the whole connection. To let a single connection target different organizations per tool call, start the server with the --dynamic-multi-org flag. This is off by default.
When enabled, every tool accepts an optional orgId argument that overrides the connection's organization for that call (driving both the X-Grafana-Org-Id header and, for app-platform APIs, the resolved Kubernetes namespace). Proxied datasource tools are additionally discovered across every organization the credential can access. Calls that omit orgId use the connection's default organization.
This only works for credentials that belong to more than one organization (e.g. a user or on-behalf-of identity); a service-account token remains bound to its single organization. Use the user_info tool to discover which orgId values are valid.
Example with organization ID:
{
"mcpServers": {
"grafana": {
"command": "mcp-grafana",
"args": [],
"env": {
"GRAFANA_URL": "http://localhost:3000",
"GRAFANA_USERNAME": "<your username>",
"GRAFANA_PASSWORD": "<your password>",
"GRAFANA_ORG_ID": "2"
}
}
}
}
Custom HTTP Headers
You can add arbitrary HTTP headers to all Grafana API requests using the GRAFANA_EXTRA_HEADERS environment variable. The value should be a JSON object mapping header names to values.
Example with custom headers:
{
"mcpServers": {
"grafana": {
"command": "mcp-grafana",
"args": [],
"env": {
"GRAFANA_URL": "http://localhost:3000",
"GRAFANA_SERVICE_ACCOUNT_TOKEN": "<your token>",
"GRAFANA_EXTRA_HEADERS": "{\"X-Custom-Header\": \"custom-value\", \"X-Tenant-ID\": \"tenant-123\"}"
}
}
}
}
SOCKS5 Proxy
You can route all requests this server makes to Grafana through a SOCKS5 proxy using the GRAFANA_SOCKS5_PROXY environment variable. The proxy is scoped to this server's Grafana traffic: it does not modify the global HTTP_PROXY/HTTPS_PROXY variables, and when set it overrides their proxy selection for Grafana transports only, without affecting other MCP servers or your shell session. When unset, behavior is unchanged.
The URL must use the socks5:// or socks5h:// scheme (Go treats them identically: hostname resolution is delegated to the proxy) and may include credentials, e.g. socks5://user:pass@127.0.0.1:1080.
Example:
{
"mcpServers": {
"grafana": {
"command": "mcp-grafana",
"args": [],
"env": {
"GRAFANA_URL": "http://localhost:3000",
"GRAFANA_SERVICE_ACCOUNT_TOKEN": "<your token>",
"GRAFANA_SOCKS5_PROXY": "socks5://127.0.0.1:1080"
}
}
}
}
An invalid proxy URL is a startup error, and if building a proxied connection fails at runtime the server fails closed rather than silently sending Grafana traffic directly.
Forwarding Headers from the Client (SSE/Streamable-HTTP Only)
When the MCP server runs behind a gateway or reverse proxy that handles SSO (e.g. an AWS ALB with OIDC), each user's session cookie must reach Grafana so it can associate the request with the authenticated user. The GRAFANA_FORWARD_HEADERS environment variable enables this by specifying a comma-separated allowlist of header names to copy from the incoming HTTP request to every outbound Grafana API request.
This only applies when using SSE (-t sse) or streamable-http (-t streamable-http) transports. It has no effect in stdio mode.
Example: forward the session cookie
{
"env": {
"GRAFANA_URL": "https://grafana.internal",
"GRAFANA_SERVICE_ACCOUNT_TOKEN": "<your token>",
"GRAFANA_FORWARD_HEADERS": "Cookie"
}
}
You can forward multiple headers by separating them with commas:
GRAFANA_FORWARD_HEADERS=Cookie,X-Session-Id
Forwarded headers are merged with any headers defined in GRAFANA_EXTRA_HEADERS. If a header name appears in both, the value from the incoming request takes precedence for that request.
Trace context headers (traceparent, tracestate, baggage) are the exception: the server propagates trace context itself, so a forwarded value never overrides the one it injects. See observability.
-
You have several options to install mcp-grafana:
-
uvx (recommended): If you have uv installed, no extra setup is needed — uvx will automatically download and run the server:
uvx mcp-grafana
-
Docker image: Use the pre-built Docker image from Docker Hub.
Important: The Docker image's entrypoint is configured to run the MCP server in SSE mode by default, but most users will want to use STDIO mode for direct integration with AI assistants like Claude Desktop:
- STDIO Mode: For stdio mode you must explicitly override the default with
-t stdio and include the -i flag to keep stdin open:
docker pull grafana/mcp-grafana
# For local 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
# For Grafana Cloud:
docker run --rm -i -e GRAFANA_URL=https://myinstance.grafana.net -e GRAFANA_SERVICE_ACCOUNT_TOKEN=<your service account token> grafana/mcp-grafana -t stdio
Note — secure the networked modes: In SSE and streamable-http modes the container binds a non-loopback address (0.0.0.0:8000). Without a caller token the server starts but logs a security error (at the error log level, so it isn't hidden by --log-level; and it will refuse to start in a future major release). Set MCP_GRAFANA_SERVER_TOKEN to require an from clients (recommended). STDIO mode is unaffected. See .
Note: if you see Error: spawn mcp-grafana ENOENT in Claude Desktop, you need to specify the full path to mcp-grafana.
If using Docker:
{
"mcpServers": {
"grafana": {
"command": "docker",
"args": [
"run",
"--rm",
"-i",
"-e",
"GRAFANA_URL",
"-e",
"GRAFANA_SERVICE_ACCOUNT_TOKEN",
"grafana/mcp-grafana",
"-t",
"stdio"
],
"env": {
"GRAFANA_URL": "http://localhost:3000", // Or "https://myinstance.grafana.net" for Grafana Cloud
"GRAFANA_SERVICE_ACCOUNT_TOKEN": "<your service account token>",
// If using username/password authentication
"GRAFANA_USERNAME": "<your username>",
"GRAFANA_PASSWORD": "<your password>",
// Optional: specify organization ID for multi-org support
"GRAFANA_ORG_ID": "1"
}
}
}
}
Note: The -t stdio argument is essential here because it overrides the default SSE mode in the Docker image.
Using VSCode with remote MCP server
If you're using VSCode and running the MCP server in SSE mode (which is the default when using the Docker image without overriding the transport), make sure your .vscode/settings.json includes the following:
"mcp": {
"servers": {
"grafana": {
"type": "sse",
"url": "http://localhost:8000/sse"
}
}
}
For HTTPS streamable HTTP mode with server TLS certificates:
"mcp": {
"servers": {
"grafana": {
"type": "sse",
"url": "https://localhost:8443/sse"
}
}
}
Debug Mode
You can enable debug mode for the Grafana transport by adding the -debug flag to the command. This will provide detailed logging of HTTP requests and responses between the MCP server and the Grafana API, which can be helpful for troubleshooting.
To use debug mode with the Claude Desktop configuration, update your config as follows:
If using the binary:
{
"mcpServers": {
"grafana": {
"command": "mcp-grafana",
"args": ["-debug"],
"env": {
"GRAFANA_URL": "http://localhost:3000", // Or "https://myinstance.grafana.net" for Grafana Cloud
"GRAFANA_SERVICE_ACCOUNT_TOKEN": "<your service account token>"
}
}
}
}
If using Docker:
{
"mcpServers": {
"grafana": {
"command": "docker",
"args": [
"run",
"--rm",
"-i",
"-e",
"GRAFANA_URL",
"-e",
"GRAFANA_SERVICE_ACCOUNT_TOKEN",
"grafana/mcp-grafana",
"-t",
"stdio",
"-debug"
],
"env": {
"GRAFANA_URL": "http://localhost:3000", // Or "https://myinstance.grafana.net" for Grafana Cloud
"GRAFANA_SERVICE_ACCOUNT_TOKEN": "<your service account token>"
}
}
}
}
Note: As with the standard configuration, the -t stdio argument is required to override the default SSE mode in the Docker image.
TLS Configuration
If your Grafana instance is behind mTLS or requires custom TLS certificates, you can configure the MCP server to use custom certificates. The server supports the following TLS configuration options:
--tls-cert-file: Path to TLS certificate file for client authentication
--tls-key-file: Path to TLS private key file for client authentication
--tls-ca-file: Path to TLS CA certificate file for server verification
--tls-skip-verify: Skip TLS certificate verification (insecure, use only for testing)
Example with client certificate authentication:
{
"mcpServers": {
"grafana": {
"command": "mcp-grafana",
"args": [
"--tls-cert-file",
"/path/to/client.crt",
"--tls-key-file",
"/path/to/client.key",
"--tls-ca-file",
"/path/to/ca.crt"
],
"env": {
"GRAFANA_URL": "https://secure-grafana.example.com",
"GRAFANA_SERVICE_ACCOUNT_TOKEN": "<your service account token>"
}
}
}
}
Example with Docker:
{
"mcpServers": {
"grafana": {
"command": "docker",
"args": [
"run",
"--rm",
"-i",
"-v",
"/path/to/certs:/certs:ro",
"-e",
"GRAFANA_URL",
"-e",
"GRAFANA_SERVICE_ACCOUNT_TOKEN",
"grafana/mcp-grafana",
"-t",
"stdio",
"--tls-cert-file",
"/certs/client.crt",
"--tls-key-file",
"/certs/client.key",
"--tls-ca-file",
"/certs/ca.crt"
],
"env": {
"GRAFANA_URL": "https://secure-grafana.example.com",
"GRAFANA_SERVICE_ACCOUNT_TOKEN": "<your service account token>"
}
}
}
}
The TLS configuration is applied to all HTTP clients used by the MCP server, including:
- The main Grafana OpenAPI client
- Prometheus datasource clients
- Loki datasource clients
- Incident management clients
- Sift investigation clients
- Alerting clients
- Asserts clients
Direct CLI Usage Examples:
For testing with self-signed certificates:
./mcp-grafana --tls-skip-verify -debug
With client certificate authentication:
./mcp-grafana \
--tls-cert-file /path/to/client.crt \
--tls-key-file /path/to/client.key \
--tls-ca-file /path/to/ca.crt \
-debug
With custom CA certificate only:
./mcp-grafana --tls-ca-file /path/to/ca.crt
Programmatic Usage:
If you're using this library programmatically, you can also create TLS-enabled context functions:
// Using struct literals
tlsConfig := &mcpgrafana.TLSConfig{
CertFile: "/path/to/client.crt",
KeyFile: "/path/to/client.key",
CAFile: "/path/to/ca.crt",
}
grafanaConfig := mcpgrafana.GrafanaConfig{
Debug: true,
TLSConfig: tlsConfig,
}
contextFunc := mcpgrafana.ComposedStdioContextFunc(grafanaConfig)
// Or inline
grafanaConfig := mcpgrafana.GrafanaConfig{
Debug: true,
TLSConfig: &mcpgrafana.TLSConfig{
CertFile: "/path/to/client.crt",
KeyFile: "/path/to/client.key",
CAFile: "/path/to/ca.crt",
},
}
contextFunc := mcpgrafana.ComposedStdioContextFunc(grafanaConfig)
URL validation:
When calling NewGrafanaClient directly (stdio or programmatic construction), pre-validate URLs to avoid a reachable panic:
if err := mcpgrafana.ValidateGrafanaURL(urlFromHeader); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
client := mcpgrafana.NewGrafanaClient(ctx, urlFromHeader, apiKey, nil)
Server TLS Configuration (Streamable HTTP Transport Only)
When using the streamable HTTP transport (-t streamable-http), you can configure the MCP server to serve HTTPS instead of HTTP. This is useful when you need to secure the connection between your MCP client and the server itself.
The server supports the following TLS configuration options for the streamable HTTP transport:
--server.tls-cert-file: Path to TLS certificate file for server HTTPS (required for TLS)
--server.tls-key-file: Path to TLS private key file for server HTTPS (required for TLS)
Note: These flags are completely separate from the client TLS flags documented above. The client TLS flags configure how the MCP server connects to Grafana, while these server TLS flags configure how clients connect to the MCP server when using streamable HTTP transport.
Example with HTTPS streamable HTTP server:
./mcp-grafana \
-t streamable-http \
--server.tls-cert-file /path/to/server.crt \
--server.tls-key-file /path/to/server.key \
-addr :8443
This would start the MCP server on HTTPS port 8443. Clients would then connect to https://localhost:8443/ instead of http://localhost:8000/.
Docker example with server TLS:
docker run --rm -p 8443:8443 \
-v /path/to/certs:/certs:ro \
-e GRAFANA_URL=http://localhost:3000 \
-e GRAFANA_SERVICE_ACCOUNT_TOKEN=<your service account token> \
grafana/mcp-grafana \
-t streamable-http \
-addr :8443 \
--server.tls-cert-file /certs/server.crt \
--server.tls-key-file /certs/server.key
Health Check Endpoint
When using the SSE (-t sse) or streamable HTTP (-t streamable-http) transports, the MCP server exposes a health check endpoint at /healthz. This endpoint can be used by load balancers, monitoring systems, or orchestration platforms to verify that the server is running and accepting connections.
Endpoint: GET /healthz
Response:
- Status Code:
200 OK
- Body:
ok
Example usage:
# For streamable HTTP or SSE transport on default port
curl http://localhost:8000/healthz
# Probe a side listener while MCP stays on loopback (Kubernetes + sidecar)
./mcp-grafana -t streamable-http --address 127.0.0.1:8000 --healthz-address :8080
curl http://127.0.0.1:8080/healthz
Note: The health check endpoint is only available when using SSE or streamable HTTP transports. It is not available when using the stdio transport (-t stdio), as stdio does not expose an HTTP server.
Observability
The MCP server supports Prometheus metrics, OpenTelemetry distributed tracing, and OpenTelemetry log export, following the OTel MCP semantic conventions. Tracing and log export are configured via standard OTEL_* environment variables and work with any transport.
Note: mcp-grafana currently only supports the OTLP/gRPC transport for both traces and logs. OTEL_EXPORTER_OTLP_PROTOCOL (and its _TRACES_PROTOCOL / _LOGS_PROTOCOL variants) are not honored — gRPC is used regardless.
Metrics
When using the SSE or streamable HTTP transports, enable Prometheus metrics with the --metrics flag:
# Metrics served on the main server at /metrics
./mcp-grafana -t streamable-http --metrics
# Metrics served on a separate address
./mcp-grafana -t streamable-http --metrics --metrics-address :9090
Available Metrics:
| Metric | Type | Description |
|---|
mcp_server_operation_duration_seconds | Histogram | Duration of MCP operations (labels: mcp_method_name, gen_ai_tool_name, error_type, network_transport, mcp_protocol_version) |
mcp_server_session_duration_seconds | Histogram | Duration of MCP client sessions (labels: network_transport, mcp_protocol_version) |
http_server_request_duration_seconds | Histogram | Duration of HTTP server requests (from otelhttp) |
Note: Metrics are only available when using SSE or streamable HTTP transports. They are not available with the stdio transport.
When the Loki cost guardrail (--loki-guardrail-mode) is enabled, four more counters record its decisions:
| Metric | Type | Description |
|---|
mcp_loki_guardrail_admitted_total | Counter | Queries that passed every enabled check (labels: backend) |
mcp_loki_guardrail_would_block_total | Counter | Queries that failed a check in shadow mode and ran anyway (labels: backend, reason) |
mcp_loki_guardrail_blocked_total | Counter | Queries rejected in enforce mode (labels: backend, reason) |
mcp_loki_guardrail_fail_open_total | Counter | Queries the guardrail could not evaluate and admitted (labels: backend, cause) |
reason is one of selector, range, bytes; cause is one of unparseable, estimate_failed; backend is one of loki, victorialogs, unknown. A query that trips several checks is counted once, labelled with the check that ran first (selector, then range, then bytes), so the four counters partition the guarded population. See Observability for how to read them during a shadow → enforce rollout.
Library embedders should set GrafanaConfig.MeterProvider (the metrics counterpart of GrafanaConfig.Logger): the guardrail runs inside a tool handler, so it has no constructor option, and a process that installs a noop global MeterProvider would otherwise drop every recording.
Slow-request logging
The --slow-request-threshold flag emits a structured log event whenever an MCP request (tool invocation, list, resource read, etc.) exceeds the given duration. It is useful for diagnosing slow queries and tool calls without drowning in the full debug log.
# Warn on any request slower than 500ms (works on all transports)
./mcp-grafana -t streamable-http --slow-request-threshold 500ms
# Same thing on stdio (the feature is transport-agnostic, unlike --metrics)
./mcp-grafana -t stdio --slow-request-threshold 500ms
# Log at INFO level instead of WARN (useful during investigation)
./mcp-grafana -t streamable-http --slow-request-threshold 500ms --slow-request-log-level info
The log event carries these structured attributes:
| Attribute | Description |
|---|
mcp.method | The MCP method (e.g., tools/call, tools/list, resources/read) |
duration | Observed request duration |
threshold | Configured threshold |
tool | Tool name (only present for tools/call methods) |
error | Error value, when the request failed (best-effort context; content is controlled by upstream error wrapping) |
error.type | Bounded-cardinality error classification (_OTHER for untyped errors) |
Slow-request logging works on all transports (including stdio) and does not require --metrics. The default threshold of 0 disables it entirely. Proxied tools flow through tools/call and are covered automatically.
Tracing
Distributed tracing is configured via standard OTEL_* environment variables and works independently of the --metrics flag. When OTEL_EXPORTER_OTLP_ENDPOINT (or the signal-specific OTEL_EXPORTER_OTLP_TRACES_ENDPOINT) is set, the server exports traces via OTLP/gRPC:
# Send traces to a local Tempo instance
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317 \
OTEL_EXPORTER_OTLP_INSECURE=true \
./mcp-grafana -t streamable-http
# Send traces to Grafana Cloud with authentication
OTEL_EXPORTER_OTLP_ENDPOINT=https://tempo-us-central1.grafana.net:443 \
OTEL_EXPORTER_OTLP_HEADERS="Authorization=Basic ..." \
./mcp-grafana -t streamable-http
Tool call spans follow semconv naming (tools/call <tool_name>) and include attributes like gen_ai.tool.name, mcp.method.name, and mcp.session.id. The server also supports W3C trace context propagation from the _meta field of tool call requests.
Logs
When OTEL_EXPORTER_OTLP_ENDPOINT (or the signal-specific OTEL_EXPORTER_OTLP_LOGS_ENDPOINT) is set, the server also exports structured logs via OTLP/gRPC in addition to the existing plain-text stderr output. The otelslog bridge automatically attaches trace_id and span_id from the active span, so log records correlate with the traces the server already emits.
Traces and logs resolve their endpoints independently, so the two signals can be enabled separately: setting only OTEL_EXPORTER_OTLP_TRACES_ENDPOINT enables tracing without log export, setting only OTEL_EXPORTER_OTLP_LOGS_ENDPOINT enables log export without tracing, and the generic OTEL_EXPORTER_OTLP_ENDPOINT enables both.
If you use the generic OTEL_EXPORTER_OTLP_ENDPOINT but want to disable log export (e.g. your backend does not support the LogsService), set:
OTEL_LOGS_EXPORTER=none
This prevents the server from creating an OTLP logs exporter regardless of the endpoint configuration, avoiding errors like unknown service opentelemetry.proto.collector.logs.v1.LogsService.
Stderr logging is unchanged when OTLP logging is enabled; you can continue to rely on container logs or pipe stderr to /dev/null if you prefer.
# Send both logs and traces to a local OTel collector
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317 \
OTEL_EXPORTER_OTLP_INSECURE=true \
./mcp-grafana -t streamable-http
The transport is OTLP/gRPC (default port 4317). Logs can be sent directly to any managed backend that accepts OTLP/gRPC — for example, Grafana Cloud — by pointing OTEL_EXPORTER_OTLP_LOGS_ENDPOINT (or the generic OTEL_EXPORTER_OTLP_ENDPOINT) at the remote gRPC endpoint and supplying auth via OTEL_EXPORTER_OTLP_LOGS_HEADERS (or OTEL_EXPORTER_OTLP_HEADERS), mirroring the tracing example above. A local OTel collector is optional — useful for fan-out, batching, or multi-backend routing, but not required.
The signal-specific variants OTEL_EXPORTER_OTLP_LOGS_ENDPOINT, OTEL_EXPORTER_OTLP_LOGS_HEADERS, OTEL_EXPORTER_OTLP_LOGS_INSECURE, OTEL_EXPORTER_OTLP_LOGS_CERTIFICATE, OTEL_EXPORTER_OTLP_LOGS_TIMEOUT, and OTEL_EXPORTER_OTLP_LOGS_COMPRESSION are honored and override their generic OTEL_EXPORTER_OTLP_* counterparts — see the OTel exporter spec for the full list and precedence rules.
If the configured collector is unreachable, log records are buffered in memory (default queue: 2048) and the oldest records are dropped once the queue fills. The process continues without blocking the service. Configure a local OTel collector if you need lossless buffering during outages.
Logs are also exported under the stdio transport, which makes it easy to centralize logs from local mcp-grafana instances invoked by IDE clients.
Docker example with metrics, tracing, and logs:
docker run --rm -p 8000:8000 \
-e GRAFANA_URL=http://localhost:3000 \
-e GRAFANA_SERVICE_ACCOUNT_TOKEN=<your token> \
-e OTEL_EXPORTER_OTLP_ENDPOINT=http://tempo:4317 \
-e OTEL_EXPORTER_OTLP_INSECURE=true \
grafana/mcp-grafana \
-t streamable-http --metrics