こんにちは。Kiban ワーキンググループ、クラウドチーム所属のPeacock (id:peacock0803sz)です。
TL;DR
roles/viewer ベースで機密情報を取得する権限を削ったIAM Roleを作成して googleapis/gcloud-mcp をサービスアカウントにimpersonationして実行することで安全に実装しました。
何を作ったのか
今回追加したのはGoogle Cloud向けのread-only MCPサーバーです。
- ローカル版: Claude Code, Codexから使うstdio MCP
- リモート版: Cloud Run上で動かし、Claude.aiのCustom Connectorから使うMCP
- こちらをClaude CodeやCodexからも利用することも可能
バックエンドは自作せずGoogleが公開している googleapis/gcloud-mcp をラップする構成にしました。
狙いは単純で、エージェントからGoogle Cloudを自然言語で参照できるようにしつつ、書き込み操作はIAMレベルで不可能にすることです。
背景
社内でCoding Agentを使う場面が増え、GitHubやGoogle Sheets、esaはMCP経由で触れるようになってきました。
ただ、日常的に見たい情報の多くはGoogle Cloud上にあります。
- 組織配下のプロジェクト一覧
- Cloud Runのサービス状態
- GKEクラスタやCloud SQLのメタデータ
- BigQueryのdatasetやtable schema
- IAMやResource Managerの構成
こうした参照系の操作をエージェント経由で扱えれば、調査やトラブルシュートの入口はかなり速くなります。
とはいえ、単純にgcloud CLIを叩けるMCPを生やせば済む話ではありませんでした。
Google Cloudは当然ながら権限のかかる操作が多く、read-onlyをきちんと設計しないと、便利さより先に危険さが立ってしまいます。
このread-only (読み取り専用)権限をしっかり設計しないと、AIが間違って削除や編集などの破壊的な操作を実行できてしまう可能性があるからです。
採用した構成
今回の構成を一言で言うと「gcloud-mcpをread-onlyなサービスアカウントとして動かす」です。
ローカル版
ローカル版は @google-cloud/gcloud-mcp をnpxで起動しつつ、MCPサブプロセスの環境変数としてimpersonation先のサービスアカウント(以下、SAと略記)を渡しています。
{ "mcpServers": { "gcp-readonly": { "command": "npx", "args": ["-y", "@google-cloud/gcloud-mcp"], "env": { "CLOUDSDK_AUTH_IMPERSONATE_SERVICE_ACCOUNT": "readonly@example.iam.gserviceaccount.com" } } } }
ここで大事なのは gcloud config set auth/impersonate_service_account ... のような永続設定の書き換えをしていないことです。
設定はMCPサブプロセスの env に閉じているので、普段ターミナルで使っているgcloud CLIの設定には影響しません。
この CLOUDSDK_AUTH_IMPERSONATE_SERVICE_ACCOUNT 環境変数は特殊な裏ワザではなく、gcloud CLIの設定プロパティ auth/impersonate_service_account に対応する環境変数です。
Google Cloudは各プロパティが CLOUDSDK_SECTION_PROPERTY 形式の環境変数に対応すると説明しており、 auth/impersonate_service_account 自体も公式の設定プロパティとして定義されています。
リモート版
リモート版はCloud Run上に載せています。
auth-proxyが gcloud-mcp をstdioの子プロセスとして起動し、Claude.aiなどのクライアントからHTTP経由で使えるようにしています。
Cloud Run版ではimpersonationを使わず、ランタイムのサービスアカウントそのものをread-only用の専用SAにしました。
これならApplication Default Credentialsが自動でその権限のトークンを返すので、コンテナ内で追加の認証処理は不要です。
コンテナイメージには google-cloud-cli と @google-cloud/gcloud-mcp をビルド時に入れています。
npx の毎回フェッチに依存させず、Cloud Runのcold startを少しでも軽くするためです。
Viewer / Readerはread-onlyではない
今回いちばん重要だったのはここでした。
gcloud-mcp 自体には --readonly のようなフラグがありません。
つまり、read-onlyを保証したければ、MCPの上ではなくIAM側で保証するしかありません。
最初は roles/viewer や roles/reader を使えばよさそうに見えます。
ただ、実際は「破壊しない」だけで、「副作用がない」とは言えません。
たとえばBigQueryでは bigquery.jobs.create が含まれており、クエリを実行できます。
これは課金も発生しうるので、今回求めていた厳密なread-onlyには合いません。
そこで、組織スコープのカスタムIAMロールを作り、
*.get*.list*.getIamPolicy
のような読み取り系権限を中心にホワイトリストで定義しました。
逆に、次のようなものは除外しています。
bigquery.jobs.createsecretmanager.versions.accessstorage.objects.get*.createや*.update、*.delete、*.patch、*.use
特に storage.objects.get は、レビューで「これはメタデータではなくオブジェクト本体も読める」という指摘を受けて削りました。
この修正は、read-onlyという言葉をどこまで厳密に捉えるかを考えるうえで象徴的でした。
単に「変更しない」だけならViewer, Readerでも十分な場面はあります。
でも今回ほしかったのは、副作用がなく、過剰なデータアクセスも避けた、調査用の読み取り権限です。そこはカスタムロールを作るのが正解でした。
監査ログも設計し直した
リモート版では、Cloud RunのランタイムSAを共通で使う構成にしています。
この形はread-onlyの共通ゲートウェイとしては扱いやすい一方、Google Cloud側の監査ログはサービスアカウント単位で残ります。
そのままだと、
- 誰が
- どのMCPツールを
- どんな引数で呼んだのか
が分かりにくくなります。
そこでauth-proxy側に構造化ログを追加し、caller emailとtool callをCloud Runログに出すようにしました。
GCP側の監査ログだけでは埋まらない部分を、アプリケーションログで補う設計です。
ここもレビューで論点になりました。
引数をそのままログに出すと、将来的に機密情報が混ざる危険があります。今回はGoogle Cloud参照専用で、そもそもsecret本体やobject本体に触れにくいロール設計にしていたため、そのまま残していますが、別のバックエンドを追加するときには見直しが必要だと思っています。
どう使えるようになったか
このMCPを追加したことで、たとえば次のような依頼がエージェント経由でできるようになります。
- 組織配下のプロジェクト一覧を見せて
- 特定プロジェクトのCloud Run一覧を見せて
- GKEクラスタの一覧を見せて

逆に、次のような依頼は権限で弾かれます。
- 新しいプロジェクトを作って
- BigQueryで
SELECT 1を実行して
後者が失敗するのは一見不便ですが、今回の目的からすると正しい挙動です。
調査用の閲覧とデータプレーンの実行は分けて考えるべきで、必要なら将来的に別のMCPを設計した方が安全だと思っているためです。
おわりに
今回の実装は、MCPを1つ追加したというよりも「エージェント経由で安全にGoogle Cloudを読むための境界を設計した」という方が実態に近いです。
今後BigQueryのクエリ実行やGCSオブジェクト内容の参照のような、よりデータプレーン寄りの操作をエージェントに渡したくなったら、それはread-only MCPの拡張ではなく、別の権限設計と監査設計を持った別サーバーとして切り出す予定です。