# Grafana's own MCP server: it exposes Prometheus, Loki and Tempo as tools an # LLM can call, so a question like "what failed in the last transaction" is # answered by querying, rather than by building another panel. # # Works against OSS Grafana 9.0+. The Cloud-only tools — Incidents, OnCall, # Sift, Agent Observability, Assistant — are simply absent here; the # datasource, dashboard and alerting tools are the ones that matter. # # Reachable in-cluster only for now, with no OIDC and no audit trail. The # intended end state is the same treatment as the five domain MCP servers: # behind a Kong gateway with openid-connect, consumer groups and http-log. # Until then, anything that can reach this Service can read all the telemetry. apiVersion: apps/v1 kind: Deployment metadata: name: mcp-grafana labels: app.kubernetes.io/name: mcp-grafana app.kubernetes.io/component: mcp spec: replicas: 1 selector: matchLabels: app.kubernetes.io/name: mcp-grafana template: metadata: labels: app.kubernetes.io/name: mcp-grafana app.kubernetes.io/component: mcp spec: containers: - name: mcp-grafana image: mcp/grafana:latest args: # Streamable HTTP rather than stdio: this runs as a service other # pods connect to, not as a subprocess of one client. - "-t" - "streamable-http" - "--log-level" - "info" # DNS-rebinding protection: the server rejects any Host header not # on this list, defaulting to loopback only — which is why the first # in-cluster probe got "forbidden: host not allowed". Name the # Service instead of disabling validation with "*": the point of the # check is that only callers using the expected name get through. - "--allowed-hosts" - "mcp-grafana:8000,mcp-grafana.observability.svc.cluster.local:8000" env: - name: GRAFANA_URL # In-cluster address, not the ingress: no TLS hop, no dependency # on sttlab.local resolving inside the cluster. value: http://grafana:3000 - name: GRAFANA_SERVICE_ACCOUNT_TOKEN valueFrom: secretKeyRef: name: observability-secrets key: GRAFANA_SERVICE_ACCOUNT_TOKEN ports: - name: http containerPort: 8000 resources: requests: cpu: 20m memory: 64Mi limits: memory: 256Mi securityContext: allowPrivilegeEscalation: false runAsNonRoot: true # Numeric, because the image declares its user by name # (USER mcp-grafana). Kubernetes cannot resolve a name to check it # is not root, and refuses to start the container with # runAsNonRoot: "image has non-numeric user". 1000/999 are what the # image actually uses. runAsUser: 1000 runAsGroup: 999 capabilities: drop: ["ALL"]