Skip to main content
View as Markdown

Troubleshooting: Auth, Email & MCP

Once the server boots, the next class of problem comes from the services layered on top. These are the ones that most often surprise people — the first two because they are warnings rather than failures, so the app looks healthy while something quietly does not work, and the MCP ones because they are the opposite: a hard refusal to start, on a variable that used to be correct.

Auth: "JWT signing keys could not be read"

code
⚠ 2 JWT signing keys could not be read with the current auth secret and were
  regenerated — previously issued tokens are no longer valid
⚠ 5 stored connection tokens encrypted with a different key — affected users
  must reconnect

Two warnings, one cause: the encryption key changed between this start and the last one. Unless you set AUTH_SECRET yourself it is derived from that key, so a new key rotates the signing secret as well, and everything sealed under the old one stops opening.

Almost always the key was never persisted in the first place. Read the line above them in the same banner:

code
✓ Encryption key: generated at /var/lib/sovrium/encryption-key

generated at on a restart — where a settled install says from — means the data directory did not survive, so the key is new on every boot while the database keeps the old ciphertext. That is the ephemeral-filesystem shape: a container with no volume, or a platform that rebuilds the filesystem on each deploy. Pin SOVRIUM_ENCRYPTION_KEY to a fixed value and it stops recurring — see Secrets.

The two are handled differently on purpose. A signing key is derived material, so Sovrium regenerates it and the only cost is that already-issued tokens no longer verify. A connection token is a delegated credential to somebody's third-party account, so it is left exactly as it is and reported instead. Those users reconnect the integration themselves; nothing is discarded on their behalf.

"Email sending disabled — SMTP not configured"

code
Email sending disabled — SMTP not configured (set SMTP_HOST to enable)

A warning, not a failure — and the more dangerous for it. The app boots, sign-up succeeds, password reset returns 200. The mail is written to the log instead of sent, so the links never arrive and the flow looks broken from the user's side only.

Set SMTP_HOST and its companions to enable delivery:

>_ terminal
SMTP_HOST=smtp.example.com
SMTP_PORT=587          # default
SMTP_USER=apikey
SMTP_PASS=<secret>

See Environment Variables for the full set, and Email Integration for provider setup. Anything with email verification or password reset should be tested against a real SMTP host before it ships.

MCP: "the MCP static tokens were removed"

code
MCP_TOKEN_ADMIN is set, but the MCP static tokens were removed. They had no user
identity, so the row-level user_access tier never ran for a token-authenticated
caller. Issue an API key instead (app.auth.apiKeys) and present it on the
x-api-key header, then unset MCP_TOKEN_ADMIN.

MCP_TOKEN_ADMIN, MCP_TOKEN_MEMBER and MCP_TOKEN_VIEWER no longer exist. If your instance still sets one and MCP_ENABLED=true, the server refuses to start — deliberately. Ignoring the variable would be worse: you would believe /mcp is guarded by the secret you issued, when it is guarded by something else entirely.

The fix is three steps:

>_ terminal
# 1. Remove the retired variables
unset MCP_TOKEN_ADMIN MCP_TOKEN_MEMBER MCP_TOKEN_VIEWER MCP_AUTH_STRATEGY
app.yaml
# 2. Make sure the app has auth, with self-service keys enabled
auth:
  strategies:
    - type: emailAndPassword
  apiKeys: true
  1. Sign in as the user whose role the client should inherit, mint an API key, and send it on the x-api-key header — not Authorization: Bearer.

The role is no longer baked into the credential: a key acts as its owner, resolved live on every call. Demote that user and every key they hold narrows with them; ban them and the keys stop working. That is what the static tokens could not do — carrying no user, they never triggered row-level record rules at all.

MCP: "MCP_AUTH_STRATEGY=token names a strategy that no longer exists"

code
MCP_AUTH_STRATEGY=token names a strategy that no longer exists. /mcp now
dispatches on the header a request carries: x-api-key is verified as an API key,
Authorization: Bearer as an OAuth access token. Unset MCP_AUTH_STRATEGY (oauth2
is still accepted as a deprecated no-op).

There is no strategy to choose any more. /mcp decides which verifier to use from the header the request actually presents, so both credentials are live at once — a Claude Desktop user on OAuth and a CI job holding an API key can call the same instance.

Unset the variable. MCP_AUTH_STRATEGY=oauth2 is still accepted so a correct config is not punished, but it selects nothing and will be removed.

MCP: "MCP_ENABLED=true requires app.auth"

code
MCP_ENABLED=true requires app.auth to be configured. Both MCP credentials — API
keys and OAuth access tokens — are Better Auth plugins, so without app.auth
nobody can authenticate to /mcp. Either configure app.auth or unset MCP_ENABLED.

Both surviving credentials are issued by the auth layer, so an app with no app.auth block gives nobody a way in. Mounting /mcp there would leave a route that is either unreachable or unguarded, so the boot stops instead. Add an app.auth block, or leave MCP_ENABLED off.

Still stuck?

  • Run sovrium validate <config> to check the configuration on its own.
  • An unhandled failure prints Unexpected error: with a message and an issue link. That banner means Sovrium did not anticipate the failure — copy the message into the report.
  • Search the docs with ⌘K, or open a GitHub issue or discussion.

Last updated September 1, 2026

This documentation was written with AI, so errors or outdated content are possible. Sovrium is in beta. Contributions and corrections are welcome.

Built with Sovrium