Skip to main content
Glama
robert-sinclair

grafana-unified-mcp

grafana-unified-mcp

하나의 MCP 서버로 여러 Grafana 인스턴스를 관리합니다. 표준 Grafana MCP 서버의 모든 도구를 제공하며, instance라는 추가 인수를 통해 실행할 Grafana를 지정할 수 있습니다.

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

존재 이유

상위 프로젝트 grafana/mcp-grafana는 프로세스 시작 시 GRAFANA_URL을 한 번만 바인딩합니다. 요청마다 X-Grafana-Service-Account-Token을 읽지만 URL은 고정되어 있으며, 이를 재정의하는 데 사용되던 헤더는 이제 명시적으로 비활성화되었습니다. 상위 프로젝트의 validate_url.go에서:

Deprecated: X-Grafana-URL은 더 이상 Grafana 클라이언트를 구성하지 않습니다. 이 미들웨어는 잘못된 형식의 헤더 처리를 유지하기 위해 임시로 보관됩니다.

따라서 하나의 mcp-grafana 프로세스는 오직 하나의 Grafana와만 통신할 수 있습니다. 10개의 Grafana가 있다면 10개의 서버, 모든 클라이언트 설정에 10개의 항목, 그리고 모델이 구분해야 하는 동일한 이름의 10개 도구 세트가 필요합니다.

이 서버는 인스턴스별로 하나의 상위 프로세스를 실행하고 instance 인수에 따라 각 호출을 올바른 프로세스로 라우팅하여 이 문제를 해결합니다. 도구는 런타임에 실제 바이너리에서 검색되므로, 현재 65개의 도구를 포함하여 상위 프로젝트가 노출하는 모든 도구를 사용할 수 있으며, 여기에는 도구별 코드가 없고 상위 프로젝트가 새 도구를 추가할 때 업데이트할 필요가 없습니다.

Related MCP server: mcphub

작동 방식

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

자식 프로세스는 첫 사용 시 시작되고, 활성 상태를 유지하며, 유휴 시간(--idle-timeout, 기본값 15분)이 지나면 종료되고, 죽으면 투명하게 다시 생성됩니다. 연결할 수 없는 Grafana는 해당 인스턴스에만 영향을 미칩니다.

설치

두 가지 구성 요소가 필요합니다: 상위 바이너리와 이 패키지입니다.

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

이미 바이너리가 있다면 MCP_GRAFANA_BINARY=/path/to/mcp-grafana 또는 --mcp-grafana-binary로 경로를 지정하세요.

설정

엔드포인트

예상되는 형태 그대로입니다 — 인스턴스 이름을 상위 환경 변수에 매핑합니다:

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

인스턴스별 선택적 키: GRAFANA_ORG_ID, GRAFANA_USERNAME / GRAFANA_PASSWORD, description, extra_env, extra_args. 문서 자체에서 비밀 정보를 보호하려면 GRAFANA_SERVICE_ACCOUNT_TOKEN_ENV (이 프로세스의 환경 변수에서 읽음) 또는 GRAFANA_SERVICE_ACCOUNT_TOKEN_FILE (자식 프로세스가 읽는 파일 경로)을 사용하세요.

인증

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

토큰과 그 해시를 생성합니다:

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

클라이언트에 token을 제공하고, 문서에 token_sha256을 넣습니다. 토큰은 hmac.compare_digest를 사용해 다이제스트로 비교되며, 모든 클라이언트는 매 시도마다 확인되므로 일치 위치가 타이밍을 통해 유출되지 않습니다.

호출자별로 두 가지가 적용됩니다:

  • instances — 호출자가 볼 수 있는 instance 열거형은 부여된 범위로 제한되며, 범위 밖의 인스턴스를 호출하면 존재하지 않는 인스턴스와 동일한 메시지로 거부되므로 토큰이 접근할 수 없는 인스턴스를 열거할 수 없습니다.

  • scope — read-only 호출자는 변형 도구를 볼 수조차 없습니다. 이 구분은 여기서 유지 관리하는 목록이 아닌 상위 프로젝트 자체의 readOnlyHint 어노테이션(현재 65개 도구 중 49개가 읽기 전용)에서 비롯되므로, 상위 프로젝트에서 추가된 도구는 코드 변경 없이 분류됩니다. 어노테이션이 없는 것은 읽기 전용이 아닌 것으로 처리됩니다.

이중 안전 장치로 --child-arg=--disable-write를 추가하면 모든 호출자에 대해 소스에서 쓰기 도구를 제거합니다.

인증 없이 실행

--auth-mode none은 포트에 접근할 수 있는 모든 호출자에게 읽기 전용으로 서비스를 제공합니다. 인스턴스를 범위 지정할 신원이 없으므로 모든 설정된 인스턴스는 읽을 수 있지만, 열린 포트가 대시보드를 수정하거나 스냅샷을 삭제할 수 없어야 하므로 아무것도 쓸 수 없습니다. 이는 세 가지 계층에서 적용됩니다:

  1. 게시된 카탈로그에서 모든 변형 도구를 생략합니다;

  2. 인증 검사가 클라이언트가 직접 도구를 호출하더라도 거부합니다;

  3. 자식 프로세스는 --disable-write로 시작되어 상위 프로젝트도 거부합니다.

