---
title: "Deploy to MCPize Cloud Run, managed hosting in 5 minutes | Recipe — StudioMeyer Academy"
description: "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."
canonical: "https://studiomeyer.academy/en/recipes/8.1-mcpize-cloud-run"
language: en
markdown_versions: ["https://studiomeyer.academy/en/recipes/8.1-mcpize-cloud-run.md"]
publisher: "StudioMeyer Academy, https://studiomeyer.academy (llms.txt: https://studiomeyer.academy/llms.txt)"
---

[← Alle Recipes](https://studiomeyer.academy/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](https://studiomeyer.academy/login?next=/recipes/8.1-mcpize-cloud-run) 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.

### Step 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:

```bash
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.

### Step 2: Install MCPize CLI + initialize

```bash
npm install -g mcpize
mcpize login
mcpize init
```

`mcpize init` adds an `mcpize.json` to your repo:

```json
{
  "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`.

### Step 3: First deploy

```bash
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
```

### Step 4: Trap #1, \`PORT\` trigger

The single most common "container failed to start" failure mode:

```typescript
// 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`:

```typescript
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`:

```json
"startCommand": "node dist/server.js --http"
```

But fixing `isHttpMode()` is the right answer.

### Step 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**:

```json
"buildCommand": "npm ci && npx prisma generate && npm run build"
```

Default output is simpler. Use it.

### Step 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:

```json
// 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.

### Step 7: Set secrets

```bash
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:

```bash
# 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 · run on your machine

ls -la mcpize.json 2>/dev/null || (cat package.json 2>/dev/null | grep -i mcpize | head -3) || echo "no mcpize config"

**Expect:** mcpize.json present in repo root, or "mcpize" referenced in package.json.

**If stuck:** Run \`npx mcpize init\` in your repo root, then \`npx mcpize deploy --yes\`.

### Step 8: Deploy iterations

Subsequent deploys are the same one command:

```bash
mcpize deploy
```

Each deploy creates a new Cloud Run revision (00002, 00003, ...). Old revisions stay around for instant rollback:

```bash
mcpize rollback 00002
```

### Step 9: Verify

Run `academy_validate_step`. The validator hits `https://your-mcp-server.mcpize.com/health` and confirms 200.

Manual end-to-end:

```bash
# 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 surfac](https://studiomeyer.academy/recipes/7.5-error-handling) [Docker Compose, direct hosting →](https://studiomeyer.academy/recipes/8.2-docker-compose)

Phase 8

MCPize Cloud Run, Docker Compose, health endpoints, blue-green

[Alle Phasen →](https://studiomeyer.academy/recipes)

Concepts

[MCPize](https://studiomeyer.academy/concepts/mcpize) [Dual transport (stdio + HTTP)](https://studiomeyer.academy/concepts/dual_transport)

## Sitemap

Public pages of this site as Markdown: [sitemap](https://studiomeyer.academy/sitemap.md). Curated entry point: [llms.txt](https://studiomeyer.academy/llms.txt), full text: [llms-full.txt](https://studiomeyer.academy/llms-full.txt).
