Skip to main content
Glama
robert-sinclair

grafana-unified-mcp

grafana-unified-mcp

Un servidor MCP para múltiples instancias de Grafana. Todas las herramientas del servidor MCP estándar de Grafana, más un argumento adicional — instance — que indica contra qué Grafana ejecutarlas.

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

Por qué existe

El upstream grafana/mcp-grafana vincula GRAFANA_URL una vez, al inicio del proceso. Lee X-Grafana-Service-Account-Token por petición, pero la URL es fija — y el encabezado que solía anularla ahora está explícitamente inactivo. Del upstream validate_url.go:

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

Por lo tanto, un proceso mcp-grafana solo puede comunicarse con una única instancia de 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 soluciona eso ejecutando un proceso hijo upstream por instancia y enrutando cada llamada a la correcta según el argumento instance. Las herramientas se descubren desde el binario real en tiempo de ejecución, por lo que obtienes lo que upstream expone — actualmente 65 herramientas — sin código específico por herramienta aquí y nada que actualizar cuando upstream añada más.

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│
                                        │ appstate │ │ uoregon  │ │   …      │
                                        └─────┬────┘ └───┬──────┘ └┬─────────┘
                                              ▼          ▼         ▼
                                          appstate    uoregon    …Grafana

Los hijos se inician en el primer uso, permanecen activos, se cierran cuando están inactivos (--idle-timeout, por defecto 15 min) y se reinician transparentemente si mueren. Una instancia de Grafana inaccesible solo degrada su propia instancia.

Instalación

Dos piezas: el binario upstream 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, apunta a él con MCP_GRAFANA_BINARY=/ruta/a/mcp-grafana o --mcp-grafana-binary.

Configuración

Puntos finales

Exactamente la forma que esperarías: nombre de instancia a las variables de entorno 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"
  }
}

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 del 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": ["appstate", "uoregon"],
      "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'

Proporciona token al cliente; coloca 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 del tiempo.

Se aplican dos cosas por cada llamante:

  • instances — el enum instance que ve un llamante 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, por lo que un token no puede enumerar lo que no puede alcanzar.

  • scope — los llamantes read-only ni siquiera ven las herramientas mutantes. La división proviene de la propia anotación readOnlyHint de upstream (49 de 65 herramientas son de solo lectura hoy), no de una lista mantenida aquí, por lo que las herramientas añadidas por upstream se clasifican sin un cambio de código. Cualquier cosa no anotada se trata como no de solo lectura.

Para mayor seguridad, añade --child-arg=--disable-write para eliminar las herramientas de escritura en el origen para todos los llamantes.

Ejecución sin autenticación

--auth-mode none atiende a todos los llamantes que puedan alcanzar el puerto, solo lectura. No hay identidad para limitar instancias, por lo que todas las instancias configuradas permanecen legibles — pero nada es escribible, porque un puerto abierto no debiera poter reescribir un panel o eliminar una instantánea. Eso se aplica en tres capas:

  1. El catálogo publado omitte toda herramienta mutante;

  2. La comporbación de autorización las rechazaaunque un client las nombr diretamente;

  3. Los hijos se inician con --disable-write, por lo qu upstream también las rechaza.

La tercera capa es lo que lo hace más que un filtro. Upstream intercamba grafana_api_request por un registro separado solo para GET — sin parámetro body, method restringido a GET, no-GET rehazado en tiempo de ejecución — por lo que inclusive un error en las capas 1 y 2 no podría convertirse en una escritura.

stdio es diferente: el llamante local ya posee el documento de puntos finales y cada token en él, por lo que restringirlos sería teatro. stdio obtiene acceso completo.

Si necesitas escrituras sobre HTTP, utiliza tokens de portador con un cliente read-write en lugar de un puerto abierto.

De dónde proviene la configuración

Cualquiera de estos, tanto para --endpoints como para --auth:

Fuente

Ejemplo

Archivo

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

Variable de entorno en línea

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

Ambos documentos se releen cada --config-refresh-seconds (por defecto 300). Una actualización fallida registra un log y conserva el último valor bueno, por lo que un error transitorio de AWS o un archivo mal escrito no pueden derribar el servidor. Añadir una instancia no necesita reinicio; eliminar una detiene a su hijo.

Valida antes de iniciar:

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

Ejecutar

# 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 es importante. El SDK aplica protección contra el secuestro de DNS basado en el encabezado Host. Detrás de un proxy que reenvía un nombre de host público, ese host debe estar permitido o cada petición será rechazada. --public-url lo permite (y se usa para metadatos de recurso RFC 9728); --allowed-host añade más.

GET /healthz informa 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 — incluyendo rutinas de Claude Code y 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 ejecuta la sesión — para Claude Code en la web, eso son las variables de entorno, por lo que las rutinas programadas y las sesiones en la nube lo toman sin que el secreto viva en el repositorio. Asigna un cliente read-only a las rutinas; mantén read-write para los humanos.

Desplegar como un 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 | jq

La unidad se ejecuta como un usuario no privilegiado dedicado con ProtectSystem=strict, PrivateTmp y NoNewPrivileges. TLS termina en nginx o un ALB al frente — consulta deploy/nginx.conf.example, que deshabilita el almacenamiento en búfer de respuesta (requerido para transmisión SSE).

Usarlo

Apunta el modelo a list_grafana_instances primero:

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

Luego, todas las demás herramientas toman ese nombre:

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

Pasa check_health=true para también sondear cada Grafana — más lento, ya que abre una conexión a cada instancia.

Una peculiaridad de nombres

El grafana_api_request de upstream ya tiene un parámetro requerido llamado endpoint (la ruta de la API). Inyectar un argumento de enrutamiento con ese nombre lo sombrearía silenciosamente, por lo que 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 de ida — ninguna herramienta se rompe por la colisión, sea cual sea tu eleción.

Desarrolo

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

Las pruebas de integración impulsan un hijo real de mcp-grafana contra un Grafana inalcanzable: suficente para probar el desubrimiento del catálogo, la inyección y eliminación de instance, el enrutamiento y el filtrao de autenticación, sin nesesitar credenciales vivas. Estalec MCP_GRAFANA_BINARY para apuntar al binario, o se sáltan.

Hoja de ruta

  • OAuth 2.1 — la capa de autenticación ya es una interface, y el SDK ya acepta un proveedor de OAuth junto al verifcador de token. Completar OAuth2Provier.verify_token es todo el trabajo; auth/oauth.py documenta los tres pasos. Mapea los grupos del IdP en los ámbitos existentes grafana:read / grafana:write / instance:<name> y toda verificación de autorización sigue funcinando sin cambios.

  • Fan-outinstance: "*" para ejecutar una consulta de solo lectura en cada instancia y combinar los resultados. Útil para "¿cuál de estas está alerando?"; se omite por aora porque la combinación de resultados merece su propio diseño.

-
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