Skip to main content
Glama
robert-sinclair

grafana-unified-mcp

grafana-unified-mcp

Ein MCP-Server vor vielen Grafana-Instanzen. Jedes Tool des standardmäßigen Grafana-MCP-Servers, plus ein zusätzliches Argument — instance — das angibt, gegen welche Grafana-Instanz es ausgeführt werden soll.

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

Warum es das gibt

Das vorgelagerte grafana/mcp-grafana bindet GRAFANA_URL einmalig, beim Prozessstart. Es liest X-Grafana-Service-Account-Token pro Anfrage, aber die URL ist festgelegt — und der Header, der früher zum Überschreiben verwendet wurde, ist jetzt explizit wirkungslos. Aus dem vorgelagerten validate_url.go:

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

Ein mcp-grafana-Prozess kann also immer nur mit einer einzigen Grafana-Instanz sprechen. Zehn Grafana-Instanzen bedeuten zehn Server, zehn Einträge in jeder Client-Konfiguration und zehn Sätze von Tools mit identischen Namen, die das Modell auseinanderhalten muss.

Dieser Server behebt das, indem er einen vorgelagerten Kindprozess pro Instanz ausführt und jeden Aufruf basierend auf dem instance-Argument an den richtigen weiterleitet. Tools werden zur Laufzeit aus der echten Binärdatei entdeckt, sodass Sie erhalten, was auch immer der vorgelagerte Server bereitstellt — derzeit 65 Tools — ohne pro-Tool-Code hier und nichts, was aktualisiert werden muss, wenn der vorgelagerte Server weitere hinzufügt.

Related MCP server: mcphub

Wie es funktioniert

                                        ┌──────────────────────────────────┐
  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

Kinder starten bei der ersten Verwendung, bleiben warm, werden bei Inaktivität beendet (--idle-timeout, Standard 15 Min.) und werden transparent neu gestartet, wenn sie sterben. Eine unerreichbare Grafana-Instanz beeinträchtigt nur ihre eigene Instanz.

Installation

Zwei Teile: die vorgelagerte Binärdatei und dieses Paket.

# 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] @ .'

Wenn Sie die Binärdatei bereits haben, verweisen Sie mit MCP_GRAFANA_BINARY=/pfad/zu/mcp-grafana oder --mcp-grafana-binary darauf.

Konfiguration

Endpunkte

Genau die Form, die Sie erwarten würden — Instanzname zu den vorgelagerten Umgebungsvariablen:

{
  "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"
  }
}

Optionale pro-Instanz-Schlüssel: GRAFANA_ORG_ID, GRAFANA_USERNAME / GRAFANA_PASSWORD, description, extra_env, extra_args. Um Geheimnisse außerhalb des Dokuments selbst zu halten, verwenden Sie GRAFANA_SERVICE_ACCOUNT_TOKEN_ENV (aus der Umgebung dieses Prozesses gelesen) oder GRAFANA_SERVICE_ACCOUNT_TOKEN_FILE (ein Pfad, den das Kind liest).

Authentifizierung

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

Erstellen Sie ein Token und seinen Hash:

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

Geben Sie token an den Client; setzen Sie token_sha256 in das Dokument. Tokens werden durch den Digest unter hmac.compare_digest verglichen, und jeder Client wird bei jedem Versuch überprüft, sodass die Übereinstimmungsposition nicht durch Timing preisgegeben wird.

Pro Aufrufer werden zwei Dinge erzwungen:

  • instances — die instance-Aufzählung, die ein Aufrufer sieht, wird auf seine Berechtigung eingeschränkt, und ein Aufruf, der eine Instanz außerhalb dieser benennt, wird mit derselben Nachricht abgelehnt wie eine nicht existierende, sodass ein Token nicht aufzählen kann, was es nicht erreichen kann.

  • scope — read-only-Aufrufer sehen mutierende Tools nicht einmal. Die Aufteilung stammt aus der eigenen readOnlyHint-Annotation des vorgelagerten Servers (49 von 65 Tools sind heute schreibgeschützt), nicht aus einer hier gepflegten Liste, sodass neu hinzugefügte Tools ohne Codeänderung klassifiziert werden. Alles, was nicht annotiert ist, wird als nicht schreibgeschützt behandelt.

Für doppelte Sicherheit fügen Sie --child-arg=--disable-write hinzu, um Schreib-Tools an der Quelle für jeden Aufrufer zu entfernen.

Ausführung ohne Authentifizierung

--auth-mode none bedient jeden Aufrufer, der den Port erreichen kann, schreibgeschützt. Es gibt keine Identität, um Instanzen danach abzugrenzen, daher bleiben alle konfigurierten Instanzen lesbar — aber nichts ist beschreibbar, da ein offener Port kein Dashboard umschreiben oder einen Snapshot löschen können sollte. Das wird auf drei Ebenen erzwungen:

  1. Der veröffentlichte Katalog lässt jedes mutierende Tool aus;

  2. Die Autorisierungsprüfung verweigert sie, selbst wenn ein Client eines direkt benennt;

  3. Kinder werden mit --disable-write gestartet, sodass der vorgelagerte Server sie ebenfalls ablehnt.

