Skip to main content
View as Markdown

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.

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.

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.

Built with Sovrium