Authentication
External MCP clients authenticate with a Bearer JWT issued by Cinatra’s Better Auth OAuth-provider plugin. The same plugin authenticates A2A traffic into /api/a2a, so an OAuth client registered against your instance works for both surfaces with the same credentials.
Where the OAuth provider lives
Section titled “Where the OAuth provider lives”The plugin is configured inside src/lib/auth.ts and mounted at /api/auth/* alongside Better Auth’s normal endpoints. Discovery documents are exposed at:
/.well-known/oauth-authorization-server— OAuth 2.1 server metadata/.well-known/oauth-protected-resource— protected-resource metadata for clients that auto-discover
An MCP client that supports OAuth auto-discovery (Claude Code’s mcp-remote, several ChatGPT connector flows) reads these documents and finds the token endpoint and audience without manual configuration.
The flow
Section titled “The flow”- The MCP client opens the OAuth flow against your Cinatra instance.
- The user logs in (Better Auth session, possibly with passkey / 2FA depending on configuration).
- The OAuth provider issues a JWT with an audience that matches the instance’s canonical origin (tunnel-safe — the audience reflects the public-facing URL, not whichever proxy host the request hit).
- The MCP client stores the JWT and sends it in the
Authorization: Bearer <jwt>header on every MCP call. - The JWT is verified on every call. When it expires, the client refreshes through the same flow.
What the JWT carries
Section titled “What the JWT carries”The JWT identifies the user (or service account) and the OAuth client. The MCP server resolves both into an actor context — a typed envelope (PrimitiveActorContext) that every primitive handler receives. The actor carries:
source— one of"ui","route","worker","scheduler","agent","a2a","mcp".- The user identity (or service-account identity).
- The org / team / project / workspace context the call runs in.
Authorization runs against the actor. A user who can’t read an agent in the UI can’t read it over MCP either. The two paths are unified.
Local development
Section titled “Local development”For local development the loopback origin (http://localhost:3000) is treated specially:
- First-user-as-admin. The first user to register on a fresh instance becomes the platform admin (Better Auth
adminplugin). - Dev-admin bypass. When running on
127.0.0.1/localhost, an admin session can call admin-gated MCP handlers without registering an OAuth client. This is loopback-gated and never engages in production.
The shared dev bridge-token bypass and the XFF loopback fallback have been intentionally removed (the bridge between WayFlow and the host app uses a separate per-deployment shared secret described in Security).
Client registration
Section titled “Client registration”The OAuth provider supports dynamic client registration (RFC 7591). A new MCP client can register itself against /api/auth/oauth2/register without prior credentials and receive a client-id / client-secret pair. Auto-discovering MCP clients (mcp-remote, several ChatGPT connector flows) use this path so end users don’t have to provision a client manually before connecting.
For deployments that need to disable self-registration, the relevant Better Auth plugin flags are allowDynamicClientRegistration and allowUnauthenticatedClientRegistration in src/lib/auth.ts. Both default to enabled.
Tokens and rotation
Section titled “Tokens and rotation”The OAuth provider issues both access tokens and refresh tokens. Default TTLs are:
- Access tokens — 30 days.
- Refresh tokens — 1 year.
Clients exchange refresh tokens for new access tokens through the standard refresh_token grant. The full grant-type set is authorization_code, client_credentials, and refresh_token. Long TTLs are deliberate — they let MCP clients survive across dev sessions without re-running the browser flow.
For dev workflows that need a public URL, bring your own tunnel (Tailscale Funnel, named Cloudflare Tunnel, ngrok) and save the URL at /administration/development?tab=tunnel.
Where to go next
Section titled “Where to go next”- Connecting Claude Code with
mcp-remote: Connecting Claude Code - The actor envelope every primitive sees: Primitives
- The shared auth model with A2A: Cross-instance collaboration