Remote access with embedded OAuth
When MCP_AUTH_PASSWORD is set, this server also acts as an OAuth 2.1
authorization server, so no separate Keycloak or Authentik install is needed. It
provides dynamic client registration, PKCE S256, a password screen, access
tokens and rotating refresh tokens.
Common setup
Set these in .env:
MCP_AUTH_PASSWORD=a-strong-password-of-at-least-12-characters
MCP_AUTH_STATE_DIR=/data/firefly-mcp-auth
MCP_RESOURCE_URL=https://mcp.example.com
The state directory must live on a persistent volume; if it is lost, every
client has to be authorized again. MCP_RESOURCE_URL must match the URL the
proxy publishes, character for character. Writing the internal Docker address
there fails the audience check, and the client only sees "invalid token".
Scopes
The three scopes map onto the three execution surfaces:
| Scope | Surface |
|---|---|
firefly:read |
firefly_query |
firefly:write |
+ firefly_mutate |
firefly:destructive |
+ firefly_destructive |
Broader implies narrower, so one scope is enough. Entering the password grants
all three, whatever the client asked for — ChatGPT requests firefly:read alone
and would otherwise never be able to record a transaction. RFC 6749 §3.3 allows
a grant wider than the request as long as the token response reports it, and
/oauth/token returns the granted scope.
There is no second screen asking which of the three to allow. Whoever holds the password could have ticked every box on it, so the question only added a step. For a connection that cannot write, give this server a read-only Firefly Personal Access Token — that limit is Firefly's to enforce, not this server's.
ChatGPT
- Publish the server over HTTPS. For a home server, Cloudflare Tunnel is the recommended route.
- In ChatGPT's plugin / custom connector screen, enter
https://mcp.example.com/mcpas the MCP endpoint. - Choose OAuth as the authentication method.
- On the Firefly screen that opens, enter the
MCP_AUTH_PASSWORDpassword. That grants the connection all three scopes.
ChatGPT handles client registration, PKCE and the token exchange by itself.
Claude web and Desktop
Add the same /mcp address as a custom connector and start the OAuth flow, then
enter the password. Claude also registers as a public client and uses PKCE;
there is no static bearer token to enter.
HTTPS options
Cloudflare Tunnel — recommended
No port forwarding and no local certificate. Point a stable hostname at
Cloudflare in DNS and set the tunnel target to http://firefly-mcp:3000. Give
the cloudflared service in the compose example your CLOUDFLARE_TUNNEL_TOKEN.
Caddy
On a VPS, point DNS at the server, run compose with the caddy profile and
change the hostname in Caddyfile. Caddy obtains the certificate on 80/443 and
proxies to firefly-mcp:3000:
Dokploy / Traefik
In Dokploy, create an HTTPS router bound to the domain and set the container port to 3000. The equivalent labels are:
labels:
- traefik.enable=true
- traefik.http.routers.firefly-mcp.rule=Host(`mcp.example.com`)
- traefik.http.routers.firefly-mcp.entrypoints=websecure
- traefik.http.routers.firefly-mcp.tls=true
- traefik.http.services.firefly-mcp.loadbalancer.server.port=3000
In all three options MCP_RESOURCE_URL is the origin of the external hostname
and nothing else: https://mcp.example.com. The MCP connection URL may include
the /mcp alias. An internal host, a different port, or a path in the resource
value produces an audience mismatch.