
Porting a remote MCP server with MCP Apps × Hono × React from AWS Lambda to Google Cloud Run
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.
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.
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.
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.invokertoallUsers(allowing unauthenticated access)
With min_instance_count = 0, instances scale to zero when there are no requests, achieving scale-to-zero equivalent to Lambda.
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.
# 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 (streamHandle → serve), 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.