Configuration
Copy .env.example to .env. Never commit .env or runtime token stores.
The complete variable table is generated from .env.example:
Open the generated environment reference →
Sources of truth
src/config.ts runtime validation/defaults
.env.example public configuration contract + human notes
↓
npm run docs:generate
↓
docs/environment-reference.mdWhen adding a variable, update the runtime schema and .env.example; do not hand-edit five translated tables.
Storage defaults
Local development:
./data/spotify-token.json
./data/mcp-oauth.json
./data/mcp-oauth-clients.jsonProduction Docker (NODE_ENV=production):
/data/spotify-token.json
/data/mcp-oauth.json
/data/mcp-oauth-clients.jsonAll paths can be overridden.
MCP authentication
JamRelay can accept OAuth and an optional static Bearer key at the same time.
MCP_AUTH_MODE=bearer enables MCP_API_KEY as an additional authentication path.
MCP_AUTH_MODE=none disables only the static API key. It does not make an OAuth-protected /mcp endpoint public.
OAuth owner secret
OAuth authorization is enabled when an owner secret is configured:
MCP_OAUTH_OWNER_SECRET=<STRONG_RANDOM_SECRET>That secret is entered only on JamRelay's owner approval page. It is never sent to the MCP client.
Dynamic clients
For Claude, Gemini CLI, VS Code, and other clients that can register automatically:
MCP_OAUTH_OWNER_SECRET=<STRONG_RANDOM_SECRET>
MCP_OAUTH_DCR_ENABLED=trueLegacy/static ChatGPT client
The backwards-compatible single-client variables are optional, but if any of these three are provided, all three are required:
MCP_OAUTH_CLIENT_ID=
MCP_OAUTH_CLIENT_SECRET=
MCP_OAUTH_REDIRECT_URI=They also require MCP_OAUTH_OWNER_SECRET.
Several pre-registered clients
Use:
MCP_OAUTH_CLIENTS_PATH=./data/mcp-oauth-clients.jsonand start from examples/mcp-oauth-clients.example.json.
The registry can contain ChatGPT, Cursor, VS Code, Claude, or any other known client simultaneously. DCR may remain enabled for automatic clients at the same time.
Local Bearer-only development
PUBLIC_BASE_URL=http://127.0.0.1:5267
HOST=127.0.0.1
PORT=5267
MCP_AUTH_MODE=bearer
MCP_API_KEY=GENERATE_A_SEPARATE_RANDOM_VALUE
SPOTIFY_TOKEN_STORE_PATH=./data/spotify-token.json
MCP_OAUTH_STORE_PATH=./data/mcp-oauth.json
TRUST_PROXY=falseLeave MCP_OAUTH_OWNER_SECRET empty if you do not want OAuth at all.
Remote deployment: ChatGPT + automatic OAuth clients
PUBLIC_BASE_URL=https://mcp.example.com
HOST=0.0.0.0
PORT=5267
MCP_AUTH_MODE=bearer
MCP_API_KEY=GENERATE_A_SEPARATE_RANDOM_VALUE
MCP_OAUTH_OWNER_SECRET=GENERATE_A_SEPARATE_RANDOM_VALUE
MCP_OAUTH_DCR_ENABLED=true
# Existing ChatGPT pre-registration:
MCP_OAUTH_CLIENT_ID=chatgpt-jamrelay
MCP_OAUTH_CLIENT_SECRET=GENERATE_A_SEPARATE_RANDOM_VALUE
MCP_OAUTH_REDIRECT_URI=https://EXACT_CALLBACK_FROM_CHATGPT
SPOTIFY_TOKEN_STORE_PATH=/data/spotify-token.json
MCP_OAUTH_STORE_PATH=/data/mcp-oauth.json
MCP_OAUTH_CLIENTS_PATH=/data/mcp-oauth-clients.json
TRUST_PROXY=trueThis configuration lets the existing ChatGPT static client coexist with DCR clients without changing ChatGPT's callback.
Generating secrets
node -e "console.log(require('crypto').randomBytes(32).toString('base64'))"Use separate random values for the encryption key, API key, OAuth client secret, and owner secret. TOKEN_ENCRYPTION_KEY must decode from base64 to exactly 32 bytes.