Skip to main content
Glama
robert-sinclair

grafana-unified-mcp

grafana-unified-mcp

Один MCP-сервер перед многими экземплярами Grafana. Все инструменты из стандартного MCP-сервера Grafana, плюс один дополнительный аргумент — instance — который указывает, против какого экземпляра Grafana его запускать.

query_prometheus(instance="appstate", expr="up", datasourceUid="...")
search_dashboards(instance="uoregon", query="login latency")

Зачем это нужно

Исходный grafana/mcp-grafana привязывает GRAFANA_URL один раз, при запуске процесса. Он читает X-Grafana-Service-Account-Token для каждого запроса, но URL фиксирован — и заголовок, который раньше его переопределял, теперь явно инертен. Из исходного validate_url.go:

Deprecated: X-Grafana-URL no longer configures the Grafana client. This middleware is retained temporarily to preserve malformed-header handling.

Таким образом, один процесс mcp-grafana может общаться только с одной Grafana. Десять Grafana означают десять серверов, десять записей в каждой конфигурации клиента и десять наборов инструментов с одинаковыми именами, которые модель должна различать.

Этот сервер решает эту проблему, запуская один дочерний процесс upstream для каждого экземпляра и направляя каждый вызов к нужному на основе аргумента instance. Инструменты обнаруживаются из реального бинарного файла во время выполнения, поэтому вы получаете всё, что предоставляет upstream — на данный момент 65 инструментов — без кода для каждого инструмента здесь и без необходимости обновлять что-либо, когда upstream добавляет новые.

Как это работает

                                        ┌──────────────────────────────────┐
  Claude Code / routines /              │  grafana-unified-mcp             │
  cloud sessions                        │                                  │
        │                               │  ┌────────────────────────────┐  │
        │  streamable-HTTP              │  │ bearer auth                │  │
        │  Authorization: Bearer …      │  │  → Principal(instances,    │  │
        ├──────────────────────────────►│  │      read-only|read-write) │  │
        │                               │  └────────────┬───────────────┘  │
        │                               │               │                  │
        │                               │  ┌────────────▼───────────────┐  │
        │                               │  │ catalog: inject `instance`  │  │
        │                               │  │ filter by caller's grant   │  │
        │                               │  └────────────┬───────────────┘  │
        │                               │               │ route on         │
        │                               │               │ instance=…       │
        │                               │  ┌────────────▼───────────────┐  │
        │                               │  │ child pool (lazy, reaped)  │  │
        │                               │  └──┬──────────┬──────────┬───┘  │
        └───────────────────────────────┴─────┼──────────┼──────────┼──────┘
                                              │ stdio    │ stdio    │ stdio
                                        ┌─────▼────┐ ┌───▼──────┐ ┌▼─────────┐
                                        │mcp-grafana│ │mcp-grafana│ │mcp-grafana│
                                        │ appstate │ │ uoregon  │ │   …      │
                                        └─────┬────┘ └───┬──────┘ └┬─────────┘
                                              ▼          ▼         ▼
                                          appstate    uoregon    …Grafana

Дочерние процессы запускаются при первом использовании, остаются в горячем состоянии, завершаются при простое (--idle-timeout, по умолчанию 15 мин) и прозрачно перезапускаются, если они умирают. Недоступная Grafana ухудшает работу только своего собственного экземпляра.

Установка

Две части: бинарный файл upstream и этот пакет.

# 1. the upstream mcp-grafana binary (needs Go 1.26+; GOTOOLCHAIN=auto fetches it)
deploy/install-mcp-grafana.sh /usr/local/bin

# 2. this server
python3 -m venv /opt/grafana-unified-mcp/.venv
/opt/grafana-unified-mcp/.venv/bin/pip install 'grafana-unified-mcp[aws] @ .'

Если у вас уже есть бинарный файл, укажите на него с помощью MCP_GRAFANA_BINARY=/path/to/mcp-grafana или --mcp-grafana-binary.

Настройка

Конечные точки

Именно та форма, которую вы ожидаете — имя экземпляра к переменным окружения upstream:

{
  "appstate": {
    "GRAFANA_URL": "https://appstate.uw2.example.cloud/grafana",
    "GRAFANA_SERVICE_ACCOUNT_TOKEN": "glsa_…"
  },
  "uoregon": {
    "GRAFANA_URL": "https://uoregon.uw2.example.cloud/grafana",
    "GRAFANA_SERVICE_ACCOUNT_TOKEN": "glsa_…",
    "description": "University of Oregon production"
  }
}

