Remote access — sign-in & identity modes¶
When more than one person uses your server, you need real sign-in. MCP clients authenticate to a remote server with OAuth 2.1 + PKCE and discover the sign-in endpoint via RFC 9728, so you need an OIDC authorization server in front of Précis-MCP.
You pick the sign-in posture at install with one variable, PRECIS_AUTH_MODE:
| Mode | PRECIS_AUTH_MODE |
Sign-in | When |
|---|---|---|---|
| A | devkey |
One static key, localhost only | Trial / demo / your own machine — see the Quickstart. Single-user. |
| B | keycloak |
The bundled Keycloak (optionally federated to your IdP) | You want a ready-to-run sign-in provider. The default multi-user path. |
| C | oidc |
Your own OIDC IdP (Auth0, Okta, Entra, Ping, …), trusted directly | You already run an IdP and want no extra moving parts. |
The modes differ only in which issuer signs the token. The token check, the per-user permission model, and provisioning are identical across all three.
The dev-key server is a separate entrypoint — not part of B/C
Mode A is a distinct, localhost-only process (python -m precis_mcp.server),
enabled by ENABLE_MCP_DEV_SERVER=1 and off by default. The multi-user
modes B/C run a different entrypoint (precis_mcp.app_open, the OAuth-gated
/mcp server) and the production bundle doesn't include the dev server.
PRECIS_AUTH_MODE is enforced on both entrypoints: if you set
PRECIS_AUTH_MODE=keycloak or oidc, the dev server refuses to start even
if ENABLE_MCP_DEV_SERVER=1 — so a B/C-configured host can't accidentally
run a no-auth server. (And app_open likewise refuses devkey.)
Mode B — bundled Keycloak (PRECIS_AUTH_MODE=keycloak)¶
Précis-MCP ships a ready-to-run Keycloak in the bundled docker-compose, with a
committed realm (sign-in client, the precis_user_id user mapper, and the
audience mapper for /mcp) plus a per-deploy reconcile. You don't configure OIDC
by hand.
| Variable | Purpose | Default |
|---|---|---|
PRECIS_AUTH_MODE |
keycloak |
— |
PRECIS_BASE_URL |
Public address of your server; the other URLs derive from it | — |
KC_BASE_URL_INTERNAL |
Backend → Keycloak (container topology) | http://localhost:8080/auth |
KC_BOOTSTRAP_ADMIN_PASSWORD |
Keycloak realm-admin password | — |
KC_REALM / KC_CLIENT_ID |
Realm + client | precis / precis-spa |
KC_MCP_AUDIENCE |
Expected token audience on /mcp |
derived from PRECIS_BASE_URL |
KC_ENABLE_EXCEL_ADDIN |
Provision and advertise the hosted Excel add-in public client | unset |
KC_ADDIN_REDIRECT_URIS |
Optional override for the add-in callback URI | derived from PRECIS_BASE_URL |
Bring it up:
docker compose -f deploy/docker-compose.yml up -d
This pulls the pinned published image (ghcr.io/precis-finance/precis-mcp,
selected by PRECIS_MCP_TAG) and starts your data store, the bundled Keycloak
with its seeded realm, and the server. Pulling a pinned release is the default;
docker compose … up -d --build builds from source instead (rolling main /
forks). From a workstation, scripts/deploy-mcp.sh does the same, pull-first —
see the production checklist. To let your users sign in with your corporate IdP, point Keycloak's
identity-brokering at it (OIDC or SAML) — Keycloak stays the issuer and the
above doesn't change; the walkthrough is
Sign in with your corporate IdP. This is also the
path for claude.ai / ChatGPT: their connectors self-register (DCR), which
the bundled Keycloak supports.
To enable the hosted Excel add-in in mode B, set KC_ENABLE_EXCEL_ADDIN=true
before the Keycloak reconcile runs. The default callback is
https://<your-host>/excel/auth-callback.html; set KC_ADDIN_REDIRECT_URIS
only when your ingress needs a non-standard public origin. The add-in bundle is
baked into the published image and served at /excel; see
Précis for Excel.
Mode C — your own OIDC IdP (PRECIS_AUTH_MODE=oidc)¶
No Keycloak runs. The verifier points straight at your IdP. Register a client in your IdP and tell Précis-MCP about the issuer:
| Variable | Purpose |
|---|---|
PRECIS_AUTH_MODE |
oidc |
OIDC_ISSUER |
Your IdP's issuer URL (used verbatim — keep any trailing slash) |
OIDC_JWKS_URL |
JWKS endpoint for the issuer. Required in mode C. |
OIDC_AUDIENCE |
The audience your IdP stamps for the /mcp resource |
OIDC_CLIENT_ID / OIDC_CLIENT_SECRET |
Your pre-registered client (secret only if confidential) |
PRECIS_IDENTITY_CLAIM |
Which token claim carries identity (default precis_user_id) |
PRECIS_IDENTITY_COLUMN |
id (the claim value is the user id) or external_id (look it up) |
EXCEL_ADDIN_CLIENT_ID |
Optional public PKCE client_id for the hosted Excel add-in |
EXCEL_ADDIN_SCOPE / EXCEL_ADDIN_RESOURCE_INDICATOR |
Optional token-request shape for IdPs such as Entra/Okta |
Two things to know:
- Pre-registered client, not DCR. Most enterprise IdPs disallow dynamic
client registration, so you register the client yourself and set
OIDC_CLIENT_ID. Modern MCP clients (Claude Code, ChatGPT, a first-party frontend) then sign in via discovery. claude.ai/ChatGPT's public connectors need DCR — if your IdP doesn't offer it, use mode B (Keycloak brokering). - Stable identity claim. Map
PRECIS_IDENTITY_CLAIMto a stable, unique claim (e.g. an immutable subject /oid), not a mutable email. If it differs from your Précis-MCP user ids, setPRECIS_IDENTITY_COLUMN=external_idand store the IdP value withcreate-user --external-id(below). - Excel add-in uses a separate public client. If you enable the hosted Excel
add-in in mode C, register a public PKCE client in your IdP with callback
https://<your-host>/excel/auth-callback.html, then setEXCEL_ADDIN_CLIENT_IDon the/mcphost. Entra/Okta usually also needEXCEL_ADDIN_SCOPEandEXCEL_ADDIN_RESOURCE_INDICATOR=false; see External IdP recipes.
Create the first admin and provision users¶
Being signed in by the IdP grants nothing — each user must also exist in
Précis-MCP with a profile, and the UI can't bootstrap itself. The simplest path
is to let the deploy create it — pass --admin-id on your deploy-mcp.sh run
(or set PRECIS_BOOTSTRAP_ADMIN_ID in deploy/.env):
bash scripts/deploy-mcp.sh --admin-id alice@example.com
The deploy's first-admin step creates that admin — idempotently, so it's safe on
every run — and prints a one-time temporary password (change it on first login)
in the deploy output. If you already deployed with --admin-id, it's done; the
manual steps below are only for adding admins later, or if you didn't set it.
To do it by hand, run the admin CLI in the precis-mcp container. One
wrinkle in bundled-Keycloak (mode B): create-admin, create-user, and
reset-password provision a Keycloak account, so they need the Keycloak
bootstrap-admin password — which the long-running server deliberately does not
carry. Inject it for that one call (it lives in deploy/.env as
KEYCLOAK_ADMIN_PASSWORD) by using run rather than exec:
docker compose -f deploy/docker-compose.yml --env-file deploy/.env run --rm \
-e KC_BOOTSTRAP_ADMIN_PASSWORD="$(grep '^KEYCLOAK_ADMIN_PASSWORD=' deploy/.env | cut -d= -f2-)" \
precis-mcp python -m precis_mcp.admin_cli create-admin --id alice@example.com
# mode C (external IdP — no Keycloak account, the IdP owns the credential):
docker compose -f deploy/docker-compose.yml exec precis-mcp \
python -m precis_mcp.admin_cli create-admin --id alice --no-keycloak --external-id <idp-subject>
Then provision and grant access. create-user provisions Keycloak too, so it
takes the same injected credential as above; profile and assignment commands
touch only the platform DB, so a plain exec into the running server works:
docker compose -f deploy/docker-compose.yml exec precis-mcp \
python -m precis_mcp.admin_cli profile create --file analyst.yml
docker compose -f deploy/docker-compose.yml exec precis-mcp \
python -m precis_mcp.admin_cli assign --user bob --profile analyst
A user with no profile authenticates but can read nothing — assign a profile to grant access. What a profile contains — scenario patterns, roles, domain and dimension scopes, with worked examples — is documented in User profiles & permissions.
Verify¶
Check the issuer/JWKS/audience config before go-live:
python -m precis_mcp.admin_cli check-auth # static + reachability
python -m precis_mcp.admin_cli check-auth --no-fetch # static only
You can also have the server fail fast at startup on a misconfig by setting
PRECIS_AUTH_PREFLIGHT=1. Then confirm end-to-end: a client completes sign-in,
a query succeeds, and a token with the wrong /mcp audience is rejected.
Connect a client¶
A modern MCP client discovers the sign-in endpoint from your server and runs the
OAuth flow itself — you give it the server URL (and, for mode C, the
pre-registered client_id if the client needs it configured):
{
"mcpServers": {
"precis": { "url": "https://<your-host>/mcp" }
}
}