Deployment
Deploy MCP server + widget UI to Vercel, Railway, Docker.
ConversoKit is two deployable artifacts:
- MCP server (
apps/mcp-server) — Express, Node ≥18. - Widget UI (
apps/widget-ui) — static Vite output.
The widget UI talks to the MCP server via HTTPS (or window.openai inside ChatGPT). Deploy them together or separately.
Quickstart: conversokit deploy
The CLI writes deployment config straight into your project root:
npx conversokit deploy vercel # writes vercel.json + api/mcp.ts
npx conversokit deploy docker # writes Dockerfile + docker-compose.yml + .dockerignore
npx conversokit deploy railway # writes railway.json + Procfile
Re-run with --force to overwrite. The MCP server already answers GET /health so the platform health checks work out of the box. Each command prints next-step CLI instructions for that target.
Docker (works anywhere)
Multistage Dockerfiles ship in each app:
docker build -f apps/mcp-server/Dockerfile -t conversokit-mcp .
docker build -f apps/widget-ui/Dockerfile -t conversokit-ui .
docker run -p 3000:3000 --env-file .env conversokit-mcp
Or use the root docker-compose.yml:
docker compose up --build
# UI on :8080, MCP on :3000
Vercel (serverless MCP + static UI)
apps/mcp-server/vercel.json configures @vercel/node for the Express handler. Run from each app:
cd apps/mcp-server && vercel --prod
cd apps/widget-ui && vercel --prod
Set environment variables in the Vercel dashboard (STRIPE_SECRET_KEY, etc.).
Fly.io
cd apps/mcp-server
fly launch --copy-config --no-deploy # uses fly.toml
fly secrets set STRIPE_SECRET_KEY=sk_test_... STRIPE_WEBHOOK_SECRET=whsec_...
fly deploy
Railway
railway init
railway up
# config in apps/mcp-server/railway.json
Render
apps/mcp-server/render.yaml is a Render Blueprint:
# Connect your repo to Render and pick "Blueprint" — it picks up render.yaml.
Production reminders
- Persistent stores. The default
InMemory*stores inapps/mcp-server/src/store/lose data on restart. Swap them for a DB-backed implementation before going live (seeintegrations.md). - CORS.
cors()defaults to*. In production, restrict to your widget UI origin. COOKIE_SECRET. Set it. The dev fallback is intentionally weak.- Stripe webhooks. Configure the live webhook URL in your Stripe dashboard and store the corresponding
STRIPE_WEBHOOK_SECRET. - Rate limits. Add
express-rate-limit(or platform-level) on/tools/:nameso a misbehaving caller can't burn through paid integrations. - Health checks.
GET /healthreturns{ ok: true }and bypasses auth — point platform probes at it.