Die dritte Ebene ist es, die es mehr als einen Filter macht. Der vorgelagerte Server tauscht grafana_api_request gegen eine separate GET-only-Registrierung aus — kein body-Parameter, method auf GET eingeschränkt, Nicht-GET zur Laufzeit abgelehnt — sodass selbst ein Fehler in den Ebenen 1 und 2 nicht zu einem Schreibzugriff werden könnte.

stdio ist anders: Der lokale Aufrufer hat bereits das Endpunkte-Dokument und jedes Token darin, daher wäre eine Einschränkung für ihn Theater. stdio erhält vollen Zugriff.

Wenn Sie Schreibzugriffe über HTTP benötigen, verwenden Sie Bearer-Tokens mit einem read-write-Client anstelle eines offenen Ports.

Woher die Konfiguration kommt

Jede dieser Quellen, sowohl für --endpoints als auch für --auth:

Quelle

Beispiel

Datei

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

Inline-Umgebungsvariable

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

Beide Dokumente werden alle --config-refresh-seconds (Standard 300) neu gelesen. Ein fehlgeschlagenes Aktualisieren protokolliert den Fehler und behält den letzten gültigen Wert, sodass ein vorübergehender AWS-Fehler oder eine halb geschriebene Datei den Server nicht lahmlegen kann. Das Hinzufügen einer Instanz benötigt keinen Neustart; das Entfernen einer Instanz stoppt ihr Kind.

Vor dem Start validieren:

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

Ausführen

# 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 ist wichtig. Das SDK wendet DNS-Rebinding-Schutz basierend auf dem Host-Header an. Hinter einem Proxy, der einen öffentlichen Hostnamen weiterleitet, muss dieser Host erlaubt sein, oder jede Anfrage wird abgelehnt. --public-url erlaubt ihn (und wird für RFC 9728-Ressourcenmetadaten verwendet); --allowed-host fügt weitere hinzu.

GET /healthz meldet die Prozessgesundheit, aktive Kinder und den Katalogstatus, ohne Grafana zu berühren.

Einen Client verbinden

.mcp.json, für die lokale stdio-Nutzung:

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

Für den bereitgestellten Server — einschließlich Claude Code-Routinen und Cloud-Sitzungen, wofür die Bearer-Tokens existieren:

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

Setzen Sie GRAFANA_UNIFIED_MCP_TOKEN in der Umgebung, in der die Sitzung läuft — für Claude Code im Web sind das die Umgebungsvariablen, sodass geplante Routinen und Cloud-Sitzungen es aufnehmen, ohne dass das Geheimnis im Repository lebt. Geben Sie Routinen einen read-only-Client; behalten Sie read-write für Menschen.

Als systemd-Dienst bereitstellen

Siehe deploy/. Kurz gesagt:

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

Die Unit läuft als dedizierter, unprivilegierter Benutzer mit ProtectSystem=strict, PrivateTmp und NoNewPrivileges. TLS terminiert an nginx oder einem ALB davor — siehe deploy/nginx.conf.example, das die Antwortpufferung deaktiviert (erforderlich für SSE-Streaming).

Verwendung

Weisen Sie das Modell zuerst auf list_grafana_instances:

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

Dann akzeptiert jedes andere Tool diesen Namen:

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

Übergeben Sie check_health=true, um auch jede Grafana-Instanz zu prüfen — langsamer, da es eine Verbindung zu jeder Instanz öffnet.

Eine Namensbesonderheit

Das vorgelagerte grafana_api_request hat bereits einen erforderlichen Parameter namens endpoint (der API-Pfad). Das Injizieren eines Routing-Arguments mit diesem Namen würde es stillschweigend überschatten, weshalb das Routing-Argument standardmäßig instance ist. Wenn Sie es mit --routing-param endpoint umbenennen, wird der eigene Parameter dieses Tools automatisch als api_path neu veröffentlicht und auf dem Rückweg zurückgemappt — kein Tool wird jemals durch die Kollision beschädigt, egal was Sie wählen.

Entwicklung

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

Die Integrationstests treiben ein echtes mcp-grafana-Kind gegen eine unerreichbare Grafana-Instanz an: genug, um die Katalogerkennung, das Injizieren und Entfernen von instance, das Routing und die Authentifizierungsfilterung zu beweisen, ohne dass Live-Anmeldeinformationen erforderlich sind. Setzen Sie MCP_GRAFANA_BINARY, um auf die Binärdatei zu verweisen, oder sie werden übersprungen.

Fahrplan

  • OAuth 2.1 — die Authentifizierungsschicht ist bereits ein Interface, und das SDK akzeptiert bereits einen OAuth-Anbieter neben dem Token-Verifizierer. Das Ausfüllen von OAuth2Provider.verify_token ist die gesamte Aufgabe; auth/oauth.py dokumentiert die drei Schritte. Ordnen Sie IdP-Gruppen den bestehenden grafana:read / grafana:write / instance:<name>-Bereichen zu, und jede Autorisierungsprüfung funktioniert unverändert weiter.

  • Fan-out — instance: "*", um eine einzige schreibgeschützte Abfrage über alle Instanzen hinweg auszuführen und die Ergebnisse zusammenzuführen. Nützlich für "Welche von diesen alarmiert?"; vorerst weggelassen, weil die Zusammenführung von Ergebnissen ein eigenes Design verdient.

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