Skip to main content
Glama
robert-sinclair

grafana-unified-mcp

grafana-unified-mcp

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

query_prometheus(instance="tenant-a", expr="up", datasourceUid="...")
search_dashboards(instance="tenant-b", query="login latency")

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

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

Устарело: X-Grafana-URL больше не настраивает клиент Grafana. Это промежуточное ПО временно сохранено для обработки некорректных заголовков.

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

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

Related MCP server: mcphub

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

                                        ┌──────────────────────────────────┐
  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│
                                        │ tenant-a │ │ tenant-b │ │   …      │
                                        └─────┬────┘ └───┬──────┘ └┬─────────┘
                                              ▼          ▼         ▼
                                          tenant-a    tenant-b   …Grafana

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

Установка

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

# 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.

Настройка

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

Именно то, что вы ожидаете — имя экземпляра для переменных окружения оригинального сервера:

{
  "tenant-a": {
    "GRAFANA_URL": "https://tenant-a.example.cloud/grafana",
    "GRAFANA_SERVICE_ACCOUNT_TOKEN": "glsa_…"
  },
  "tenant-b": {
    "GRAFANA_URL": "https://tenant-b.example.cloud/grafana",
    "GRAFANA_SERVICE_ACCOUNT_TOKEN": "glsa_…",
    "description": "Tenant B 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": ["tenant-a", "tenant-b"],
      "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 оригинального сервера (49 из 65 инструментов сегодня доступны только для чтения), а не на списке, поддерживаемом здесь, поэтому инструменты, добавленные оригинальным сервером, классифицируются без изменения кода. Все, что не аннотировано, считается не доступным только для чтения.

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

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

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

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

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

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

Третий уровень — это то, что делает это больше, чем просто фильтр. Оригинальный сервер заменяет 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": "tenant-a", "url": "…", "connection": "live"}, … ],
    "routing_argument": "instance",
    "access": { "client": "claude-routines", "scope": "read-only" } }

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

search_dashboards(instance="tenant-a", query="latency")

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

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

У оригинального 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-out — instance: "*" для выполнения одного запроса только для чтения по всем экземплярам и объединения результатов. Полезно для "какой из них сигнализирует?"; пока не реализовано, потому что объединение результатов заслуживает собственного проектирования.

Related MCP Connectors

Related MCP Servers

  • F
    license
    Not graded
    quality
    D
    maintenance
    Proxy that aggregates multiple MCP servers and presents them as a unified interface, allowing clients to access resources from multiple servers transparently.
    4
    -
  • A
    license
    Not graded
    quality
    D
    maintenance
    Enables MCP-compatible agents to interact with Grafana instances for searching, creating, and updating dashboards, exploring logs via Loki, querying datasources, managing alerts, incidents, and on-call shifts, and accessing observability data.
    8
    Apache 2.0