# Mcp Read Only Grafana

> MCP server for read-only access to Grafana instances

- **Type:** MCP server
- **Install:** `agentstack add mcp-lukleh-mcp-read-only-grafana`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [lukleh](https://agentstack.voostack.com/s/lukleh)
- **Installs:** 0
- **Category:** [Integrations](https://agentstack.voostack.com/c/integrations)
- **Latest version:** 0.1.0
- **License:** MIT
- **Upstream author:** [lukleh](https://github.com/lukleh)
- **Source:** https://github.com/lukleh/mcp-read-only-grafana

## Install

```sh
agentstack add mcp-lukleh-mcp-read-only-grafana
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

# MCP Read-Only Grafana Server

[](https://github.com/lukleh/mcp-read-only-grafana/actions/workflows/test.yml)

A secure MCP (Model Context Protocol) server for Grafana with a read-only default and a separate `mcp-grafana-write` command for write-capable workflows.

> Default layout:
> - Live config: `~/.config/lukleh/mcp-read-only-grafana/connections.yaml`
> - Credentials: injected via the MCP client or shell environment
> - Rotated session state: `~/.local/state/lukleh/mcp-read-only-grafana/session_tokens.json`
> - Cache: `~/.cache/lukleh/mcp-read-only-grafana/`

**Compatibility:** Targeted and tested against Grafana 9.5.x. Newer versions (e.g., 10.x) should work for read-only endpoints but may expose extra fields not covered here.

## Features

- **Read-only by default** - `mcp-read-only-grafana` exposes the safe default surface
- **Separate write command** - `mcp-grafana-write` enables dashboard saves, alerting writes, folders, and related mutations from the same package
- **API key first authentication** - Prefers Grafana API keys or service-account tokens for stable machine access
- **Deprecated session-cookie fallback** - Still supports Grafana session cookies, including automatic capture of rotated cookies in `session_tokens.json`
- **Hierarchical dashboard navigation** - Handle large dashboards efficiently with lightweight metadata queries and per-panel detail fetching
- **Multiple instances** - Support for multiple Grafana connections
- **Comprehensive API coverage** - Access dashboards, panels, folders, datasources, and alerts
- **Security focused** - Timeouts, SSL verification, and secure token storage

## Prerequisites

- Python 3.11 or higher
- [uv](https://github.com/astral-sh/uv)
- Grafana credentials for at least one instance
- an MCP client such as Claude Code or Codex

## Quick Start

### 1. Install the Server

```bash
# Run the published package without cloning the repository
uvx mcp-read-only-grafana@latest --write-sample-config

# Or install it once and reuse the command directly
uv tool install mcp-read-only-grafana
mcp-read-only-grafana --write-sample-config

# The same install also provides the separate write-capable command
mcp-grafana-write --print-paths
```

When using `uvx`, prefer `mcp-read-only-grafana@latest` in user-facing docs and
MCP client configs. This avoids reusing a stale cached tool environment after a
new release is published. For the separate write-capable command, use
`uvx --from mcp-read-only-grafana@latest mcp-grafana-write`.

The command above writes a starter config and matching schema to:

- `~/.config/lukleh/mcp-read-only-grafana/connections.yaml`
- `~/.config/lukleh/mcp-read-only-grafana/connections.schema.json`

The live runtime config file used by the installed server is
`~/.config/lukleh/mcp-read-only-grafana/connections.yaml`.

### 2. Confirm Runtime Paths

```bash
uvx mcp-read-only-grafana@latest --print-paths
```

### Where `connections.yaml` Lives

By default, the server reads the live runtime config from:

- `~/.config/lukleh/mcp-read-only-grafana/connections.yaml`

On this machine, that expands to:

- `/Users//.config/lukleh/mcp-read-only-grafana/connections.yaml`

Important distinction:

- The live runtime file is `~/.config/lukleh/mcp-read-only-grafana/connections.yaml`
- The checked-in repo sample is [`connections.yaml.sample`](connections.yaml.sample)

The sample file documents the format, but it is not the file the installed
server reads unless you explicitly copy or generate it into the runtime config
directory.

### 3. Edit the Connections File

Edit `~/.config/lukleh/mcp-read-only-grafana/connections.yaml` with your Grafana instances:

```yaml
- connection_name: production_grafana
  url: https://grafana.example.com
  description: Production Grafana instance
  api_key: glsa_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

- connection_name: staging_grafana
  url: https://staging-grafana.example.com
  description: Staging Grafana instance
```

### 4. Set Up Authentication

You can keep credentials either directly in `connections.yaml` or in the
environment used to launch the server. Prefer `api_key` for normal use. For
local shell testing you can export credentials directly; for normal MCP use,
inject them through the client config when you want runtime overrides.

YAML credentials:

```yaml
- connection_name: production_grafana
  url: https://grafana.example.com
  api_key: glsa_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
```

You can authenticate with either a Grafana API key or a deprecated session
cookie fallback:

- API key or service-account token:

  ```bash
  export GRAFANA_API_KEY_PRODUCTION_GRAFANA=your_api_key_here
  ```

- Deprecated session cookie fallback:

  ```bash
  export GRAFANA_SESSION_PRODUCTION_GRAFANA=your_session_token_here
  ```

Precedence is:
- Rotated session cookies in `session_tokens.json`
- Runtime environment variables
- Credentials declared in `connections.yaml`

If both a session token and an API key are available for the same connection,
the server prefers the API key.

#### Deprecated: How to Get a Grafana Session Token

1. Log in to Grafana in a web browser
2. Open developer tools
3. Go to Application/Storage -> Cookies
4. Find the cookie named `grafana_session` or `grafana_sess`
5. Copy the value and export or inject it as `GRAFANA_SESSION_`

Use this only as a temporary fallback. Browser session cookies rotate and
expire quickly, so they are less reliable than API keys for MCP usage.

#### How to Get a Grafana API Key

1. In Grafana, go to **Administration -> Service Accounts** or **Configuration -> API Keys**
2. Create a key with the minimum required permissions
3. Export or inject it as `GRAFANA_API_KEY_`

If you start with a session cookie, the server will keep refreshed cookies in
`~/.local/state/lukleh/mcp-read-only-grafana/session_tokens.json`. On later
requests, that persisted state file takes precedence over the live
`GRAFANA_SESSION_*` environment value and any static `session_token` in
`connections.yaml` until you update or remove it.

### 5. Configure Your MCP Client

**Claude Code**

```bash
claude mcp add mcp-read-only-grafana \
  --scope user \
  -e GRAFANA_API_KEY_PRODUCTION_GRAFANA=your_api_key_here \
  -- uvx mcp-read-only-grafana@latest
```

**Codex**

```bash
codex mcp add mcp-read-only-grafana \
  --env GRAFANA_API_KEY_PRODUCTION_GRAFANA=your_api_key_here \
  -- uvx mcp-read-only-grafana@latest
```

If you absolutely need the deprecated session-cookie fallback, swap
`GRAFANA_API_KEY_*` for `GRAFANA_SESSION_*` in the MCP client config.

If you want the write-capable endpoints, launch the separate write command from
the same package:

```bash
uvx --from mcp-read-only-grafana@latest mcp-grafana-write
```

For a persistent install created with `uv tool install mcp-read-only-grafana`,
run `mcp-grafana-write` directly.

### 6. Restart and Test

Restart your MCP client and try a simple query such as:

```text
List all dashboards in the production Grafana instance.
```

## Command Line Testing

```bash
# Show the resolved runtime paths
uvx mcp-read-only-grafana@latest --print-paths

# Write or refresh the default connections.yaml
uvx mcp-read-only-grafana@latest --write-sample-config
uvx mcp-read-only-grafana@latest --write-sample-config --overwrite

# Run the server with the default home-directory config
uvx mcp-read-only-grafana@latest

# Run the separate write-capable command from the same package
uvx --from mcp-read-only-grafana@latest mcp-grafana-write

# Point the server at a different config root
uvx mcp-read-only-grafana@latest --config-dir /path/to/config-dir

# Validate the generated configuration
uvx mcp-read-only-grafana@latest validate-config

# Test all configured Grafana connections
uvx mcp-read-only-grafana@latest test-connection

# Test one specific connection
uvx mcp-read-only-grafana@latest test-connection production_grafana
```

## Local Development

If you want to work on the repository itself:

```bash
git clone https://github.com/lukleh/mcp-read-only-grafana.git
cd mcp-read-only-grafana
uv sync --extra dev
uv run pytest -q
uv run mcp-read-only-grafana --print-paths
uv run mcp-grafana-write --print-paths
```

The checked-in sample file remains available at [connections.yaml.sample](connections.yaml.sample) for documentation and review, but package users should prefer `--write-sample-config`.

Even during local development, the server still uses the resolved runtime
config path by default. It does not automatically read the repo's
`connections.yaml.sample`.

## Available MCP Tools

### `list_connections`
List all configured Grafana instances.

**Returns:** JSON with connection names, URLs, and descriptions

### `get_health`
Check Grafana instance health and version.

**Parameters:**
- `connection_name` (required): Name of the Grafana connection

**Returns:** Health status and version information

### `search_dashboards`
Search for dashboards by name or tag.

**Parameters:**
- `connection_name` (required): Name of the Grafana connection
- `query` (optional): Search query for dashboard names
- `tag` (optional): Tag to filter dashboards
- `limit` (optional): Maximum results per page (Grafana default 1000, max 5000)
- `page` (optional): Page number (1-indexed)
- `fields` (optional): Subset of Grafana fields to return (e.g., `uid`, `title`, `url`, `type`, `tags`, `folderTitle`, `folderUid`)

**Returns:** List of matching dashboards with UIDs, titles, and tags

### `get_dashboard_info`
Get lightweight dashboard metadata and panel list (without full panel definitions). **Recommended first step for exploring dashboards, especially large ones.**

**Parameters:**
- `connection_name` (required): Name of the Grafana connection
- `dashboard_uid` (required): UID of the dashboard

**Returns:** Dashboard metadata, variables, and list of all panels with basic info

### `get_dashboard_panel`
Get full configuration for a single panel from a dashboard. **Use this after `get_dashboard_info()` to explore specific panels in detail.**

**Parameters:**
- `connection_name` (required): Name of the Grafana connection
- `dashboard_uid` (required): UID of the dashboard
- `panel_id` (required): Panel ID to retrieve

**Returns:** Full panel JSON including queries, transformations, and field config

### `get_dashboard`
Get complete dashboard definition. **Use with caution for large dashboards - may exceed token limits. Prefer `get_dashboard_info()` + `get_dashboard_panel()` for large dashboards.**

**Parameters:**
- `connection_name` (required): Name of the Grafana connection
- `dashboard_uid` (required): UID of the dashboard

**Returns:** Full dashboard JSON including panels, variables, and settings

### `get_dashboard_panels`
Get simplified panel information from a dashboard. Returns basic panel metadata without full configuration.

**Parameters:**
- `connection_name` (required): Name of the Grafana connection
- `dashboard_uid` (required): UID of the dashboard

**Returns:** List of panels with IDs, titles, types, and descriptions

### `list_folders`
List all folders in Grafana.

**Parameters:**
- `connection_name` (required): Name of the Grafana connection

**Returns:** Folder hierarchy with IDs and titles

### `list_folder_dashboards`
List all dashboards within a specific folder.

**Parameters:**
- `connection_name` (required): Name of the Grafana connection
- `folder_uid` (required): UID of the folder
- `limit` (optional): Maximum results per page
- `page` (optional): Page number
- `fields` (optional): Subset of Grafana fields (e.g., `uid`, `title`, `url`, `tags`, `folderUid`)

**Returns:** List of dashboards in the folder with UIDs, titles, and URLs

### `list_datasources`
List configured data sources.

**Parameters:**
- `connection_name` (required): Name of the Grafana connection

**Returns:** Data source names, types, UIDs, and configuration

### `get_datasource_health`
Run the datasource-specific Grafana health check when the datasource plugin supports it.

> **Note:** Not every datasource plugin implements `GET /api/datasources/uid/:uid/health`. When Grafana returns `404`, the MCP returns a structured `unsupported` or `not_found` result instead of surfacing the raw error.

**Parameters:**
- `connection_name` (required): Name of the Grafana connection
- `datasource_uid` (required): UID of the datasource

**Returns:** Health information for supported datasources, or a structured explanation when the health endpoint is unavailable

### `query_prometheus`
Execute a PromQL query against a Prometheus datasource.

**Parameters:**
- `connection_name` (required): Name of the Grafana connection
- `datasource_uid` (required): UID of the Prometheus datasource
- `query` (required): PromQL query string
- `time_from` (optional): Start time (RFC3339 or relative like "now-1h")
- `time_to` (optional): End time (RFC3339 or "now")
- `step` (optional): Query resolution step (e.g., "15s", "1m")

**Returns:** Query results with timestamps and values

### `query_loki`
Execute a LogQL query against a Loki datasource.

**Parameters:**
- `connection_name` (required): Name of the Grafana connection
- `datasource_uid` (required): UID of the Loki datasource
- `query` (required): LogQL query string
- `time_from` (optional): Start time (RFC3339 or relative like "now-1h")
- `time_to` (optional): End time (RFC3339 or "now")
- `limit` (optional): Maximum number of log lines (default: 100)

**Returns:** Log query results with timestamps and log lines

### `explore_query`
Execute Grafana Explore queries via the `/api/ds/query` endpoint.

> Note: This is the only tool that issues an HTTP POST (required by Grafana Explore). The call is still read-only and does not mutate Grafana state.

**Parameters:**
- `connection_name` (required): Name of the Grafana connection
- `queries` (required): List of Explore query definitions (including datasource, `refId`, etc.)
- `range_from` (optional): Relative or absolute start time (e.g., `now-6h`)
- `range_to` (optional): End time (e.g., `now`)
- `max_data_points` (optional): Maximum number of datapoints to request
- `interval_ms` (optional): Query interval in milliseconds
- `additional_options` (optional): Extra request fields that must not overlap with reserved keys

**Returns:** Raw Explore results as returned by Grafana

### `list_alerts`
List alert rules.

**Parameters:**
- `connection_name` (required): Name of the Grafana connection
- `folder_uid` (optional): Filter alerts by folder

**Returns:** Alert rules with status and conditions

### `get_alert_rule_by_uid`
Get a specific alert rule by its UID.

**Parameters:**
- `connection_name` (required): Name of the Grafana connection
- `alert_uid` (required): UID of the alert rule

**Returns:** Alert rule details including conditions, labels, and annotations

### `get_alert_rules_with_state`
Get all alert rules with their current evaluation state. This is the same endpoint used by Grafana's Alert List panel - useful for checking if an alert is working after creation.

**Parameters:**
- `connection_name` (required): Name of the Grafana connection
- `state` (optional): Filter by state (e.g., "firing", "pending", "inactive")
- `rule_name` (optional): Filter by rule name (partial match)

**Returns:** Rules organized by namespace with current state (Normal, Pending, Alerting, NoData, Error), health status, and evaluation info

### `get_firing_alerts`
Get currently firing alert instances from Alertmanager. Returns alerts that have transitioned from Pending to Firing state.

**Parameters:**
- `connection_name` (required): Name of the Grafana connection
- `filter_labels` (optional): Label matchers (e.g., `["alertname=HighCPU", "severity=critical"]`)
- `silenced` (optional): Include silenced alerts (default: true)
- `inhibited` (optional): Include inhibited alerts (default: true)
- `active` (optional): Include active alerts (default: true)

**Returns:** List of firing alert instances with labels, annotations, startsAt, and other metadata

### `get_alert_state_history

…

## Source & license

This open-source MCP server is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [lukleh](https://github.com/lukleh)
- **Source:** [lukleh/mcp-read-only-grafana](https://github.com/lukleh/mcp-read-only-grafana)
- **License:** MIT

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** no
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** no
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/mcp-lukleh-mcp-read-only-grafana
- Seller: https://agentstack.voostack.com/s/lukleh
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
