Auth, RBAC & Rate Limiting
An MCP connection is an authenticated actor calling your data, and it is treated as one. There is no AI-specific bypass anywhere in this path: a tool call runs through the same role permissions and row-level rules as the equivalent HTTP request from a human session.
Two Authentication Strategies
| Strategy | Default when | Credential |
|---|---|---|
token |
app.auth is absent |
A static per-role bearer token, at least 32 characters. |
oauth2 |
app.auth is configured |
OAuth 2.1 against Better Auth's OAuth-server plugin. |
MCP_AUTH_STRATEGY=token
MCP_TOKEN_ADMIN=$(openssl rand -hex 32)
MCP_TOKEN_MEMBER=$(openssl rand -hex 32)
MCP_TOKEN_VIEWER=$(openssl rand -hex 32)
Startup validation rejects token with no MCP_TOKEN_* set and oauth2 with no app.auth, so neither misconfiguration can result in a mounted route that nobody can reach — or worse, one that everybody can.
A role token is a password with no expiry and no owner. In token mode the credential grants a role, not a user: it cannot be attributed to a person, and it is valid until you change the env var and restart. Issue only the tokens you need — an app with no admin automation should not set MCP_TOKEN_ADMIN at all — and prefer oauth2 whenever app.auth exists, because it gives you per-user identity and revocation.
RBAC Is the Ceiling
The connecting credential's role bounds everything downstream. A viewer token can only read and list, even against a table whose aiAccess.operations allows writes — aiAccess widens what is offered, never what is permitted.
| Layer | Answers |
|---|---|
aiAccess on the entity |
Is this eligible to appear as a tool at all? |
MCP_ENABLED |
Is the server running? |
| Role permissions | May this actor perform this operation? |
| Field-level permissions | Which columns appear in the schema and the result? |
| Row-level rules | Which records are visible? |
All five must pass. The practical consequence is that you cannot accidentally over-expose a table by writing aiAccess: true on it — the worst case is that a role sees exactly what it could already have fetched over the API.
Audit
With MCP_AUDIT_ENABLED (default true), every tool call is recorded to system.ai_tool_calls and to the activity stream. Admins can read that table back through MCP itself as {app}_system_ai_tool_calls_list.
Turning auditing off is permitted for compliance edge cases and is a bad default. An AI actor is precisely the one whose calls you will later want to reconstruct.
Rate Limiting
| Variable | Default | Scope |
|---|---|---|
MCP_RATE_LIMIT_PER_MINUTE |
60 |
Per token, or per OAuth client_id. |
MCP_RATE_LIMIT_PER_DAY |
5000 |
Same. |
Over-limit requests answer 429 with standard rate-limit headers. The per-day ceiling is the one that matters most: a model in a retry loop can burn a minute's budget and keep going, but it cannot quietly run all night against your database.
Admin Internals
MCP_EXPOSE_INTERNALS defaults to true, giving the admin role read-only tools over the auth and system tables — {app}_auth_*_read, {app}_system_*_list and so on — with secret columns denylisted.
This is what makes "which users signed up this week?" answerable without a database client. Set it to false to remove those tools from tools/list entirely, including for admins; do that when the MCP surface is meant for business data only and platform internals are out of scope for whoever is connecting.
Related Pages
- MCP Overview — the
MCP_*variables in context. - Server Mode —
aiAccessand what it does not do. - Connecting a Client — sending the credential.
- Roles & RBAC — the permissions being enforced.
- Activity Monitoring — where tool calls surface.
- OAuth Server — the
oauth2strategy's backing plugin.
Last updated July 27, 2026
This documentation was written with AI, so errors or outdated content are possible. Sovrium is in beta — contributions and corrections are welcome.