MCP OAuth Gateway
by ImKevinLe
README.md
# MCP OAuth Gateway (multi-tenant)
One deploy that lets any client connect **their own ChatGPT or Claude** to their
brand's n8n MCP toolkit with a one-click **"Sign in"** — no token to paste, no CLI.
It speaks the OAuth flow the AI apps require, authenticates the human with a
**passwordless magic-link email**, then transparently proxies MCP traffic to the
brand's n8n endpoint with the n8n bearer token injected server-side. n8n is
untouched. Each brand is a **tenant** served at its own subdomain; a single
gateway instance serves them all.
```
client's ChatGPT / Claude → https://<brand>.mcp.lucyboo.io/mcp
│ OAuth: discover → register → sign in (email link) → token
▼
┌──────────────────────────────┐
│ MCP OAuth Gateway (this app) │ one Coolify service, many tenants
│ • per-host OAuth server │
│ • magic-link sign-in │
│ • tenant registry (SQLite) │
└───────────────┬──────────────┘
│ Authorization: Bearer <that tenant's n8n token>
▼
n8n MCP endpoint for that brand (lucyboo.io/mcp/<brand>)
```
## Client prerequisite
Custom MCP connectors need a **paid** plan:
- **ChatGPT:** Plus, Pro, Business, or Enterprise (Developer mode / connectors).
- **Claude:** Pro, Max, Team, or Enterprise.
Free tiers can't add any connector. Confirm before onboarding.
## One-time setup
1. **Wildcard DNS:** point `*.mcp.lucyboo.io` at the server.
2. **Wildcard TLS:** in Coolify, use a wildcard cert (DNS challenge) for
`*.mcp.lucyboo.io`, or add each brand subdomain to the app so Coolify issues a
cert per host. Wildcard means zero per-client cert steps.
3. **Deploy this repo** in Coolify: New Resource → Application → Dockerfile.
- Persistent volume mounted at `/data`.
- Env vars from [`.env.example`](./.env.example): `BASE_DOMAIN`, `SESSION_SECRET`,
the `SMTP_*` values, `DB_PATH=/data/gateway.db`.
- Bind the app to `*.mcp.lucyboo.io` (or the wildcard domain).
4. Deploy. `https://anything.mcp.lucyboo.io/healthz` should return
`{"ok":true,...}` once DNS resolves.
## Onboard a client (per brand)
Two steps, no redeploy:
**1. Stand up the brand's n8n workflow** — clone the Le Design MCP workflow, swap
in that client's tools, credentials, brand data, and a fresh bearer token,
activate it. (This is the real per-client work; it's the brand's actual toolkit.)
**2. Register the tenant** — run the admin CLI in the container (Coolify → the
service's Terminal), or over SSH:
```bash
node src/admin.js add \
--slug a1grass \
--brand "A-1 Grass" \
--n8n https://lucyboo.io/mcp/a1-grass \
--token <that-workflow's-n8n-bearer-token> \
--emails owner@a1grass.com,ops@a1grass.com
```
The client's connector URL is then `https://a1grass.mcp.lucyboo.io/mcp`.
Other commands: `node src/admin.js list`, `node src/admin.js remove --slug a1grass`.
(You can also seed tenants on boot with a `TENANTS_SEED` JSON file — see
[`tenants.json.example`](./tenants.json.example).)
## What you send the client
- Connector URL: `https://<brand>.mcp.lucyboo.io/mcp`
- In **Claude**: Add custom connector → that URL → **Sign in now** → OAuth client
**Register automatically**. (Not "Use Claude's published identity".)
- In **ChatGPT**: add a custom MCP server with that URL, complete the sign-in.
They enter an approved email, click the link in their inbox, and it's live.
## Security
- Tenants are isolated by subdomain; a token issued for one brand can't call
another. Sessions, codes, and tokens are all tagged by tenant.
- `allowed_emails` per tenant is the allowlist — only those addresses can sign in
or hold a token. Remove one to revoke that person (tokens fail on next check).
- The n8n token never reaches the client; the proxy injects it server-side.
- Access tokens last 1 hour; refresh tokens are issued. HTTPS only; cookies are
`Secure` + `HttpOnly`.
- The DB (on the private `/data` volume) holds each tenant's n8n token. Keep the
volume private; it's the one secret store.
## Known limitations (v1)
- **Magic link is same-device** — open the email link in the browser that started
the connect flow so the AI app's callback resumes cleanly.
- SQLite on a mounted volume — right for this scale, not multi-node.
- All of a tenant's tools are exposed to any signed-in user for that brand; no
per-user tool scoping yet.
## Local smoke test
```bash
npm install
BASE_DOMAIN=mcp.local SESSION_SECRET=dev DB_PATH=./dev.db \
SMTP_HOST=localhost SMTP_PORT=25 SMTP_USER=x SMTP_PASS=x npm start
# add a tenant, then curl with a Host header:
node src/admin.js add --slug demo --brand Demo --n8n https://example.com/mcp --token t --emails you@x.com
curl -s -H 'Host: demo.mcp.local' http://localhost:8080/.well-known/oauth-authorization-server
```
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues