Skip to main content
Glama
robert-sinclair

grafana-unified-mcp

grafana-unified-mcp

1つのMCPサーバーで複数のGrafanaインスタンスを統合。標準のGrafana MCPサーバーの全ツールに加え、どのGrafanaに対して実行するかを指定する追加の引数instanceを提供します。

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より:

非推奨: X-Grafana-URLはGrafanaクライアントを設定しなくなりました。このミドルウェアは、無効なヘッダー処理を維持するために一時的に残されています。

つまり、1つのmcp-grafanaプロセスは1つのGrafanaとしか通信できません。10個のGrafanaがあれば、10個のサーバー、クライアント設定ごとに10個のエントリ、そしてモデルが区別するための同一名のツールセットが10個必要になります。

このサーバーは、インスタンスごとに1つの上流子プロセスを実行し、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は、そのインスタンスのみに影響が及びます。

インストール

2つの要素が必要です:上流のバイナリと、このパッケージです。

# 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を使用してダイジェストで比較され、すべてのクライアントが毎回チェックされるため、タイミング攻撃によってマッチ位置が漏洩することはありません。

呼び出し元ごとに2つの項目が強制されます:

  • instances — 呼び出し元が認識するinstance列挙型はその権限に限定され、範囲外のインスタンスを指定した呼び出しは存在しないインスタンスと同じメッセージで拒否されるため、トークンは到達可能なインスタンスを列挙できません。

  • scope — read-onlyの呼び出し元は変更を伴うツールを認識すらしません。この分割は、ここで管理されるリストではなく、上流自身のreadOnlyHintアノテーション(現在65ツール中49が読み取り専用)に基づいているため、上流で追加されたツールはコード変更なしで分類されます。アノテーションがないものは読み取り専用ではないものとして扱われます。

念には念を入れて、--child-arg=--disable-writeを追加すると、すべての呼び出し元に対してソースレベルで書き込みツールを削除します。

認証なしでの実行

--auth-mode noneは、ポートに到達可能なすべての呼び出し元に読み取り専用でサービスを提供します。インスタンスをスコープするための識別情報がないため、設定されたすべてのインスタンスは読み取り可能ですが、何も書き込みはできません。なぜなら、オープンポートがダッシュボードを書き換えたりスナップショットを削除したりできるべきではないからです。これは3つのレイヤーで強制されます:

  1. 公開されるカタログはすべての変更ツールを省略します;

  2. 認証チェックは、クライアントが直接ツールを指定した場合でも拒否します;

  3. 子プロセスは--disable-writeで起動されるため、上流も拒否します。

3番目のレイヤーが、単なるフィルター以上のものにしています。上流は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 パラメータストア

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リバindプロテクションを適用します。公開ホスト名を転送するプロキシの背後では、そのホストが許可されていなければ、すべてのリクエストが拒否されます。--public-urlはこれを許可します(RFC 9728リソースメタデータにも使用されます)。--allowed-hostでさらにホストを追加できます。

GET /healthzは、Grafanaにアクセスすることなく、プロセスヘルス、稼働中の子プロセス、カタログ状態を報告します。

クライアントの接続

.mcp.json、ローカルstdio使用の場合:

{
  "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を設定します — Web上の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

統合テストは、到達不能なGrafanaに対して実際のmcp-grafana子プロセスを駆動します:カタログ検出、instanceの注入と除去、ルーティング、認証フィルタリングを、ライブの認証情報なしで証明するのに十分です。MCP_GRAFANA_BINARYをバイナリのパスに設定するか、設定しない場合はテストはスキップされます。

ロードマップ

  • OAuth 2.1 — 認証レイヤーはすでにインターフェースであり、SDKはトークン検証器とともにOAuthプロバイダーをすでに受け入れています。OAuth2Provider.verify_tokenを実装するだけです。auth/oauth.pyに3つのステップが文書化されています。IdPグループを既存のgrafana:read / grafana:write / instance:<name>スコープにマッピングすれば、すべての認証チェックは変更なしで機能し続けます。

  • ファンアウト — instance: "*"を使用して、1つの読み取り専用クエリをすべてのインスタンスに実行し、結果をマージします。「どのインスタンスがアラートを発しているか?」といった用途に便利です。結果のマージには独自の設計が必要なため、現時点では除外されています。

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