Deploy to MCPize Cloud Run, managed hosting in 5 minutes
MCPize is the easiest path from npm package to hosted MCP server. Container builds via Buildpacks, Cloud Run runs it, you get an HTTPS URL. The PORT trigger gotcha + Prisma custom-output trap.
Deploy to MCPize Cloud Run, managed hosting in 5 minutes
MCPize is StudioMeyer's managed hosting service for MCP servers. Buildpack-based container build + Cloud Run runtime + HTTPS URL + a directory listing. If your server already does dual-transport (Phase 7.1), the deploy is one command. This recipe is the path plus the three traps that have caused every "container failed to start" bug we've shipped.
Schritt 1: Prerequisites
Before mcpize deploy:
- Server has dual-transport (
isHttpMode()triggers onPORTenv, see 7.1). package.jsonhas"main": "dist/server.js"and"bin": { "your-server": "./dist/server.js" }.npm run buildworks locally and producesdist/server.js.dist/server.jsstarts and binds toprocess.env.PORTif set.
Smoke test locally:
PORT=8080 HOST=0.0.0.0 node dist/server.js &
curl http://localhost:8080/health
# → {"status":"ok","server":"...","version":"..."}
kill %1
If this fails locally, MCPize will fail too, and remote debugging is harder.
Schritt 2: Install MCPize CLI + initialize
npm install -g mcpize
mcpize login
mcpize init
mcpize init adds an mcpize.json to your repo:
{
"name": "your-mcp-server",
"description": "What it does in one line.",
"version": "1.0.0",
"runtime": "node22",
"buildCommand": "npm ci && npm run build",
"startCommand": "node dist/server.js",
"envVars": ["DATABASE_URL", "STRIPE_SECRET_KEY"]
}
envVars is the list of names, not values. Values go in the MCPize dashboard or mcpize secrets set NAME value.
Schritt 3: First deploy
mcpize deploy
What happens:
- MCPize uploads your repo (excluding
node_modules,.git,.env). - Buildpacks builds a container image: Node 22 base +
npm ci+npm run build. - Container pushed to MCPize's registry.
- Cloud Run starts a revision with the image.
- Cloud Run sets
PORT=8080and waits for the container to bind to it. - If
/healthreturns 200 within 60 seconds, the revision goes live and you get an HTTPS URL. - If not, the revision is rolled back and you get logs.
Successful deploy:
✓ Build complete (Image: gcr.io/mcpize/your-mcp-server:abc123)
✓ Cloud Run revision deployed (00001)
✓ TCP probe succeeded (1 attempt)
✓ Server live at https://your-mcp-server.mcpize.com
🌐 https://your-mcp-server.mcpize.com/mcp
Schritt 4: Trap #1, `PORT` trigger
The single most common "container failed to start" failure mode:
// BAD: server only checks --http arg, ignores PORT env
if (process.argv.includes('--http')) {
startHttpServer(8080);
} else {
startStdioServer();
}
Cloud Run sets PORT=8080 but doesn't pass --http. Server starts in stdio mode, listens on nothing, Cloud Run waits 60s for the TCP probe, kills the container.
Fix is in dual-transport.ts from 7.1, isHttpMode() includes !!process.env.PORT:
function isHttpMode(): boolean {
return process.argv.includes('--http')
|| process.env.MCP_HTTP === '1'
|| !!process.env.PORT; // ← this line is the fix
}
If you don't have dual-transport, the easiest hack is to add --http to startCommand in mcpize.json:
"startCommand": "node dist/server.js --http"
But fixing isHttpMode() is the right answer.
Schritt 5: Trap #2. Prisma custom-output
If your server uses Prisma with a custom output path (output = "./generated/prisma"), Buildpacks doesn't include the generated client in the image. Symptom: container starts, throws Cannot find module '@prisma/client/generated' on first DB call.
Two fixes:
- Move Prisma to default output (
generator client { ... }withoutoutput). Generated client lives innode_modules/@prisma/client, gets included automatically. - Or run
prisma generatein the build step:
"buildCommand": "npm ci && npx prisma generate && npm run build"
Default output is simpler. Use it.
Schritt 6: Trap #3, printf vs echo newline
Some MCPize buildpacks pipe your startCommand through a wrapper. If your start command uses echo "..." or any shell command that adds a trailing newline, the wrapper sometimes interprets the newline as end-of-command. Symptom: container starts, exits 0, Cloud Run rolls back.
Fix: use printf (no trailing newline) or just call the binary directly:
// BAD
"startCommand": "echo 'starting' && node dist/server.js"
// GOOD
"startCommand": "node dist/server.js"
Keep startCommand minimal. Logging belongs in your app, not the start command.
Schritt 7: Set secrets
mcpize secrets set DATABASE_URL postgresql://...
mcpize secrets set STRIPE_SECRET_KEY sk_live_...
mcpize secrets set OAUTH_PEPPER "$(openssl rand -base64 32)"
Each secret is per-server, not per-account. If you run multiple MCP servers in the same MCPize account, each one needs its own secrets.
Step 7b: Cold-start mitigation
Cloud Run scales to zero by default. The first request after idle pays a 2-5 second cold-start while the container boots, long enough that an MCP client may time out the initial tools/list call. Two mitigations:
# Option A: Keep one instance warm (~$15/month for a small server)
mcpize config set min-instances 1
# Option B: External pinger every 5 minutes (free, slightly hacky)
# Set up a Cron-Job.org or Uptime Kuma monitor that hits /health every 5 min.
# Cheap, but the first real request after a real outage still cold-starts.
We default to min-instances=1 for any MCP server that handles real traffic. The $15/mo is cheap insurance against the first-request timeout that would otherwise lose users on every fresh session.
ls -la mcpize.json 2>/dev/null || (cat package.json 2>/dev/null | grep -i mcpize | head -3) || echo "no mcpize config"
Schritt 8: Deploy iterations
Subsequent deploys are the same one command:
mcpize deploy
Each deploy creates a new Cloud Run revision (00002, 00003, ...). Old revisions stay around for instant rollback:
mcpize rollback 00002
Schritt 9: Verify
Run academy_validate_step. The validator hits https://your-mcp-server.mcpize.com/health and confirms 200.
Manual end-to-end:
# Health
curl https://your-mcp-server.mcpize.com/health
# → {"status":"ok","server":"your-mcp-server","version":"1.0.0"}
# OAuth discovery (if you wired Phase 7.2)
curl https://your-mcp-server.mcpize.com/.well-known/oauth-authorization-server | jq .issuer
# → "https://your-mcp-server.mcpize.com"
# Connect from Claude Desktop config:
# {
# "mcpServers": {
# "my-server": { "url": "https://your-mcp-server.mcpize.com/mcp" }
# }
# }
Common traps
isHttpMode()doesn't checkPORT, covered in Step 4. The single most common failure.- Prisma custom output, covered in Step 5.
startCommandwith shell side-effects, covered in Step 6.- Forgetting secrets, container starts, every DB call fails. Set secrets before deploy.
devDependenciesneeded at runtime. Buildpacks runsnpm ci --omit=devby default. Move runtime deps out ofdevDependencies.- Hardcoding the URL in the README. MCPize URLs are stable per-server but the format may change. Use
homepagefield in package.json as the canonical link.
What good looks like
mcpize deploy from a clean checkout finishes in 2-3 minutes, ends with "Server is live", and the /health URL returns the right version. Subsequent deploys are one command. Rollback is one command. You don't think about Cloud Run, Buildpacks, or container registries.
For most MCP servers, MCPize is the right host. Self-hosting (8.2) is for when you need custom infrastructure or have specific compliance needs.