Authentication & Clients
MCP servers reuse RaisinDB's access control. A server is either open or gated by scopes, and clients authenticate either interactively (OAuth 2.1) or with a bearer token.
Public vs. scoped servers
public | scopes | Who can open the server |
|---|---|---|
true | — | Anyone, no credential. |
false | [] (empty) | Any authenticated caller. See the note below. |
false | [role_or_group, …] | Only callers holding all listed scopes. |
Each scope is a role or group id from raisin:access_control. Per-tool scopes are checked the same way (tool.scopes ⊆ caller's roles/groups), and a tool only appears in tools/list if the caller holds its scopes.
If anonymous access is enabled for the repository, an unauthenticated request resolves to the anonymous principal, which still satisfies a non-public server that declares no scopes. To restrict a non-public server to specific callers, declare scopes the anonymous role does not hold. (Data tools remain row-level-security scoped to the caller either way.)
Interactive clients: OAuth 2.1
RaisinDB runs a standard OAuth 2.1 authorization server, so MCP clients log in without any pasted tokens. The flow is fully automatic for the client:
Key properties:
- Discovery —
/.well-known/oauth-authorization-server(RFC 8414) and/.well-known/oauth-protected-resource/mcp/{repo}/{branch}/{slug}(RFC 9728). - Dynamic Client Registration —
POST /register(RFC 7591); no pre-provisioning. - PKCE S256 required (OAuth 2.1).
- Resource-bound tokens — the issued token's audience is the specific MCP endpoint (RFC 8707), so a token minted for one server is rejected at any other.
- Consent narrows, never widens — the granted scopes are the intersection of what the user requested and the roles/groups they actually hold.
The resource owner is authenticated against the existing identity store (the same login as everywhere else) — there is no separate MCP login.
Headless clients: bearer token
Non-interactive agents present a RaisinDB access token directly:
Authorization: Bearer <token>
This is the simplest path for first-party or server-to-server agents.
Connecting a client
Point any MCP client at the Streamable HTTP URL:
http://localhost:8080/mcp/{repo}/main/{slug}
Most MCP clients let you add an HTTP server by URL — for example an mcp add --transport http <name> <url> command, or an entry in the client's MCP config. Interactive clients trigger the OAuth login on first connect; headless clients send the bearer token.
Quick manual check
# Public server — no auth needed
curl -s http://localhost:8080/mcp/myapp/main/catalog \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
# Scoped server — present a token
curl -s http://localhost:8080/mcp/myapp/main/private \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
Self-hosting behind a proxy
The OAuth issuer and token audiences are derived per request from the host RaisinDB is reached on, so a multi-tenant deployment serving each org on its own host advertises the right issuer automatically. To stop a spoofed host from minting a token with an attacker-chosen audience, the derived host is validated against an allowlist.
Single fixed origin — set an absolute override (skips host derivation entirely):
RAISINDB_BASE_URL=https://db.example.com
Multi-host / wildcard subdomains — instead of a single base URL, allowlist trusted host suffixes (comma-separated). A suffix matches its apex and any subdomain (db.example.com matches db.example.com and acme.db.example.com, but not evil-db.example.com):
RAISINDB_TRUSTED_HOST_SUFFIXES=.db.example.com
A tenant's custom domains are allowlisted per tenant via the trusted_oauth_hosts field on its auth config (exact host match), in addition to the global suffixes.
Only set RAISINDB_TRUST_FORWARDED_HEADERS=1 when a trusted proxy sets X-Forwarded-* (they are ignored by default, since they are client-spoofable on a directly reachable server). When no allowlist is configured at all, the issuer falls back to the request host unvalidated (single-origin convenience) and a warning is logged.