MCP OAuth
JamRelay includes an OAuth authorization server for remote MCP clients. MCP OAuth is separate from owner sessions and each provider's authentication flow. Provider credentials are stored through the encrypted connection store.
Supported features
- Authorization Code flow;
- PKCE
S256enforced; - exact per-client redirect validation;
- RFC 9207
issin authorization responses; - RFC 9728 protected-resource metadata at root and path-suffixed discovery URLs;
- authorization-server metadata;
- MCP
resourcevalidation; - one-hour access tokens;
- rotating refresh tokens;
- public clients with
token_endpoint_auth_method=none; - confidential clients with
client_secret_basicorclient_secret_post; - multiple static clients;
- RFC 7591 Dynamic Client Registration for deployed clients;
- owner approval protected by
MCP_OAUTH_OWNER_SECRET.
After approval, the consent screen records the selected provider connections and operation permissions in the MCP grant. Use the local CLI to inspect clients/grants (clients list) or revoke a client (clients revoke CLIENT_ID). Repeat owner consent to change a grant. Tokens without a current grant are rejected and must be authorized again.
Authorization screen
GET /oauth/authorize is served by the JamRelay backend and renders the branded dark authorization screen. It keeps OAuth parameters in backend-validated hidden form fields, never displays the full callback URL, and accepts the owner secret only over the password field on the approval form. Authorization pages are marked no-store and include a restrictive content security policy. Selecting Cancel returns the client to its registered callback with error=access_denied and the original state.
Recommended configuration for automatic clients
For clients that support DCR:
PUBLIC_BASE_URL=https://mcp.example.com
MCP_OAUTH_OWNER_SECRET=<STRONG_RANDOM_SECRET>
MCP_OAUTH_DCR_ENABLED=trueNo client ID or callback needs to be created manually. The client registers itself first.
Discovery chain:
/.well-known/oauth-protected-resource
/.well-known/oauth-protected-resource/mcp
/.well-known/oauth-authorization-server
/oauth/register
/oauth/authorize
/oauth/tokenMultiple static clients
Set MCP_OAUTH_CLIENTS_PATH to a JSON registry. Locally its default is:
./data/mcp-oauth-clients.jsonand in production Docker:
/data/mcp-oauth-clients.jsonStart from:
examples/mcp-oauth-clients.example.jsonEach static client can have several redirect URIs. Clients without a secret can use tokenEndpointAuthMethods: ["none"].
DCR security model
POST /oauth/register is unauthenticated by design because an unknown MCP client must obtain a client ID before authorization can begin. Registration does not authorize access to any music provider.
JamRelay limits the exposed registration surface by:
- OAuth endpoint rate limiting;
- a bounded dynamic-client registry;
- short retention for registrations that were never approved;
- eventual pruning of inactive approved registrations;
- mandatory owner approval before code issuance;
- PKCE
S256; - redirect scheme/host validation;
- hashed storage of dynamically generated client secrets.
Disable DCR if you only want pre-registered clients:
MCP_OAUTH_DCR_ENABLED=falseNative clients and callbacks
Native/CLI clients may register a random loopback callback such as:
http://localhost:49152/oauth/callbackJamRelay accepts loopback HTTP callbacks. For clients that explicitly register as application_type=native, it also accepts private-use callback schemes while rejecting dangerous content/file schemes.
Persistent OAuth store
OAuth state defaults to:
./data/mcp-oauth.jsonlocally and:
/data/mcp-oauth.jsonin the production Docker image.
The store contains hashed authorization codes/tokens, refresh-token state, and DCR metadata. Keep it persistent and private.
CIMD status
The MCP 2026-07-28 specification prefers Client ID Metadata Documents (CIMD) and deprecates DCR long-term. JamRelay intentionally does not advertise CIMD in this release.
Supporting CIMD on the authorization-server side means fetching client-controlled HTTPS metadata. That should be added only with deliberate SSRF and DNS-rebinding protections. Current registration is provided through the static registry plus DCR.
See MCP OAuth registration for client-specific behavior.
See Provider connections and Security for owner-session and grant-enforcement details.
Secure provider OAuth without a dashboard
Provider OAuth does not accept an MCP access token as owner consent. First visit /owner/login and submit the owner secret with the form nonce. Then open /auth/providers/<provider>/start and approve the exact connection/replacement. The consent POST includes session CSRF and binds a one-time initiation to the provider's state, connection and owner session. Complete the provider redirect in that same browser within ten minutes. Logging out or restarting invalidates pending browser authorization. Existing provider state and SoundCloud/YouTube PKCE verification remain mandatory.
If the callback fails, old credentials are preserved. A fresh owner approval is required to retry; replaying a callback cannot update credentials. New authorization cannot use an old account's refresh token. Apple Music remains a separate Developer Token/Music User Token workflow described in configuration.
MCP clients still connect to /mcp, discover OAuth metadata, register with DCR or the static registry, and obtain explicit owner consent for selected connection IDs and permissions. The owner secret is entered only in the consent form; never share it with an AI client. Repeat consent to change a grant. Local client revocation immediately blocks existing access tokens; successful new owner consent creates a replacement grant.