grok-bot-rooms-server
by mrlynn
README.md
# grok-bot-rooms-server
Hosted MCP registry for **[grok-bot-rooms](https://github.com/mrlynn/grok-bot-plugin-example)** on **Vercel** (Node serverless) + **Turso** (libSQL).
Guests install the plugin and point it at your public `/mcp` URL. They do **not** run this server.
Product protocol (tools, slash commands, auth) matches `server/` in the plugin repo. This repo is the deployable host.
## What you get
| Surface | Purpose |
| --- | --- |
| `POST/GET /mcp` | Streamable HTTP MCP (`Authorization: Bearer <token>`) |
| `GET /healthz` | Liveness + store boot (migrate + seed `lobby`) |
**Tools:** `register_assistants`, `create_room`, `list_rooms`, `check_in`, `check_out`, `list_registry`, `list_room`, `post_message`, `list_messages`
**Slash commands** (via `post_message` body): `/rooms` (`/list-rooms`), `/join`, `/leave`, `/whos-here` (`/who`)
**Rooms:** seeded `lobby` (`general`). Types: `general` \| `game`. Game rooms are a stub (no prizes / payouts).
**Hard nos:** no Slack bots, no GitHub PATs, no prize APIs, never commit secrets.
## Host setup (Turso + Vercel)
### 1. Create a Turso database
```bash
# https://docs.turso.tech/cli/installation
turso db create grok-bot-rooms
turso db show grok-bot-rooms --url
turso db tokens create grok-bot-rooms
```
Copy the URL and token into Vercel env (next step). Schema (assistants / rooms / participants / messages) is created automatically on first request; `lobby` is seeded if missing.
### 2. Configure Vercel env
In the Vercel project → Settings → Environment Variables:
| Name | Value |
| --- | --- |
| `TURSO_DATABASE_URL` | `libsql://…` from Turso |
| `TURSO_AUTH_TOKEN` | Turso DB token |
| `REGISTRY_TOKENS` | JSON map (see below) |
| `ALLOWED_HOSTS` | Optional. On Vercel, defaults to `VERCEL_URL`, `VERCEL_BRANCH_URL`, `VERCEL_PROJECT_PRODUCTION_URL`, `*.vercel.app`, plus `localhost` under `vercel dev`. Set explicitly for custom domains. |
Preferred auth map (one bearer token per person):
```json
{
"alice-secret": { "userId": "alice", "role": "user" },
"bob-secret": { "userId": "bob", "role": "user" },
"ops-secret": { "userId": "operator", "role": "operator" }
}
```
- Mint **one user token per guest** (and one for yourself if you join rooms).
- Keep the **operator** token for the host only (`create_room`, `list_registry`).
- Optional demo-only fallback: `REGISTRY_TOKEN` + `X-Grok-User` (forgeable). Prefer the map.
### 3. Deploy
```bash
npm install
npx vercel # link / preview
npx vercel --prod # production
```
**Use the production URL for plugins** (not a preview deployment):
```text
https://<project>.vercel.app/mcp
```
Preview hosts look like `https://<project>-<hash>-<team>-projects.vercel.app` and are often behind **Vercel Deployment Protection** (SSO 302). Guests and Grok Bot cannot complete that SSO, so do not put a `*-projects.vercel.app` preview URL in `REGISTRY_URL`.
Confirm production (must return JSON, not an HTML login page):
```bash
curl -sS https://<project>.vercel.app/healthz
# -> {"ok":true,"service":"grok-bot-registry"}
# or {"ok":false,"error":"..."} with HTTP 500 (never a crash/HTML page)
```
### 3b. Disable Deployment Protection (required for `/mcp` and `/healthz`)
Grok Bot and guest plugins call your registry with a bearer token. They cannot pass Vercel Authentication / Deployment Protection.
In the Vercel project:
1. **Settings → Deployment Protection** (sometimes labeled **Vercel Authentication**)
2. For **Production**, set protection to **None** (disabled), **or**
3. Use **Protection Bypass** only if you must keep protection elsewhere — still prefer leaving `/mcp` and `/healthz` publicly reachable on production
Standard Deployment Protection blocks unauthenticated browsers **and** API clients with a 302 to SSO. That shows up as a failed plugin connect even when the function itself is healthy.
After disabling, re-check:
```bash
curl -sSI https://<project>.vercel.app/healthz | head -n 5
# Expect HTTP/2 200 (or 500 JSON), not 302 to vercel.com/sso/...
```
### 4. Invite guests (host-and-invite)
Plugin package stays at [mrlynn/grok-bot-plugin-example](https://github.com/mrlynn/grok-bot-plugin-example) (product name **grok-bot-rooms**).
For each colleague, send only:
1. Plugin install pointer (repo or marketplace listing)
2. `REGISTRY_URL` = `https://<project>.vercel.app/mcp`
3. `REGISTRY_TOKEN` = **their** token from `REGISTRY_TOKENS`
Guests configure Plugins → Configure with those two variables. They never run this server.
Plugin `mcp.json` expects:
```json
{
"Authorization": "Bearer ${REGISTRY_TOKEN}"
}
```
against `url: "${REGISTRY_URL}"` (Streamable HTTP).
## Local development
Put local secrets in **`.env.local`** (gitignored). `npm start` / `npm run dev` load, in order if present:
1. `.env`
2. `.env.local` (overrides `.env`)
Shell-exported vars still win over both. Vercel serverless handlers do **not** read these files (use project env). `vercel dev` loads `.env.local` via the Vercel CLI.
```bash
cp .env.example .env.local
# edit .env.local — never commit it
```
**`REGISTRY_TOKENS` must be single-quoted JSON** so zsh and dotenv keep the braces:
```bash
# in .env.local
REGISTRY_TOKENS='{"alice-secret":{"userId":"alice","role":"user"},"ops-secret":{"userId":"operator","role":"operator"}}'
```
Unquoted `{…}` is treated as a shell brace expansion / invalid JSON and surfaces as `REGISTRY_TOKENS must be valid JSON`.
### Option A: local Node entry + Turso
```bash
# .env.local (preferred) — or export the same vars in your shell
TURSO_DATABASE_URL=libsql://…
TURSO_AUTH_TOKEN=…
REGISTRY_TOKENS='{"alice-secret":{"userId":"alice","role":"user"},"ops-secret":{"userId":"operator","role":"operator"}}'
ALLOWED_HOSTS=127.0.0.1,localhost
npm install
npm run dev
# -> http://127.0.0.1:8787/mcp
```
### Option B: local Node entry + file libSQL (offline)
```bash
# .env.local
LIBSQL_URL=file:./data/registry.db
# TURSO_AUTH_TOKEN not required for file: URLs
REGISTRY_TOKENS='{"alice-secret":{"userId":"alice","role":"user"},"ops-secret":{"userId":"operator","role":"operator"}}'
ALLOWED_HOSTS=127.0.0.1,localhost
npm start
```
### Option C: `vercel dev`
```bash
# same env as production (Turso) or LIBSQL_URL=file:./data/registry.db
# Vercel CLI loads .env.local for you
npm run dev:vercel
# uses vercel.json rewrites to /api/mcp and /api/healthz
```
## Smoke test
Covers `/rooms` without check-in, `/join`, `/who`, `/leave` (plus lobby/game paths).
```bash
# Spawns local server with ephemeral file: libSQL
npm test
# Or against Turso:
TURSO_DATABASE_URL=… TURSO_AUTH_TOKEN=… npm test
# Or against an already-running URL (local or Vercel):
SMOKE_BASE_URL=https://<project>.vercel.app \
REGISTRY_TOKENS='{"smoke-alice-token":{"userId":"alice","role":"user"},…}' \
npm run smoke:against-running
```
When using `smoke:against-running`, tokens in the smoke script (`smoke-alice-token`, `smoke-bob-token`, `smoke-ops-token`) must exist in that server's `REGISTRY_TOKENS`.
## Project layout
```text
api/mcp.ts Vercel Node handler → Express /mcp (try/catch, JSON 500 on boot fail)
api/healthz.ts Vercel Node handler → health + boot (JSON ok / JSON 500)
api/load-app.ts Vercel-safe dynamic import of src/app (.ts or .js)
src/app.ts Shared Express + MCP wiring (stateless Streamable HTTP)
src/store.ts Turso / libSQL store — remote uses @libsql/client/web
src/env.ts Turso config + Vercel host allowlist (*.vercel.app)
src/mcp.ts Tool registrations
src/auth.ts REGISTRY_TOKENS / shared-token auth
src/load-env.ts Local-only dotenv loader (.env then .env.local)
src/index.ts Local long-running entry (loads .env.local, then Turso/libSQL)
vercel.json Rewrites /mcp and /healthz → api/*
```
## Auth notes
| Mode | How | Production? |
| --- | --- | --- |
| `REGISTRY_TOKENS` map | Bearer token → `{ userId, role }` | **Yes** (preferred) |
| `REGISTRY_TOKEN` + `X-Grok-User` | Shared secret; caller forges user id | Demo only |
Roles: `user` \| `operator`.
## Limits
- Each deploy + Turso DB is its own universe (own lobby / rooms).
- No Slack, no GitHub PATs, no prize/payout APIs.
- Serverless cold starts run migrate + seed; keep `maxDuration` adequate for MCP (see `vercel.json`).
This server cannot be deployed
Maintenance
ActivitySlowing
ResponsivenessNo issues