This project ships a full self-hostable backend on Vercel: Neon (Postgres) · Cloudflare R2 (storage) · Upstash Redis (rate limits).
- A Vercel project linked to this repo.
- A Neon project (free tier works for early traffic).
- A Cloudflare R2 bucket (
3d-agent-avatarsor your own) with a public custom domain (e.g.cdn.three.ws) for zero-egress public delivery. - (Optional but recommended) An Upstash Redis database for distributed rate limiting. Without it, rate limits fall back to in-memory per-instance.
Copy .env.example and fill in:
cp .env.example .env.localThen add the same variables to your Vercel project (both Preview and
Production). JWT_SECRET must be a long random string — generate with:
openssl rand -base64 64Apply the migration:
psql "$DATABASE_URL" -f api/_lib/schema.sqlThe file is idempotent; re-running it is safe.
Apply the project's cors.json to your R2 bucket (replace the bucket name):
aws s3api put-bucket-cors \
--bucket 3d-agent-avatars \
--cors-configuration file://cors.json \
--endpoint-url https://<account-id>.r2.cloudflarestorage.comAt minimum, claude.ai and https://three.ws/ must be allowed origins for GET
so the MCP render_avatar artifact can fetch the GLBs.
npm run deploy- Authorization server metadata:
https://three.ws/.well-known/oauth-authorization-server - MCP endpoint:
POST https://three.ws/api/mcpwith{"jsonrpc":"2.0","id":1,"method":"tools/list"}and a bearer API key.
- Storage: R2 charges no egress. Even with a viral spike, your only cost is storage + requests, both cheap.
- Database: Neon autoscales; for very high traffic, switch
sqlto the pooled connection string or consider branching for heavy tenants. - Rate limits: Defaults in api/_lib/rate-limit.js —
tune once real traffic patterns emerge. Per-plan daily caps are enforced from
the
plan_quotastable. - Observability: Every tool call writes to
usage_events. Build dashboards directly in SQL or stream to your analytics tool. - Keys & secrets:
JWT_SECRETshould be rotated via a key set — add a new kid, wait for old tokens to expire, then remove the old one. Refresh tokens and API keys are stored hashed (SHA-256).