Recipe-Inhalt ist auf Englisch. Englisches Original lesen →
← Alle Recipes
Phase 8 · Deploy MCP·9 steps

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.

9 steps0%
Du liest ohne Account. Mit Login speichern wir Step-Fortschritt + Notes.

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 on PORT env, see 7.1).
  • package.json has "main": "dist/server.js" and "bin": { "your-server": "./dist/server.js" }.
  • npm run build works locally and produces dist/server.js.
  • dist/server.js starts and binds to process.env.PORT if 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:

  1. MCPize uploads your repo (excluding node_modules, .git, .env).
  2. Buildpacks builds a container image: Node 22 base + npm ci + npm run build.
  3. Container pushed to MCPize's registry.
  4. Cloud Run starts a revision with the image.
  5. Cloud Run sets PORT=8080 and waits for the container to bind to it.
  6. If /health returns 200 within 60 seconds, the revision goes live and you get an HTTPS URL.
  7. 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 { ... } without output). Generated client lives in node_modules/@prisma/client, gets included automatically.
  • Or run prisma generate in 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.

Client-Check · auf Deinem Rechner ausführen
ls -la mcpize.json 2>/dev/null || (cat package.json 2>/dev/null | grep -i mcpize | head -3) || echo "no mcpize config"
Erwartet: mcpize.json present in repo root, or "mcpize" referenced in package.json.
Falls hängen geblieben: Run `npx mcpize init` in your repo root, then `npx mcpize deploy --yes`.

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 check PORT, covered in Step 4. The single most common failure.
  • Prisma custom output, covered in Step 5.
  • startCommand with shell side-effects, covered in Step 6.
  • Forgetting secrets, container starts, every DB call fails. Set secrets before deploy.
  • devDependencies needed at runtime. Buildpacks runs npm ci --omit=dev by default. Move runtime deps out of devDependencies.
  • Hardcoding the URL in the README. MCPize URLs are stable per-server but the format may change. Use homepage field 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.

Error handling, what to surfacDocker Compose, direct hosting