
# 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.    |

```bash
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.

:::callout
**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](/en/docs/activity-monitoring). 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](/en/docs/mcp-integration) — the `MCP_*` variables in context.
- [Server Mode](/en/docs/mcp-server) — `aiAccess` and what it does not do.
- [Connecting a Client](/en/docs/mcp-clients) — sending the credential.
- [Roles & RBAC](/en/docs/auth-roles-rbac) — the permissions being enforced.
- [Activity Monitoring](/en/docs/activity-monitoring) — where tool calls surface.
- [OAuth Server](/en/docs/auth-oauth-server) — the `oauth2` strategy's backing plugin.
