Skip to main content
Version: 0.1.0

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

publicscopesWho can open the server
trueAnyone, 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.

Non-public servers and anonymous access

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 RegistrationPOST /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.