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="appstate", expr="up", datasourceUid="...")
search_dashboards(instance="uoregon", query="login latency")Warum es das gibt
Das Upstream-Projekt 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 diente, ist jetzt explizit wirkungslos. Aus dem Upstream-Code validate_url.go:
Deprecated: X-Grafana-URL no longer configures the Grafana client. This middleware is retained temporarily to preserve malformed-header handling.
Ein einzelner mcp-grafana-Prozess kann also immer nur mit einer einzigen Grafana-Instanz kommunizieren. Zehn Grafanas bedeuten zehn Server, zehn Einträge in jeder Client-Konfiguration und zehn Sätze von Tools mit identischen Namen, die das Modell unterscheiden muss.
Dieser Server behebt das, indem er einen Upstream-Kindprozess pro Instanz ausführt und jeden Aufruf basierend auf dem instance-Argument an die richtige Instanz weiterleitet. Tools werden zur Laufzeit aus der echten Binärdatei ermittelt, sodass Sie erhalten, was auch immer der Upstream bereitstellt — derzeit 65 Tools — ohne pro-Tool-Code hier und ohne Aktualisierungsbedarf, wenn der Upstream weitere hinzufügt.
Funktionsweise
┌──────────────────────────────────┐
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 …GrafanaKindprozesse starten bei erster Nutzung, bleiben warm, werden bei Leerlauf beendet (--idle-timeout, Standard 15 Min.) und bei Absturz transparent neu gestartet. Eine nicht erreichbare Grafana-Instanz beeinträchtigt nur ihre eigene Instanz.
Installation
Zwei Teile: die Upstream-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 erwartete Form — Instanzname zu den Upstream-Umgebungsvariablen:
{
"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"
}
}Optionale Schlüssel pro Instanz: GRAFANA_ORG_ID, GRAFANA_USERNAME / GRAFANA_PASSWORD, description, extra_env, extra_args. Um Geheimnisse aus dem Dokument selbst herauszuhalten, verwenden Sie GRAFANA_SERVICE_ACCOUNT_TOKEN_ENV (aus der Umgebung dieses Prozesses gelesen) oder GRAFANA_SERVICE_ACCOUNT_TOKEN_FILE (ein Pfad, den der Kindprozess liest).
Authentifizierung
{
"clients": [
{
"name": "claude-routines",
"token_sha256": "3f786850e387550fdab836ed7e6dc881de23001b…",
"instances": ["appstate", "uoregon"],
"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. Token werden mittels Digest unter hmac.compare_digest verglichen, und jeder Client wird bei jedem Versuch überprüft, sodass die Trefferposition nicht durch das Timing preisgegeben wird.
Pro Aufrufer werden zwei Dinge erzwungen:
instances— dieinstance-Aufzählung, die ein Aufrufer sieht, wird auf seine Berechtigung eingeschränkt, und ein Aufruf, der eine Instanz außerhalb dieser Berechtigung nennt, wird mit derselben Meldung abgelehnt wie eine nicht existierende Instanz, 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 eigenenreadOnlyHint-Annotation des Upstreams (derzeit 49 von 65 Tools sind 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 einzuschränken, 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. Dies wird auf drei Ebenen durchgesetzt:
Der veröffentlichte Katalog lässt jedes mutierende Tool aus;
Die Autorisierungsprüfung lehnt sie ab, selbst wenn ein Client eines direkt nennt;
Kindprozesse werden mit
--disable-writegestartet, sodass der Upstream sie ebenfalls ablehnt.
Die dritte Ebene macht es zu mehr als einem Filter. Der Upstream tauscht grafana_api_request gegen eine separate, nur-GET-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 führen könnte.
stdio ist anders: Der lokale Aufrufer besitzt bereits das Endpunkte-Dokument und jedes darin enthaltene Token, daher wäre eine Einschränkung Theater. stdio erhält vollen Zugriff.
Wenn Sie Schreibzugriffe über HTTP benötigen, verwenden Sie Bearer-Token mit einem read-write-Client anstelle eines offenen Ports.
Woher die Konfiguration stammt
Jede dieser Quellen, sowohl für --endpoints als auch für --auth:
Quelle | Beispiel |
Datei |
|
Inline-Umgebungsvariable |
|
AWS Secrets Manager |
|
AWS SSM Parameter Store |
|
Beide Dokumente werden alle --config-refresh-seconds (Standard 300) neu gelesen. Ein fehlgeschlagenes Aktualisieren wird protokolliert 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 erfordert keinen Neustart; das Entfernen einer Instanz stoppt ihren Kindprozess.
Vor dem Start validieren:
grafana-unified-mcp --endpoints … --auth … --check-configAusführung
# 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-urlist wichtig. Das SDK wendet DNS-Rebinding-Schutz basierend auf demHost-Header an. Hinter einem Proxy, der einen öffentlichen Hostnamen weiterleitet, muss dieser Host erlaubt sein, sonst wird jede Anfrage abgelehnt.--public-urlerlaubt ihn (und wird für RFC 9728-Ressourcenmetadaten verwendet);--allowed-hostfügt weitere hinzu.
GET /healthz meldet die Prozessgesundheit, aktive Kindprozesse und den Katalogstatus, ohne Grafana zu kontaktieren.
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-Token gedacht sind:
{
"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 | jqDie Unit läuft als dedizierter, unprivilegierter Benutzer mit ProtectSystem=strict, PrivateTmp und NoNewPrivileges. TLS wird an nginx oder einem ALB davor terminiert — siehe deploy/nginx.conf.example, das die Antwortpufferung deaktiviert (erforderlich für SSE-Streaming).
Verwendung
Weisen Sie das Modell zuerst auf list_grafana_instances hin:
list_grafana_instances()
→ { "instances": [ {"name": "appstate", "url": "…", "connection": "live"}, … ],
"routing_argument": "instance",
"access": { "client": "claude-routines", "scope": "read-only" } }Dann akzeptiert jedes andere Tool diesen Namen:
search_dashboards(instance="appstate", query="latency")Übergeben Sie check_health=true, um auch jede Grafana-Instanz zu überprüfen — langsamer, da eine Verbindung zu jeder Instanz geöffnet wird.
Eine Namensbesonderheit
Der Upstream-Parameter grafana_api_request hat bereits einen erforderlichen Parameter namens endpoint (den API-Pfad). Das Einfügen eines Routing-Arguments mit diesem Namen würde es stillschweigend überschatten, weshalb das Routing-Argument standardmäßig instance heißt. 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 + integrationDie Integrationstests treiben einen echten mcp-grafana-Kindprozess gegen eine nicht erreichbare Grafana-Instanz an: genug, um Katalogermittlung, instance-Injektion und -Entfernung, Routing und Auth-Filterung zu beweisen, ohne dass Live-Anmeldeinformationen erforderlich sind. Setzen Sie MCP_GRAFANA_BINARY, um auf die Binärdatei zu verweisen, sonst werden sie übersprungen.
Fahrplan
OAuth 2.1 — die Auth-Schicht ist bereits ein Interface, und das SDK akzeptiert bereits einen OAuth-Anbieter neben dem Token-Verifizierer. Das Ausfüllen von
OAuth2Provider.verify_tokenist die gesamte Aufgabe;auth/oauth.pydokumentiert die drei Schritte. Ordnen Sie IdP-Gruppen den vorhandenen Bereichengrafana:read/grafana:write/instance:<name>zu, und jede Autorisierungsprüfung funktioniert unverändert weiter.Fan-out —
instance: "*", um eine einzige schreibgeschützte Abfrage über alle Instanzen auszuführen und die Ergebnisse zusammenzuführen. Nützlich für "Welche von diesen alarmiert?"; vorerst weggelassen, da die Zusammenführung von Ergebnissen ein eigenes Design verdient.
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