Необязательные ключи для каждого экземпляра: GRAFANA_ORG_ID, GRAFANA_USERNAME / GRAFANA_PASSWORD, description, extra_env, extra_args. Чтобы не хранить секреты в самом документе, используйте GRAFANA_SERVICE_ACCOUNT_TOKEN_ENV (читается из окружения этого процесса) или GRAFANA_SERVICE_ACCOUNT_TOKEN_FILE (путь, который читает дочерний процесс).

Аутентификация

{
  "clients": [
    {
      "name": "claude-routines",
      "token_sha256": "3f786850e387550fdab836ed7e6dc881de23001b…",
      "instances": ["appstate", "uoregon"],
      "scope": "read-only"
    },
    {
      "name": "platform-oncall",
      "token_sha256": "…",
      "instances": ["*"],
      "scope": "read-write"
    }
  ]
}

Создайте токен и его хеш:

grafana-unified-mcp --hash-token          # generates one
grafana-unified-mcp --hash-token 'my-existing-token'

Передайте token клиенту; поместите token_sha256 в документ. Токены сравниваются по дайджесту с помощью hmac.compare_digest, и каждый клиент проверяется при каждой попытке, чтобы позиция совпадения не утекала через время.

Для каждого вызывающего применяются две вещи:

  • instances — перечисление instance, которое видит вызывающий, сужается до его разрешения, и вызов с именем экземпляра за его пределами отклоняется с тем же сообщением, что и для несуществующего, так что токен не может перечислить то, к чему у него нет доступа.

  • scope — вызывающие с read-only никогда даже не видят изменяющие инструменты. Разделение происходит из аннотации readOnlyHint upstream (на сегодня 49 из 65 инструментов доступны только для чтения), а не из списка, поддерживаемого здесь, поэтому инструменты, добавленные upstream, классифицируются без изменения кода. Всё, что не аннотировано, считается не read-only.

Для надёжности добавьте --child-arg=--disable-write, чтобы удалить инструменты записи в источнике для всех вызывающих.

Запуск без аутентификации

--auth-mode none обслуживает каждого вызывающего, который может достичь порта, только для чтения. Нет идентичности для ограничения экземпляров, поэтому все настроенные экземпляры остаются читаемыми — но ничего не доступно для записи, потому что открытый порт не должен иметь возможность переписать дашборд или удалить снимок. Это обеспечивается на трёх уровнях:

  1. опубликованный каталог опускает все изменяющие инструменты;

  2. проверка авторизации отклоняет их, даже если клиент называет один напрямую;

  3. дочерние процессы запускаются с --disable-write, поэтому upstream также отклоняет их.

Третий уровень — это то, что делает это больше, чем фильтр. Upstream заменяет grafana_api_request на отдельную регистрацию только для GET — без параметра body, method сужается до GET, не-GET отклоняется во время выполнения — так что даже ошибка на уровнях 1 и 2 не может привести к записи.

stdio отличается: локальный вызывающий уже имеет документ конечных точек и все токены в нём, поэтому ограничивать их было бы театральностью. stdio получает полный доступ.

Если вам нужна запись через HTTP, используйте токены Bearer с клиентом read-write, а не открытый порт.

Откуда берётся конфигурация

Любой из этих источников, как для --endpoints, так и для --auth:

Источник

Пример

Файл

/etc/grafana-unified-mcp/endpoints.json

Встроенная переменная окружения

env:GRAFANA_ENDPOINTS_JSON

AWS Secrets Manager

aws-secrets:prod/grafana/endpoints?region=us-west-2

AWS SSM Parameter Store

aws-ssm:/prod/grafana/endpoints?region=us-west-2

Оба документа перечитываются каждые --config-refresh-seconds (по умолчанию 300). Сбой обновления логируется и сохраняется последнее корректное значение, поэтому временная ошибка AWS или неполностью записанный файл не могут остановить сервер. Добавление экземпляра не требует перезапуска; удаление экземпляра останавливает его дочерний процесс.

Проверьте перед запуском:

grafana-unified-mcp --endpoints … --auth … --check-config

Запуск

# local, over stdio (no auth — the local caller already holds the config)
grafana-unified-mcp --endpoints ./examples/endpoints.json

# deployed, over streamable-HTTP behind a reverse proxy
grafana-unified-mcp \
  --transport streamable-http \
  --address 127.0.0.1:8900 \
  --endpoints aws-secrets:prod/grafana/endpoints?region=us-west-2 \
  --auth      aws-secrets:prod/grafana/mcp-auth?region=us-west-2 \
  --public-url https://grafana-mcp.example.com

