はじめに
Google Cloud Next '26 で、Cloud Run の フルマネージドなリモート MCP サーバ が発表されました(What's new for Cloud Run at Next '26)。ざっくり言うと「AIエージェントに Cloud Run へのデプロイを任せられる」MCP サーバです。
この記事では、その公式 MCP サーバを Claude Code につないで、サンプルアプリを「自然言語」で Cloud Run にデプロイするところまでをハンズオンで確認します。
なお本文中のコードブロックは、ラベルなしが自分で実行するコマンド、実行結果 がその出力、Claude Code への指示 / の応答 が Claude Code とのやりとりです。
対象読者
- Claude Code / MCP をなんとなく触っているインフラ・アプリエンジニア
- 「エージェントにデプロイさせる」がどこまで実用になるか気になっている人
かかる時間とお金
| 項目 | 目安 |
|---|---|
| 所要時間 | 30分程度 |
| 費用 | 実質 ¥0(後述の無料枠内。コンテナイメージ保存分だけ数円レベル、削除で消える) |
Cloud Run には無期限の無料枠があり、月200万リクエスト / 180,000 vCPU秒 / 360,000 GB秒 まで無料です。今回のような hello world を1回デプロイして動作確認する程度なら、コンピューティングはまず無料枠に収まります。
唯一の実費は、ビルドしたコンテナイメージを Artifact Registry に保存する分(数MB〜で月数円レベル)です。最後にお片付けすれば消えます。
必要なもの
- Google Cloud プロジェクト(課金有効。無料枠内でも課金アカウントの紐付けは必要)
-
gcloudCLI(認証済み) - Node.js 18 以上(MCP サーバを
npxで起動するため) - Claude Code
事前に自分の権限を確認しておく
権限が絞られたアカウントで試す場合、手を動かす前に自分の実効権限を見ておくと詰まりません。プロジェクトに対する権限チェックは gcloud のサブコマンドが無いので、Resource Manager の testIamPermissions API を直接叩きます。
export PROJECT_ID="YOUR_PROJECT_ID"
curl -sS -X POST \
-H "Authorization: Bearer $(gcloud auth print-access-token)" \
-H "Content-Type: application/json" \
"https://cloudresourcemanager.googleapis.com/v1/projects/${PROJECT_ID}:testIamPermissions" \
-d '{
"permissions": [
"run.services.create",
"run.services.update",
"run.services.setIamPolicy",
"run.routes.invoke",
"run.services.delete",
"iam.serviceAccounts.actAs",
"cloudbuild.builds.create",
"artifactregistry.repositories.create",
"artifactregistry.repositories.uploadArtifacts",
"serviceusage.services.enable"
]
}'
このAPIは「自分が持っている権限だけ」を返す仕様なので、返ってこなかったものが不足です。グループ経由で継承した権限も含めた実効権限が見えます。
特に iam.serviceAccounts.actAs は見落としがちです。Cloud Run はサービスの実行 ID としてサービスアカウント(デフォルトでは Compute Engine のデフォルト SA)を使うため、デプロイする側にその SA を「使う」権限が必要になります。これが無いと、ドライラン段階で弾かれます。
7 PERMISSION_DENIED: Permission 'iam.serviceAccounts.actAs' denied on
service account PROJECT_NUMBER-compute@developer.gserviceaccount.com
(or it may not exist)
必要なロールは roles/iam.serviceAccountUser です。自分で付与できない場合はプロジェクト管理者に依頼することになるので、着手前に確認しておくと手戻りがありません。
API も3つ有効か見ておきます。
gcloud services list --enabled --project "$PROJECT_ID" \
--format="value(config.name)" \
| grep -E 'run\.googleapis\.com|cloudbuild\.googleapis\.com|artifactregistry\.googleapis\.com'
3行そろえば全部有効です。足りなければ gcloud services enable で有効化します(serviceusage.services.enable 権限が必要)。
⚠️ この記事の検証環境について
筆者は最初、以前から使っている既存プロジェクトで試して行き詰まりました(後述の「つまずき」)。最終的には新規に作ったプロジェクトで完走しています。手軽に試すなら、新規プロジェクトを用意するのが確実です。
Cloud Run MCP とは(先に整理しておく)
ここが一番混同されやすいので先に切り分けます。「Cloud Run と MCP」の話には、別物が2つ あります。
| 何をするもの | 今回の対象 | |
|---|---|---|
| ① Cloud Run にデプロイするための MCP サーバ | エージェントに「これデプロイして」と言うと Cloud Run にアプリが上がる。今回発表されたのはこれ | ✅ |
| ② Cloud Run の上に自作 MCP サーバをホストする | 自分で書いた MCP サーバを Cloud Run にコンテナとして乗せて公開する | ❌(今回は扱わない) |
この記事で扱うのは ① です。②は「MCPサーバの置き場所として Cloud Run を使う」という別テーマで、混ざると一気にわかりにくくなるので分けて考えます。
①の実体は、GitHub で公開されている GoogleCloudPlatform/cloud-run-mcp です。提供しているツールはこのあたり。
| ツール | 役割 |
|---|---|
deploy_local_folder |
ローカルのフォルダをまるごとデプロイ |
deploy_file_contents |
ファイルの中身を直接渡してデプロイ |
deploy_container_image |
既存のコンテナイメージをデプロイ |
list_services |
サービス一覧 |
get_service |
サービス詳細(URLなど) |
get_service_log |
ログ・エラーの取得 |
list_projects |
GCP プロジェクト一覧 |
create_project |
GCP プロジェクトの新規作成 |
計8つです。create_project まで持っているあたりに、このMCPサーバがどこまでやれる想定かが表れています。
加えて deploy / logs というプロンプト(自然言語ショートカット)も同梱されています。
セットアップ
1. gcloud で認証する
MCP サーバはローカルの Google Cloud 認証情報(ADC)を使ってデプロイします。まず2つ認証を通します。
# ユーザー認証
gcloud auth login
# アプリケーションデフォルト認証(ADC)
gcloud auth application-default login
# 対象プロジェクトを設定
gcloud config set project YOUR_PROJECT_ID
2. Claude Code に MCP サーバを登録する
Claude Code には MCP をCLIから登録するコマンドがあります。ターミナルで(Claude Code セッションの中ではなく)実行します。
claude mcp add cloud-run -- npx -y @google-cloud/cloud-run-mcp
Added stdio MCP server cloud-run with command: npx -y @google-cloud/cloud-run-mcp to local config
File modified: /Users/YOUR_NAME/.claude.json [project: /Users/YOUR_NAME/path/to/workdir]
-- の後ろがサーバを起動するコマンドです。
ここで一度つまずいた: スコープの話
上の出力の [project: ...] に注目してください。デフォルトは local スコープで、実行したディレクトリに紐づいて登録されます。
このあとサンプルアプリを取得するために git clone して別ディレクトリへ移動すると、そこは別プロジェクト扱いになり、登録した cloud-run が見えません。
どのディレクトリからでも使いたいので、user スコープで登録し直します。
claude mcp remove cloud-run --scope local
claude mcp add --scope user cloud-run -- npx -y @google-cloud/cloud-run-mcp
Removed MCP server cloud-run from local config
File modified: /Users/YOUR_NAME/.claude.json [project: /Users/YOUR_NAME/path/to/workdir]
Added stdio MCP server cloud-run with command: npx -y @google-cloud/cloud-run-mcp to user config
File modified: /Users/YOUR_NAME/.claude.json
2回目の File modified: に [project: ...] が付いていないのが user スコープの目印です。チームで共有したい場合は --scope project を使うと .mcp.json に書き出され、リポジトリにコミットして配れます。
なお user スコープで登録できていれば、後段でサンプル用のディレクトリに移動したあとでも、セッション内の /mcp に User MCPs として表示されます。
Manage MCP servers
User MCPs (/Users/YOUR_NAME/.claude.json)
cloud-run · ✔ connected · 8 tools
必要なら、デフォルトのプロジェクトやリージョンを環境変数で渡せます。
claude mcp add --scope user cloud-run \
-e GOOGLE_CLOUD_PROJECT=YOUR_PROJECT_ID \
-e GOOGLE_CLOUD_REGION=asia-northeast1 \
-- npx -y @google-cloud/cloud-run-mcp
3. 接続を確認する
claude mcp list
cloud-run: npx -y @google-cloud/cloud-run-mcp - ✔ Connected
✔ Connected になっていれば OK です。
筆者の場合、1回目の実行では ✘ Failed to connect でした。npx が初回にパッケージを取得するため、登録直後は起動が間に合わないことがあります。少し待ってから再実行したら ✔ Connected に変わりました。慌てて設定を疑う前に、まず1回リトライしてみてください。
それでも接続できない場合は、MCP サーバを直接起動すると素のエラーが見えます。
npx -y @google-cloud/cloud-run-mcp
起動して入力待ちで止まるならサーバ自体は正常なので、Claude Code 側の起動タイムアウト(デフォルト30秒)を疑います。
MCP_TIMEOUT=60000 claude
自然言語でデプロイさせる
題材は公式リポジトリ同梱の Node.js サンプルを使います。中身は Express の hello world です。
const express = require('express');
const app = express();
const port = process.env.PORT || 8080;
app.get('/', (req, res) => {
res.send('Hello from Node.js on Cloud Run!');
});
リポジトリを clone して、このフォルダで Claude Code を起動します。
git clone https://github.com/GoogleCloudPlatform/cloud-run-mcp.git
cd cloud-run-mcp/example-sources-to-deploy/nodejs
claude
あとはセッションの中で、日本語でこう頼むだけです。
このフォルダを Cloud Run にデプロイして。サービス名は hello-mcp で。
Claude Code が deploy_local_folder ツールを呼び、初回はツール使用の許可を聞かれます。
Tool use
cloud-run - deploy_local_folder(project: "YOUR_PROJECT_ID", folderPath:
"/Users/YOUR_NAME/path/to/cloud-run-mcp/example-sources-to-deploy/nodejs",
region: "asia-northeast1", service: "hello-mcp") (MCP)
Deploy a local folder to Cloud Run.
Do you want to proceed?
❯ 1. Yes
2. Yes, and don't ask again for cloud-run - deploy_local_folder commands in ...
3. No
引数(デプロイ先プロジェクト・フォルダ・リージョン・サービス名)がすべて明示されているので、実行前に意図どおりか確認できます。
デプロイが走ると、Cloud Build でのビルド → Cloud Run へのデプロイが進み、最後にサービスの URL が返ってきます。筆者の環境では2分半ほどで完了しました。
デプロイ完了です。
サービス名 : hello-mcp
リージョン : asia-northeast1
プロジェクト : YOUR_PROJECT_ID
Service URL : https://hello-mcp-xxxxxxxxxx-an.a.run.app
サンプルには Dockerfile がないため、Cloud Run のソースデプロイ(Buildpacks)でコンテナ化されています。index.js と package.json を置いておくだけでよく、コンテナを自分で用意する必要はありません。
/(スラッシュ)メニューから同梱プロンプトの deploy を選ぶ形でも同じことができます。
ここまで、コンソールもデプロイ用のコマンドも一切使っていません。
動作確認
返ってきた URL をそのまま開きます。
curl -s -o /dev/null -w "%{http_code}\n" https://hello-mcp-xxxxxxxxxx-an.a.run.app
200
認証ヘッダを付けていないのに 200 が返ります。ブラウザで開いても同じで、そのまま表示されます。
コンソールを一度も開かず、チャットで頼んだだけで、公開されたサービスが1つ立ち上がりました。この「公開されている」という点が後半の本題になります。
ログもMCP経由で見る
デプロイしたサービスの状態も、そのまま Claude Code に聞けます。
さっきの hello-mcp サービスのログを見せて。
get_service_log ツールが呼ばれ、直近のログとエラーが返ってきます。こちらも / メニューの logs プロンプトから実行できます。
[06:07:01] INFO GET / → 200 (223 Byte)
[06:04:56] INFO GET / → 200 (223 Byte)
[06:03:57] INFO Services.CreateService: Ready condition → True (Service hello-mcp)
[06:03:55] INFO Ready condition → True (Revision hello-mcp-xxxxxxxxxxxxx)
[06:03:55] DEFAULT Nodejs sample app listening at http://localhost:8080
[06:03:52] INFO Starting new instance. Reason: DEPLOYMENT_ROLLOUT
[06:03:46] NOTICE Services.CreateService: YOUR_ACCOUNT@example.com
[06:02:38] ERROR Services.CreateService: Runtime nodejs26 is not supported
[06:02:37] ERROR Services.CreateService: Runtime nodejs26 is not supported
デプロイ → 動作確認 → ログ確認まで、ツールを切り替えずに一つの会話で完結するのが体験としての強みです。
そして、このログを見て初めて気づいたことがあります。冒頭に ERROR が2件あります。
Runtime nodejs26 is not supported — Buildpacks が Node.js 26 を選んで2回失敗し、その後サポート対象のランタイムにフォールバックして成功していました。最終的にサービスは正常に動いているものの、ここで1分20秒ほど費やしています。
しかし、エージェントからの完了報告は「デプロイ完了です」と URL だけでした。途中で2回失敗したことは報告に出てきません。 MCP ツールの戻り値としては成功なので、そこまでは見えないわけです。
「頼むだけでデプロイできる」体験の裏で何が起きていたかは、結局ログを見ないと分かりません。便利なインターフェースほど、確認手段を別に持っておく必要があるということだと思います。その意味で、get_service_log が同じ MCP サーバに入っているのは理にかなっています。
つまずき: list_projects が空で返る(VPN と gRPC)
ここで一度詰まったので、切り分けごと残しておきます。
症状: デプロイを依頼すると、エージェントが「プロジェクト一覧が空で返ってきました」と言い出す。認証を疑って gcloud auth list や ADC を確認しても、すべて正常。gcloud projects list ではプロジェクトがちゃんと見える。
実装を見ると理由が分かります。 lib/cloud-api/projects.js の listProjects はこうなっています。
try {
const [projects] = await client.searchProjects();
return projects.map((project) => ({ id: project.projectId }));
} catch (error) {
console.error('Error listing GCP projects:', error);
return []; // ← エラーを握り潰して空配列を返す
}
「一覧が空」はプロジェクトが無いという意味ではなく、API 呼び出しが失敗した結果でした。エラーが console.error に流れるだけなので、MCP クライアント側には「空」としか見えず、認証問題にミスリードされます。
そこで、握り潰される前のエラーを直接見ます。
node -e "
import('@google-cloud/resource-manager').then(async (m) => {
try { const r = await new m.ProjectsClient().searchProjects(); console.log('OK', r[0].length); }
catch (e) { console.error('ERR', e.message); }
});
"
ERR 14 UNAVAILABLE: No connection established. Last error: Protocol error
gRPC が通っていませんでした。 一方、同じ API を REST で叩くと通ります。
curl -sS -H "Authorization: Bearer $(gcloud auth application-default print-access-token)" \
"https://cloudresourcemanager.googleapis.com/v1/projects" | head -5
この非対称が厄介で、「認証は通るのに一覧だけ空」という紛らわしい状態を作ります。
原因は VPN クライアントが HTTP/2 = gRPC を通していなかったことでした。VPN を切断したら、同じコマンドが OK を返すようになりました。
cloud-run-mcp は Cloud Run や Service Usage の操作に gRPC ベースのクライアントライブラリを使っています(lib/clients.js のコメントにも明記されています)。つまり list_projects に限らず、デプロイ本体も同じ経路です。gRPC が塞がれた環境では、この MCP サーバはほぼ機能しません。
まとめると:
| 結果 | |
|---|---|
| REST(curl / fetch) | 通る |
| gRPC(クライアントライブラリ) | 14 UNAVAILABLE |
| 表面上の症状 | 「プロジェクト一覧が空」 |
VPN 必須の環境で使いたい場合は、この MCP サーバではなく gcloud run deploy に素直にフォールバックするのが現実的です。
余談: この MCP、SKILL.md も同梱している
細かい話ですが、cloud-run-mcp リポジトリには MCP サーバの実装だけでなく、skills/cloud-run/SKILL.md という Skill も同梱されています。中身は gcloud run の使い方をまとめたものです。
MCP(=ツールとして機能を渡す)と Skill(=手順の知識を渡す)が、同じリポジトリで二層として提供されている構図になっています。「エージェントに何かをやらせる」ときの標準化が、ツール側と知識側の両輪で進んでいるのが見て取れて、個人的には面白いポイントでした。
公開されたのに、IAMポリシーには何も残らない
ここまでで「便利」は十分伝わったと思います。最後に、業務で使う前に知っておくべき挙動をひとつ。
cloud-run-mcp には SKIP_IAM_CHECK という環境変数があります。実装はこうです。
const skipIamCheck = process.env.SKIP_IAM_CHECK !== 'false';
!== 'false' なので、何も設定しなければ true です。そしてこのフラグが立っていると、デプロイ時にサービスへ次の設定が入ります。
if (skipIamCheck) {
service.invokerIamDisabled = true;
}
つまりデフォルトでデプロイすると、そのサービスは誰でもアクセスできる状態になります。実際、デプロイ直後に認証ヘッダなしで叩くと通ります。
curl -s -o /dev/null -w "%{http_code}\n" https://hello-mcp-xxxxxxxxxx-an.a.run.app
200
ここまでは「まあ、そういうオプションだよね」で済む話です。問題はここからです。
公開状態を確認しようとして IAM ポリシーを見ると、こうなります。
gcloud run services get-iam-policy hello-mcp --region asia-northeast1
etag: ACAB
空です。 allUsers へのバインディングは存在しません。プロジェクトレベルを見ても同じです。
gcloud projects get-iam-policy "$PROJECT_ID" \
--flatten="bindings[].members" \
--filter="bindings.members:allUsers OR bindings.members:allAuthenticatedUsers" \
--format="table(bindings.role,bindings.members)"
(何も返らない)
からくりは invokerIamDisabled です。これは「このサービスでは invoker の IAM チェックをそもそも行わない」というサービス側の設定で、IAM ポリシーを一切変更せずに認証を無効化します。Cloud Run Admin API v2 を直接見ると確認できます。
curl -sS -H "Authorization: Bearer $(gcloud auth print-access-token)" \
"https://run.googleapis.com/v2/projects/${PROJECT_ID}/locations/asia-northeast1/services/hello-mcp" \
| python3 -m json.tool | grep -iE "invoker|ingress"
"ingress": "INGRESS_TRAFFIC_ALL",
"invokerIamDisabled": true,
整理するとこうなります。
| 確認項目 | 結果 |
|---|---|
| 無認証での HTTP アクセス | 200(誰でもアクセス可) |
| サービスの IAM ポリシー | 空 |
プロジェクトの IAM ポリシー(allUsers) |
なし |
サービス設定 invokerIamDisabled
|
true |
| コンソールのセキュリティタブ | 「パブリック アクセスを許可」 |
コンソールで見れば分かります。サービス詳細の「セキュリティ」タブには「パブリック アクセスを許可」が選択されており、説明にも認証チェックが実行されない旨が書かれています。
問題は、IAM ポリシーを見る方法では分からないことです。
これは運用上けっこう重い話だと思っています。「allUsers バインディングを検索して公開リソースを洗い出す」という棚卸しは、多くの現場でやられているはずです。しかしこの方法では、invokerIamDisabled で公開されたサービスは1件も引っかかりません。gcloud run services get-iam-policy も空を返します。
コンソールを1つずつ開けば気づけますが、サービスが数十個あるプロジェクトで全部目視する運用は現実的ではありません。スクリプトで棚卸しするほど見落とす、という性質の穴です。
しかも既定値が true なので、何も指定しなければこの状態になります。エージェントに「デプロイして」と頼むだけで、IAM ポリシーには映らない公開サービスが増えていく、という構図です。
対策としては、まず MCP サーバの登録時に明示的に無効化しておくのが確実です。
claude mcp add --scope user cloud-run \
-e SKIP_IAM_CHECK=false \
-- npx -y @google-cloud/cloud-run-mcp
そのうえで、公開リソースの棚卸しをするなら、IAM ポリシーだけでなくサービス設定の invokerIamDisabled も併せて見る必要があります。
# ざっくり全サービスをチェックする例
for s in $(gcloud run services list --region asia-northeast1 --format="value(metadata.name)"); do
flag=$(curl -sS -H "Authorization: Bearer $(gcloud auth print-access-token)" \
"https://run.googleapis.com/v2/projects/${PROJECT_ID}/locations/asia-northeast1/services/${s}" \
| python3 -c "import json,sys; print(json.load(sys.stdin).get('invokerIamDisabled', False))")
echo "${s}: invokerIamDisabled=${flag}"
done
便利さを否定したいわけではありません。ただ、手元で試すときの既定値と、業務で使うときの既定値は違っていいはずです。エージェントにデプロイを任せるなら、この1つの環境変数は最初に決めておくべきだと思います。
お片付け
検証が終わったら消しておきます。
# Cloud Run サービスを削除
gcloud run services delete hello-mcp --region asia-northeast1
# (不要なら)MCP サーバの登録も解除
claude mcp remove cloud-run
Artifact Registry に残ったビルドイメージが気になる場合は、対応するリポジトリ(cloud-run-source-deploy など)も削除しておくと、保存分の課金も消えます。
まとめ
- Cloud Run の公式 MCP サーバを Claude Code につなぎ、自然言語でデプロイ〜ログ確認まで到達できた
- セットアップは
gcloud認証 →claude mcp add --scope user cloud-run -- npx -y @google-cloud/cloud-run-mcp→claude mcp listで確認、の3ステップ -
SKIP_IAM_CHECKは既定でtrue。デプロイしたサービスは誰でもアクセスできる状態になる - その公開は
invokerIamDisabledによるもので、IAM ポリシーには何も残らない。コンソールでは確認できるが、allUsersを検索するスクリプト的な棚卸しでは検出できない - 業務で使うなら
-e SKIP_IAM_CHECK=falseを明示しておく - 既存プロジェクトでは VPN による gRPC 遮断で行き詰まった。試すなら新規プロジェクトが確実


