1. What you need
- A host with Docker (and
docker compose). - A public HTTPS origin — a reverse proxy (Caddy, nginx, Traefik) or tunnel (e.g. Cloudflare Tunnel) that forwards to Charlotte’s port.
- That’s it.
2. Generate a bearer token
Charlotte’s HTTP mode refuses to start without a bearer token — it is the root credential for everything, including the OAuth facade (steps 3 and 5 below). Generate a long random one and export it asCHARLOTTE_AUTH_TOKEN:
3. Write charlotte.config.json
Charlotte readscharlotte.config.json from its working directory (or a path passed via --config). A minimal HTTP config:
host—"0.0.0.0"to be reachable from outside the container; default127.0.0.1is loopback-only.port— defaults to3737.profile— the HTTP tool set, fixed at startup; default"browse".publicOrigin— the publichttps://origin clients reach this server at; enables the OAuth facade (step 5). Unset = bearer-only mode.
http.* key is documented in Configuration.
4. Run it
The shippeddocker-compose.yml builds and runs the HTTP-mode image:
docker-compose.yml:
CHARLOTTE_AUTH_TOKENexported in your shell (from step 2) before you rundocker compose up—docker-compose.ymlrequires it and refuses to start without it../charlotte.config.jsonmounted read-only into the container (already wired viavolumes:— just make sure the file from step 3 exists at that path).
5. Connect claude.ai
With Charlotte reachable athttps://charlotte.example.com (step 1) and running with http.publicOrigin set to that same origin (step 3), add it as a custom connector:
- In claude.ai: Settings → Connectors → Add custom connector.
- Name: anything. URL:
https://charlotte.example.com/mcp. - Leave the OAuth Client ID / Secret fields blank. You never enter your
CHARLOTTE_AUTH_TOKENin the claude.ai UI — you’ll be redirected to Charlotte’s own consent page partway through the connection attempt, and that is the one place you type the token. - Save / attempt the connection.
Troubleshooting
Run this first:PASS/WARN/FAIL output is self-describing).
”No usable sandbox!” in Charlotte’s logs
Seccomp/AppArmor posture isn’t right — see Docker: Sandbox and security posture.401 from /mcp, or claude.ai can’t authenticate
Token mismatch. Confirm the container has the token you think it has (docker compose config shows the resolved environment: block), and that you entered the same token on Charlotte’s consent page:
Adding the connector fails before the consent page appears
Check, in order: (1)http.publicOrigin is set and matches the URL you gave claude.ai exactly; (2) your reverse proxy / tunnel is actually forwarding https://charlotte.example.com/* — try curl https://charlotte.example.com/healthz from outside your network; (3) the inbound Host-header guard isn’t rejecting the proxy’s forwarded Host header (see Security) — add it to http.allowedHosts if so.