MCP Apps × Hono × React のリモートMCPサーバーをAWS LambdaからGoogle Cloud Runに移植してみた

MCP Apps × Hono × React のリモートMCPサーバーをAWS LambdaからGoogle Cloud Runに移植してみた

2026.03.26

こんにちは、リテールアプリ共創部の戸田駿太です。

前回の記事で MCP Apps × Hono × React × AWS Lambda の構成を紹介しましたが、今回はその Google Cloud版 として Cloud Run にデプロイしました。

https://dev.classmethod.jp/articles/mcp-apps-lambda-hono-react-product-list/

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-serverserveでHTTPサーバーを起動するだけです。Cloud RunはPORT環境変数でポートを指定するので、process.env.PORT || 8080を参照します。

https://github.com/ShuntaToda/mcp-apps-react-hono-cloudrun/blob/370ac77aab1b13aac934ed04ef428dc51d22c8e5/packages/server/src/server.ts#L1-L10

HTML埋め込み

Lambda版ではCDKのNodejsFunctionが提供するesbuild設定でloader: { ".html": "text" }を使い、import文でHTMLを埋め込んでいました。

Cloud Run版では自前のbuild.mjsを用意し、esbuildのdefineオプションでHTMLを文字列として注入する方式に変えています。これにより、ローカル開発時(tsxで実行)はfs.readFileSyncでファイルを直接読み、ビルド時はdefineで埋め込むというdev/build切り替えが可能になりました。

https://github.com/ShuntaToda/mcp-apps-react-hono-cloudrun/blob/370ac77aab1b13aac934ed04ef428dc51d22c8e5/packages/server/src/load-html.ts#L1-L23

https://github.com/ShuntaToda/mcp-apps-react-hono-cloudrun/blob/370ac77aab1b13aac934ed04ef428dc51d22c8e5/packages/server/build.mjs#L17-L37

Lambda版でtsx実行時にERR_UNKNOWN_FILE_EXTENSIONエラーが出る問題の解決にもなっています。

インフラ

CDKからTerraformに変更しています。Terraformで管理するリソースは以下の3つです。

  • Artifact Registry: Dockerイメージの保存先
  • Cloud Run: Honoサーバーのホスティング(min 0 / max 3インスタンス)
  • IAM: allUsersroles/run.invokerを付与(未認証アクセス許可)

min_instance_count = 0でリクエストがない時間帯はインスタンスが0になり、Lambdaと同等のscale-to-zeroが実現できます。

https://github.com/ShuntaToda/mcp-apps-react-hono-cloudrun/blob/370ac77aab1b13aac934ed04ef428dc51d22c8e5/packages/infra/main.tf#L38-L89

ビルド&デプロイ

Lambda版はcdk deploy一発でesbuildバンドルからデプロイまで完結しましたが、Cloud Run版はDockerイメージのビルドが必要です。Cloud Buildを使ってGoogle Cloud上でイメージをビルド&pushし、Cloud Runにデプロイします。

https://github.com/ShuntaToda/mcp-apps-react-hono-cloudrun/blob/370ac77aab1b13aac934ed04ef428dc51d22c8e5/Dockerfile#L1-L35

https://github.com/ShuntaToda/mcp-apps-react-hono-cloudrun/blob/370ac77aab1b13aac934ed04ef428dc51d22c8e5/cloudbuild.yaml#L1-L19

# 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)はほぼ変更不要だったということです。変更が必要だったのはエントリポイント(streamHandleserve)、HTML読み込み方式、インフラ定義の3箇所で、MCP Appsのツールやリソースのロジックはそのまま動きました。

Node.js HTTPサーバーがそのまま動くため、Lambdaよりもシンプルに構築できました。特にSSEのためのレスポンスストリーミング設定が不要な点は大きなメリットです。

参考になれば幸いです。

https://github.com/ShuntaToda/mcp-apps-react-hono-cloudrun

参考

この記事をシェアする

FacebookHatena blogX

関連記事