세 번째 계층이 단순한 필터 이상으로 만듭니다. 상위 프로젝트는 grafana_api_request를 별도의 GET 전용 등록으로 교체합니다 — body 매개변수 없음, method가 GET으로 제한됨, GET이 아닌 요청은 런타임에 거부됨 — 따라서 계층 1과 2에 버그가 있더라도 쓰기 작업으로 이어질 수 없습니다.

stdio는 다릅니다: 로컬 호출자는 이미 엔드포인트 문서와 모든 토큰을 보유하고 있으므로 제한하는 것은 무의미합니다. stdio는 전체 액세스 권한을 얻습니다.

HTTP를 통해 쓰기 작업이 필요하다면, 열린 포트 대신 read-write 클라이언트와 함께 베어러 토큰을 사용하세요.

설정 출처

--endpoints와 --auth 모두에 대해 다음 중 하나를 사용할 수 있습니다:

출처

예시

파일

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

인라인 환경 변수

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

두 문서 모두 --config-refresh-seconds(기본값 300)마다 다시 읽힙니다. 새로고침 실패 시 로그를 남기고 마지막 유효한 값을 유지하므로, 일시적인 AWS 오류나 불완전하게 작성된 파일이 서버를 중단시키지 않습니다. 인스턴스 추가는 재시작이 필요 없으며, 제거는 해당 자식 프로세스를 중지합니다.

시작 전에 검증:

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

실행

# 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이 중요합니다. SDK는 Host 헤더를 기반으로 DNS-리바인딩 보호를 적용합니다. 공개 호스트 이름을 전달하는 프록시 뒤에서는 해당 호스트가 허용되어야 하며, 그렇지 않으면 모든 요청이 거부됩니다. --public-url이 이를 허용하고(RFC 9728 리소스 메타데이터에 사용됨), --allowed-host가 더 많은 호스트를 추가합니다.

GET /healthz는 Grafana에 접촉하지 않고 프로세스 상태, 활성 자식 프로세스, 카탈로그 상태를 보고합니다.

클라이언트 연결

로컬 stdio 사용을 위한 .mcp.json:

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

배포된 서버의 경우 — Claude Code 루틴 및 클라우드 세션 포함, 이것이 베어러 토큰이 존재하는 이유입니다:

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

세션이 실행되는 환경에 GRAFANA_UNIFIED_MCP_TOKEN을 설정하세요 — 웹의 Claude Code의 경우 환경 변수이므로, 예약된 루틴과 클라우드 세션이 비밀 정보를 저장소에 저장하지 않고도 이를 사용할 수 있습니다. 루틴에는 read-only 클라이언트를 부여하고, read-write는 사람을 위해 유지하세요.

systemd 서비스로 배포

deploy/를 참조하세요. 간단히:

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

유닛은 전용 권한 없는 사용자로 실행되며 ProtectSystem=strict, PrivateTmp, NoNewPrivileges가 적용됩니다. TLS는 앞단의 nginx 또는 ALB에서 종료됩니다 — deploy/nginx.conf.example을 참조하세요. 응답 버퍼링을 비활성화합니다(SSE 스트리밍에 필요).

사용 방법

먼저 모델을 list_grafana_instances로 안내하세요:

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

그런 다음 다른 모든 도구는 해당 이름을 사용합니다:

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

check_health=true를 전달하면 각 Grafana를 추가로 프로빙합니다 — 모든 인스턴스에 연결을 열어야 하므로 더 느립니다.

이름 관련 주의사항

상위 프로젝트의 grafana_api_request에는 이미 endpoint(API 경로)라는 필수 매개변수가 있습니다. 이 이름으로 라우팅 인수를 주입하면 조용히 가려지므로, 기본적으로 라우팅 인수는 instance입니다. --routing-param endpoint로 이름을 변경하면 해당 도구의 자체 매개변수가 자동으로 api_path로 다시 게시되고 통과 시 다시 매핑되므로, 어떤 이름을 선택하든 도구가 충돌로 인해 손상되지 않습니다.

개발

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

통합 테스트는 실제 mcp-grafana 자식 프로세스를 연결할 수 없는 Grafana에 대해 구동합니다: 카탈로그 검색, instance 주입 및 제거, 라우팅, 인증 필터링을 증명하기에 충분하며, 실제 자격 증명이 필요하지 않습니다. MCP_GRAFANA_BINARY를 바이너리 경로로 설정하세요. 그렇지 않으면 테스트가 건너뜁니다.

로드맵

  • OAuth 2.1 — 인증 계층은 이미 인터페이스이며, SDK는 이미 토큰 검증기와 함께 OAuth 제공자를 받습니다. OAuth2Provider.verify_token을 구현하는 것이 전부입니다; auth/oauth.py는 세 단계를 문서화합니다. IdP 그룹을 기존 grafana:read / grafana:write / instance:<name> 범위에 매핑하면 모든 인증 검사가 변경 없이 계속 작동합니다.

  • Fan-out — instance: "*"로 모든 인스턴스에 대해 하나의 읽기 전용 쿼리를 실행하고 결과를 병합합니다. "어느 것이 알림을 보내고 있나요?"와 같은 경우에 유용합니다. 결과 병합은 자체 설계가 필요하므로 현재는 제외되었습니다.

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