grafana-unified-mcp
grafana-unified-mcp
Un servidor MCP delante de muchas instancias de Grafana. Cada herramienta del servidor MCP estándar de Grafana, más un argumento adicional — instance — que indica contra qué Grafana ejecutarla.
query_prometheus(instance="tenant-a", expr="up", datasourceUid="...")
search_dashboards(instance="tenant-b", query="login latency")Por qué existe esto
El grafana/mcp-grafana original vincula GRAFANA_URL una sola vez, al iniciar el proceso. Lee X-Grafana-Service-Account-Token en cada solicitud, pero la URL es fija — y la cabecera que solía sobrescribirla ahora es explícitamente inerte. Del validate_url.go original:
Deprecated: X-Grafana-URL no longer configures the Grafana client. This middleware is retained temporarily to preserve malformed-header handling.
Así que un proceso de mcp-grafana solo puede hablar con un Grafana. Diez Grafanas significan diez servidores, diez entradas en cada configuración de cliente y diez conjuntos de herramientas con nombres idénticos que el modelo debe desambiguar.
Este servidor lo soluciona ejecutando un proceso hijo original por instancia y enrutando cada llamada al correcto según el argumento instance. Las herramientas se descubren desde el binario real en tiempo de ejecución, así que obtienes lo que el original exponga — actualmente 65 herramientas — sin código específico por herramienta aquí y nada que actualizar cuando el original añada más.
Related MCP server: mcphub
Cómo funciona
┌──────────────────────────────────┐
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 …GrafanaLos hijos se inician en el primer uso, permanecen activos, se reciclan cuando están inactivos (--idle-timeout, 15 min por defecto) y se reinician de forma transparente si mueren. Un Grafana inalcanzable degrada solo su propia instancia.
Instalación
Dos partes: el binario original y este paquete.
# 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] @ .'Si ya tienes el binario, apúntalo con MCP_GRAFANA_BINARY=/path/to/mcp-grafana o --mcp-grafana-binary.
Configuración
Endpoints
Exactamente la forma que esperarías — nombre de instancia a las variables de entorno del original:
{
"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"
}
}Claves opcionales por instancia: GRAFANA_ORG_ID, GRAFANA_USERNAME / GRAFANA_PASSWORD, description, extra_env, extra_args. Para mantener los secretos fuera del propio documento, usa GRAFANA_SERVICE_ACCOUNT_TOKEN_ENV (leído desde el entorno de este proceso) o GRAFANA_SERVICE_ACCOUNT_TOKEN_FILE (una ruta que el hijo lee).
Autenticación
{
"clients": [
{
"name": "claude-routines",
"token_sha256": "3f786850e387550fdab836ed7e6dc881de23001b…",
"instances": ["tenant-a", "tenant-b"],
"scope": "read-only"
},
{
"name": "platform-oncall",
"token_sha256": "…",
"instances": ["*"],
"scope": "read-write"
}
]
}Genera un token y su hash:
grafana-unified-mcp --hash-token # generates one
grafana-unified-mcp --hash-token 'my-existing-token'Dale token al cliente; pon token_sha256 en el documento. Los tokens se comparan por digest bajo hmac.compare_digest, y cada cliente se verifica en cada intento para que la posición de coincidencia no se filtre a través de la sincronización.
Se aplican dos cosas por llamador:
instances— el enuminstanceque ve un llamador se reduce a su concesión, y una llamada que nombre una instancia fuera de ella se rechaza con el mismo mensaje que una inexistente, así un token no puede enumerar lo que no puede alcanzar.scope— los llamadores deread-onlyni siquiera ven herramientas de mutación. La división proviene de la anotaciónreadOnlyHintdel propio original (49 de 65 herramientas son de solo lectura hoy), no de una lista mantenida aquí, así que las herramientas añadidas en el original se clasifican sin un cambio de código. Cualquier cosa sin anotar se trata como no de solo lectura.
Como refuerzo adicional, añade --child-arg=--disable-write para eliminar las herramientas de escritura en el origen para cada llamador.
Ejecución sin autenticación
--auth-mode none atiende a cada llamador que pueda alcanzar el puerto, solo lectura. No hay identidad para delimitar instancias, así que todas las instancias configuradas permanecen legibles — pero nada es escribible, porque un puerto abierto no debería poder reescribir un panel o eliminar una instantánea. Eso se aplica en tres capas:
el catálogo publicado omite toda herramienta de mutación;
la comprobación de autorización las rechaza incluso si un cliente nombra una directamente;
los hijos se inician con
--disable-write, así que el original también las rechaza.
La tercera capa es lo que lo convierte en algo más que un filtro. El original intercambia grafana_api_request por un registro separado solo de GET — sin parámetro body, method reducido a GET, no-GET rechazado en tiempo de ejecución — así que incluso un error en las capas 1 y 2 no podría convertirse en una escritura.
stdio es diferente: el llamador local ya posee el documento de endpoints y cada token en él, así que restringirlos sería teatro. stdio obtiene acceso completo.
Si necesitas escrituras sobre HTTP, usa tokens de portador con un cliente read-write en lugar de un puerto abierto.
De dónde viene la configuración
Cualquiera de estas, tanto para --endpoints como para --auth:
Fuente | Ejemplo |
Archivo |
|
Variable de entorno en línea |
|
AWS Secrets Manager |
|
AWS SSM Parameter Store |
|
Ambos documentos se releen cada --config-refresh-seconds (300 por defecto). Un refresco fallido registra y conserva el último valor bueno, así que un error transitorio de AWS o un archivo a medio escribir no pueden tumbar el servidor. Añadir una instancia no requiere reinicio; eliminar una detiene a su hijo.
Valida antes de iniciar:
grafana-unified-mcp --endpoints … --auth … --check-configEjecutar
# 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-urlimporta. El SDK aplica protección contra el rebinding de DNS basada en la cabeceraHost. Detrás de un proxy que reenvía un nombre de host público, ese host debe estar permitido o cada solicitud se rechaza.--public-urllo permite (y se usa para los metadatos de recursos RFC 9728);--allowed-hostañade más.
GET /healthz informa sobre la salud del proceso, los hijos activos y el estado del catálogo sin tocar Grafana.
Conectar un cliente
.mcp.json, para uso local con stdio:
{
"mcpServers": {
"grafana": {
"command": "/opt/grafana-unified-mcp/.venv/bin/grafana-unified-mcp",
"args": ["--endpoints", "/etc/grafana-unified-mcp/endpoints.json"]
}
}
}Para el servidor desplegado — incluidas las rutinas de Claude Code y las sesiones en la nube, que es el caso para el que existen los tokens de portador:
{
"mcpServers": {
"grafana": {
"type": "http",
"url": "https://grafana-mcp.example.com/mcp",
"headers": {
"Authorization": "Bearer ${GRAFANA_UNIFIED_MCP_TOKEN}"
}
}
}
}Establece GRAFANA_UNIFIED_MCP_TOKEN en el entorno donde se ejecute la sesión — para Claude Code en la web, eso son las variables del entorno, así que las rutinas programadas y las sesiones en la nube lo recogen sin que el secreto viva en el repositorio. Da a las rutinas un cliente read-only; guarda read-write para humanos.
Desplegar como servicio systemd
Consulta deploy/. En resumen:
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 | jqLa unidad se ejecuta como un usuario dedicado sin privilegios con ProtectSystem=strict, PrivateTmp y NoNewPrivileges. TLS termina en nginx o un ALB delante — consulta deploy/nginx.conf.example, que desactiva el buffering de respuestas (requerido para el streaming SSE).
Uso
Apunta el modelo a list_grafana_instances primero:
list_grafana_instances()
→ { "instances": [ {"name": "tenant-a", "url": "…", "connection": "live"}, … ],
"routing_argument": "instance",
"access": { "client": "claude-routines", "scope": "read-only" } }Luego cada otra herramienta toma ese nombre:
search_dashboards(instance="tenant-a", query="latency")Pasa check_health=true para también sondear cada Grafana — más lento, ya que abre una conexión a cada instancia.
Un matiz de nombres
El grafana_api_request del original ya tiene un parámetro obligatorio llamado endpoint (la ruta de la API). Inyectar un argumento de enrutamiento con ese nombre lo sombrearía silenciosamente, por eso el argumento de enrutamiento es instance por defecto. Si lo renombras con --routing-param endpoint, el parámetro propio de esa herramienta se republica automáticamente como api_path y se mapea de vuelta en el camino — ninguna herramienta se rompe por la colisión, sea lo que sea que elijas.
Desarrollo
uv venv && uv pip install -e '.[dev,aws]'
uv run pytest # unit + integrationLas pruebas de integración manejan un hijo real de mcp-grafana contra un Grafana inalcanzable: suficiente para probar el descubrimiento del catálogo, la inyección y eliminación de instance, el enrutamiento y el filtrado de autenticación, sin necesidad de credenciales en vivo. Establece MCP_GRAFANA_BINARY para apuntar al binario, o se omiten.
Hoja de ruta
OAuth 2.1 — la capa de autenticación ya es una interfaz, y el SDK ya acepta un proveedor OAuth junto al verificador de tokens. Completar
OAuth2Provider.verify_tokenes todo el trabajo;auth/oauth.pydocumenta los tres pasos. Mapea los grupos del IdP a los ámbitos existentesgrafana:read/grafana:write/instance:<name>y cada comprobación de autorización sigue funcionando sin cambios.Fan-out —
instance: "*"para ejecutar una consulta de solo lectura en cada instancia y fusionar resultados. Útil para "¿cuál de estas está alertando?"; omitido por ahora porque la fusión de resultados merece su propio diseño.
This server cannot be deployed
Maintenance
Related MCP Connectors
An MCP server giving access to Grafana dashboards, data and more.
Stateless MCP gateway and OTel span-streaming bridge for hosted MCP servers.
Governed MCP gateway: one endpoint for your tools, with credential custody and audit log.
MCP-first control plane for ProAgentStore agents and private instances.
Related MCP Servers
- FlicenseNot gradedqualityDmaintenanceProxy that aggregates multiple MCP servers and presents them as a unified interface, allowing clients to access resources from multiple servers transparently.4-
- AlicenseNot gradedqualityAmaintenanceA unified hub for centrally managing and dynamically orchestrating multiple MCP servers/APIs into separate endpoints with flexible routing strategies.846 npm2,488Apache 2.0
- AlicenseNot gradedqualityDmaintenanceEnables 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.8Apache 2.0
- FlicenseNot gradedqualityDmaintenanceEnables interaction with multiple Jenkins instances from a single MCP server, using header-based authentication for multi-tenancy.1-