
MCP Apps × Hono × React のリモートMCPサーバーをAWS LambdaからGoogle Cloud Runに移植してみた
こんにちは、リテールアプリ共創部の戸田駿太です。
前回の記事で MCP Apps × Hono × React × AWS Lambda の構成を紹介しましたが、今回はその Google Cloud版 として Cloud Run にデプロイしました。
Google Cloudを普段使っている方にとっては、Lambda + Function URLよりCloud Runのほうが導入しやすいケースもあると思います。また、Honoで書いたMCPサーバーがどこまでプラットフォーム非依存なのかを検証する目的もありました。
この記事ではLambda版との違いに絞って解説します。MCP Appsの仕組みやReact UIの実装については前回の記事を参照してください。
Lambda版との違い
| 項目 | Lambda版 | Cloud Run版 |
|---|---|---|
| ランタイム | AWS Lambda (Function URL) | Cloud Run (Docker) |
| ストリーミング | Lambda Response Streaming | Node.js HTTP(@hono/node-server) |
| IaC | AWS CDK | Terraform |
| ビルド&デプロイ | CDK esbuild(cdk deploy一発) |
Cloud Build + gcloud run deploy |
| エントリポイント | streamHandle(app) |
serve({ fetch: app.fetch, port }) |
| HTML埋め込み | CDK esbuildのloader |
自前のbuild.mjsでesbuildのdefine |
ストリーミング
最も大きな違いです。Lambda版ではSSEを使うためにLambda Response Streamingの設定(invokeMode: RESPONSE_STREAM)が必須でした。Cloud RunはDockerコンテナ上で通常のNode.js HTTPサーバーを動かせるため、特別な設定なしでSSEが使えます。
エントリポイント
Lambda版はstreamHandle(app)でLambda固有のハンドラーをエクスポートしていましたが、Cloud Run版は@hono/node-serverのserveでHTTPサーバーを起動するだけです。Cloud RunはPORT環境変数でポートを指定するので、process.env.PORT || 8080を参照します。
HTML埋め込み
Lambda版ではCDKのNodejsFunctionが提供するesbuild設定でloader: { ".html": "text" }を使い、import文でHTMLを埋め込んでいました。
Cloud Run版では自前のbuild.mjsを用意し、esbuildのdefineオプションでHTMLを文字列として注入する方式に変えています。これにより、ローカル開発時(tsxで実行)はfs.readFileSyncでファイルを直接読み、ビルド時はdefineで埋め込むというdev/build切り替えが可能になりました。
Lambda版でtsx実行時にERR_UNKNOWN_FILE_EXTENSIONエラーが出る問題の解決にもなっています。
インフラ
CDKからTerraformに変更しています。Terraformで管理するリソースは以下の3つです。
- Artifact Registry: Dockerイメージの保存先
- Cloud Run: Honoサーバーのホスティング(min 0 / max 3インスタンス)
- IAM:
allUsersにroles/run.invokerを付与(未認証アクセス許可)
min_instance_count = 0でリクエストがない時間帯はインスタンスが0になり、Lambdaと同等のscale-to-zeroが実現できます。
ビルド&デプロイ
Lambda版はcdk deploy一発でesbuildバンドルからデプロイまで完結しましたが、Cloud Run版はDockerイメージのビルドが必要です。Cloud Buildを使ってGoogle Cloud上でイメージをビルド&pushし、Cloud Runにデプロイします。
# Cloud Buildでイメージビルド&push
gcloud builds submit \
--config=cloudbuild.yaml \
--substitutions="_SERVICE_URL=https://<your-service>.asia-northeast1.run.app" \
--region=asia-northeast1
# Cloud Runにデプロイ
gcloud run services update shop-mcp \
--region=asia-northeast1 \
--image=asia-northeast1-docker.pkg.dev/<PROJECT_ID>/shop-mcp/shop-mcp:latest
Lambda版のFunction URLと同様に、初回デプロイではCloud RunのURLが未確定のため2段階のデプロイが必要です。MCP AppsのサンドボックスiframeはCSP(Content Security Policy)で外部ドメインへのアクセスを制限しており、React UIからHono APIへのfetchを許可するにはconnectDomainsにCloud RunのURLを指定する必要があります。このURLはViteビルド時にHTMLへ、esbuildビルド時にサーバーコードへ埋め込まれるため、URL確定後に再ビルド・再デプロイが必要になります。
Claude Desktopからの接続
claude_desktop_config.jsonに以下を追加します。
{
"mcpServers": {
"shop-mcp": {
"command": "npx",
"args": ["mcp-remote", "https://<your-service>.asia-northeast1.run.app/mcp"]
}
}
}
まとめ
Lambda版のMCP AppsサーバーをCloud Runに移植しました。
実際に移植してみて感じたのは、Honoの抽象化のおかげでサーバーコア(index.ts)はほぼ変更不要だったということです。変更が必要だったのはエントリポイント(streamHandle → serve)、HTML読み込み方式、インフラ定義の3箇所で、MCP Appsのツールやリソースのロジックはそのまま動きました。
Node.js HTTPサーバーがそのまま動くため、Lambdaよりもシンプルに構築できました。特にSSEのためのレスポンスストリーミング設定が不要な点は大きなメリットです。
参考になれば幸いです。








