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никогда даже не видят изменяющие инструменты. Разделение происходит из аннотацииreadOnlyHintupstream (на сегодня 49 из 65 инструментов доступны только для чтения), а не из списка, поддерживаемого здесь, поэтому инструменты, добавленные upstream, классифицируются без изменения кода. Всё, что не аннотировано, считается не read-only.
Для надёжности добавьте --child-arg=--disable-write, чтобы удалить инструменты записи в источнике для всех вызывающих.
Запуск без аутентификации
--auth-mode none обслуживает каждого вызывающего, который может достичь порта, только для чтения. Нет идентичности для ограничения экземпляров, поэтому все настроенные экземпляры остаются читаемыми — но ничего не доступно для записи, потому что открытый порт не должен иметь возможность переписать дашборд или удалить снимок. Это обеспечивается на трёх уровнях:
опубликованный каталог опускает все изменяющие инструменты;
проверка авторизации отклоняет их, даже если клиент называет один напрямую;
дочерние процессы запускаются с
--disable-write, поэтому upstream также отклоняет их.
Третий уровень — это то, что делает это больше, чем фильтр. Upstream заменяет grafana_api_request на отдельную регистрацию только для GET — без параметра body, method сужается до GET, не-GET отклоняется во время выполнения — так что даже ошибка на уровнях 1 и 2 не может привести к записи.
stdio отличается: локальный вызывающий уже имеет документ конечных точек и все токены в нём, поэтому ограничивать их было бы театральностью. stdio получает полный доступ.
Если вам нужна запись через HTTP, используйте токены Bearer с клиентом read-write, а не открытый порт.
Откуда берётся конфигурация
Любой из этих источников, как для --endpoints, так и для --auth:
Источник | Пример |
Файл |
|
Встроенная переменная окружения |
|
AWS Secrets Manager |
|
AWS SSM Parameter Store |
|
Оба документа перечитываются каждые --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-out —
instance: "*"для выполнения одного запроса только для чтения по всем экземплярам и объединения результатов. Полезно для вопроса "какой из них оповещает?"; пока не реализовано, потому что объединение результатов заслуживает собственного дизайна.
This server cannot be installed
Maintenance
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
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
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