--public-url важен. SDK применяет защиту от перепривязки DNS на основе заголовка Host. За прокси, перенаправляющим публичное имя хоста, этот хост должен быть разрешён, иначе каждый запрос будет отклонён. --public-url разрешает его (и используется для метаданных ресурсов RFC 9728); --allowed-host добавляет ещё.

GET /healthz сообщает о состоянии процесса, активных дочерних процессах и состоянии каталога без обращения к Grafana.

Подключение клиента

.mcp.json, для локального использования stdio:

{
  "mcpServers": {
    "grafana": {
      "command": "/opt/grafana-unified-mcp/.venv/bin/grafana-unified-mcp",
      "args": ["--endpoints", "/etc/grafana-unified-mcp/endpoints.json"]
    }
  }
}

Для развёрнутого сервера — включая процедуры Claude Code и облачные сессии, для которых и существуют токены Bearer:

{
  "mcpServers": {
    "grafana": {
      "type": "http",
      "url": "https://grafana-mcp.example.com/mcp",
      "headers": {
        "Authorization": "Bearer ${GRAFANA_UNIFIED_MCP_TOKEN}"
      }
    }
  }
}

Установите GRAFANA_UNIFIED_MCP_TOKEN в окружении, в котором выполняется сессия — для Claude Code в вебе это переменные окружения, поэтому запланированные процедуры и облачные сессии подхватывают его без хранения секрета в репозитории. Дайте процедурам клиент read-only; оставьте read-write для людей.

Развертывание как сервис systemd

Смотрите deploy/. Вкратце:

sudo deploy/install.sh                       # user, dirs, venv, unit file
sudo systemctl edit grafana-unified-mcp      # set the source URIs / region
sudo systemctl enable --now grafana-unified-mcp
curl -s localhost:8900/healthz | jq

Юнит запускается как выделенный непривилегированный пользователь с ProtectSystem=strict, PrivateTmp и NoNewPrivileges. TLS завершается на nginx или ALB спереди — смотрите deploy/nginx.conf.example, который отключает буферизацию ответов (требуется для SSE-стриминга).

Использование

Сначала укажите модели на list_grafana_instances:

list_grafana_instances()
→ { "instances": [ {"name": "appstate", "url": "…", "connection": "live"}, … ],
    "routing_argument": "instance",
    "access": { "client": "claude-routines", "scope": "read-only" } }

Затем каждый другой инструмент принимает это имя:

search_dashboards(instance="appstate", query="latency")

Передайте check_health=true, чтобы также опрашивать каждую Grafana — медленнее, так как открывается соединение с каждым экземпляром.

Одна особенность именования

У upstream grafana_api_request уже есть обязательный параметр с именем endpoint (путь API). Внедрение аргумента маршрутизации с таким именем незаметно переопределит его, поэтому аргумент маршрутизации по умолчанию называется instance. Если переименовать его с помощью --routing-param endpoint, собственный параметр этого инструмента автоматически перепубликуется как api_path и будет обратно сопоставлен на пути — ни один инструмент никогда не будет сломан коллизией, независимо от вашего выбора.

Разработка

uv venv && uv pip install -e '.[dev,aws]'
uv run pytest                       # unit + integration

Интеграционные тесты запускают реальный дочерний процесс mcp-grafana против недоступной Grafana: достаточно для проверки обнаружения каталога, внедрения и удаления instance, маршрутизации и фильтрации аутентификации, без необходимости живых учетных данных. Установите MCP_GRAFANA_BINARY, чтобы указать на бинарный файл, иначе тесты пропускаются.

План развития

  • OAuth 2.1 — уровень аутентификации уже является интерфейсом, и SDK уже принимает OAuth-провайдера вместе с верификатором токенов. Заполнение OAuth2Provider.verify_token — это вся работа; auth/oauth.py документирует три шага. Сопоставьте группы IdP с существующими скоупами grafana:read / grafana:write / instance:<name>, и все проверки авторизации продолжат работать без изменений.

  • Fan-outinstance: "*" для выполнения одного запроса только для чтения по всем экземплярам и объединения результатов. Полезно для вопроса "какой из них оповещает?"; пока не реализовано, потому что объединение результатов заслуживает собственного дизайна.

-
license - not tested
-
quality - not tested
C
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Connectors

  • An MCP server giving access to Grafana dashboards, data and more.

  • Remote MCP for GenAI span mapping, provider normalization, dashboard schemas, and receipts.

  • MCP server for interacting with the Supabase platform

View all MCP Connectors

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/robert-sinclair/grafana-unified-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server