Porting a remote MCP server with MCP Apps × Hono × React from AWS Lambda to Google Cloud Run

Porting a remote MCP server with MCP Apps × Hono × React from AWS Lambda to Google Cloud Run

2026.03.26

This page has been translated by machine translation. View original

Hello, I'm Shunta Toda from the Retail App Co-creation Department.

In my previous article, I introduced the configuration of MCP Apps × Hono × React × AWS Lambda. This time, I deployed it to Cloud Run as a Google Cloud version.

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

For those who regularly use Google Cloud, Cloud Run might be easier to implement than Lambda + Function URL. Additionally, I wanted to verify how platform-independent the MCP server written with Hono could be.

In this article, I'll focus on the differences from the Lambda version. Please refer to the previous article for details on the MCP Apps mechanism and React UI implementation.

Differences from Lambda Version

Item Lambda Version Cloud Run Version
Runtime AWS Lambda (Function URL) Cloud Run (Docker)
Streaming Lambda Response Streaming Node.js HTTP (@hono/node-server)
IaC AWS CDK Terraform
Build & Deploy CDK esbuild (cdk deploy in one go) Cloud Build + gcloud run deploy
Entry point streamHandle(app) serve({ fetch: app.fetch, port })
HTML embedding CDK esbuild's loader Custom build.mjs with esbuild's define

Streaming

This is the biggest difference. In the Lambda version, Lambda Response Streaming configuration (invokeMode: RESPONSE_STREAM) was required to use SSE. With Cloud Run, you can run a standard Node.js HTTP server on a Docker container, so SSE works without any special configuration.

Entry Point

The Lambda version exported a Lambda-specific handler with streamHandle(app), but the Cloud Run version simply starts an HTTP server with @hono/node-server's serve. Cloud Run specifies the port with the PORT environment variable, so we reference process.env.PORT || 8080.

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

HTML Embedding

In the Lambda version, we used loader: { ".html": "text" } in the CDK's NodejsFunction esbuild configuration to embed HTML via import statements.

In the Cloud Run version, we prepared our own build.mjs and changed to injecting HTML as a string using esbuild's define option. This enables dev/build switching: during local development (running with tsx), files are read directly with fs.readFileSync, and during build time, they're embedded with define.

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

This also solves the ERR_UNKNOWN_FILE_EXTENSION error that occurred when running tsx in the Lambda version.

Infrastructure

We've changed from CDK to Terraform. The resources managed by Terraform are the following three:

  • Artifact Registry: Storage for Docker images
  • Cloud Run: Hosting for the Hono server (min 0 / max 3 instances)
  • IAM: Assigning roles/run.invoker to allUsers (allowing unauthenticated access)

With min_instance_count = 0, instances scale to zero when there are no requests, achieving scale-to-zero equivalent to Lambda.

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

Build & Deploy

While the Lambda version completed everything from esbuild bundling to deployment with a single cdk deploy, the Cloud Run version requires Docker image building. We use Cloud Build to build and push images on Google Cloud, then deploy to 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

# Build & push image with Cloud Build
gcloud builds submit \
  --config=cloudbuild.yaml \
  --substitutions="_SERVICE_URL=https://<your-service>.asia-northeast1.run.app" \
  --region=asia-northeast1

# Deploy to Cloud Run
gcloud run services update shop-mcp \
  --region=asia-northeast1 \
  --image=asia-northeast1-docker.pkg.dev/<PROJECT_ID>/shop-mcp/shop-mcp:latest

Similar to Lambda version's Function URL, the Cloud Run URL is undetermined on initial deployment, requiring a two-stage deployment. MCP Apps' sandbox iframe restricts access to external domains with CSP (Content Security Policy), so the Cloud Run URL must be specified in connectDomains to allow fetch requests from React UI to Hono API. This URL needs to be embedded in HTML at Vite build time and in server code at esbuild build time, necessitating rebuild and redeployment after the URL is confirmed.

Connecting from Claude Desktop

Add the following to claude_desktop_config.json:

{
  "mcpServers": {
    "shop-mcp": {
      "command": "npx",
      "args": ["mcp-remote", "https://<your-service>.asia-northeast1.run.app/mcp"]
    }
  }
}

Summary

I ported the Lambda version of the MCP Apps server to Cloud Run.

After actually porting it, I felt that thanks to Hono's abstraction, the server core (index.ts) required almost no changes. Changes were needed only in three places: the entry point (streamHandleserve), HTML loading method, and infrastructure definition. The MCP Apps tools and resource logic worked without modification.

Since Node.js HTTP servers run as-is, the setup was simpler than Lambda. A major benefit is that response streaming configuration for SSE is not required.

I hope this is helpful.

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

References

Share this article

FacebookHatena blogX