Skip to main content
Coral stores local configuration in config.toml inside its platform-specific local state directory.

Managed state

Coral writes source installation state to config.toml when you run commands such as coral source add and coral source remove. This state includes installed-source metadata and non-secret source variables. Source secrets are stored separately within the same local trust boundary.
Prefer the coral source commands for source state instead of editing workspace entries manually.

Database

Coral stores durable app state in a local SQLite database named coral.db inside the local state directory by default. You do not need a [database] section for the default setup. Use [database] to choose an explicit backend. Relative SQLite paths resolve from the local state directory.
For Postgres, store the connection URL in an environment variable and reference the variable name from config.toml.
Remote Postgres URLs must set sslmode=verify-full so Coral verifies the server identity. Loopback and local-socket Postgres URLs may omit TLS for local development.

Workspaces

Which workspaces exist is recorded in Coral’s database, not in config.toml. There is no automatically created workspace: a fresh install has none until you run coral workspace create NAME, and Coral never falls back to a name of its own. default is an ordinary name you may create, remove, and select like any other. What config.toml holds is the per-workspace installation state for the things you install into a workspace — sources and functions:
Coral writes these tables when you run commands such as coral source add. Creating an empty workspace writes no [workspaces.<name>] scaffolding, so a workspace with nothing installed in it appears in coral workspace list without appearing in config.toml. There is no active-workspace key in config.toml; use CORAL_WORKSPACE for a shell default or --workspace <NAME> for one command, as described in Global workspace selection. Source specs imported from files and source credential material remain scoped to the source installation in a workspace. Reusable global source specs and reusable credential references are not config objects today.

Server

The long-running coral server command reads its native gRPC bind address from [server]:
bind_addr must be a literal IP address and port, not a hostname. Without a [server] section, Coral binds an ephemeral port on 127.0.0.1.
Without [auth] configured, the native gRPC server does not authenticate clients: a non-loopback bind exposes Coral and its configured sources to any client that can reach the port. With [auth] configured, gRPC requests require a bearer access token — only the gRPC health service stays unauthenticated, so orchestrator probes keep working — but the listener still serves cleartext h2c, so those tokens can be read off the wire. Either way, protect a non-loopback bind with a trusted network boundary and terminate TLS in front of Coral.
Coral terminates no TLS on any listener. For any deployment that leaves loopback, run a TLS-terminating reverse proxy in front of Coral. See Self-host Coral with Docker and Deploy Coral on Kubernetes. MCP Streamable HTTP is disabled by default. Enable its companion listener under [server.mcp_http]:
Every workspace is served at its own MCP URL on this listener, /mcp/workspace/{workspace} — for example /mcp/workspace/analytics — next to GET /livez and GET /readyz health routes. There is no single-workspace endpoint and no workspace key: the URL a client connects to names the workspace its sessions reach, a workspace is reachable as soon as it exists, and the retired /mcp path answers not-found. Without [auth], the listener is unauthenticated, public_url must stay unset, and the listener is restricted to loopback addresses by default. To serve it on other interfaces — necessary inside a Docker container, where a loopback bind is unreachable through published ports — opt in explicitly:
The opted-in listener performs no authentication: anything that can reach the port can read every configured source with the local user’s full authority. Keep it reachable only from trusted machines. In Docker, publish the port to loopback only (-p 127.0.0.1:14556:14556 — a bare -p 14556:14556 exposes it on every host interface), or keep it unpublished on an internal network. For anything beyond a single trusted machine, configure Coral’s authenticated serving mode (the [auth] section) instead.
The listener accepts requests whose Host header names localhost, 127.0.0.1, ::1, or the bind IP; other values are rejected to block DNS-rebinding attacks. When clients reach the listener under another name — for example a Docker Compose service name or the machine’s LAN address — list the extra names explicitly:
With [auth], bind may be any address and an enabled listener must set public_url; it is the public base the per-workspace URLs hang under, for example https://mcp.example.com/mcp serving each workspace at https://mcp.example.com/mcp/workspace/{workspace}. Each such workspace URL — never the base itself — is its own OAuth resource identifier and token audience, with its own protected-resource metadata document, so a token minted for one workspace’s URL is invalid at every other’s. The listener mounts the family under the base’s path, so keep that path consistent with what the ingress forwards. Both opt-in keys are rejected when [auth] is configured: remote binds then need no opt-in, and accepted hosts derive from public_url, the bind IP, plus the loopback names.

Authentication

Authentication is off by default: without an [auth] section, coral server runs in single-user local mode and does not authenticate callers. Adding [auth] turns the server into an OAuth 2.1 authorization server that delegates login to one upstream OIDC identity provider and mints its own bearer access tokens for Coral UI and MCP clients.
Configuring [auth] requires at least one public surface: an enabled [server.mcp_http] with public_url, a non-empty allowed_audiences, or both. Startup fails otherwise.
Coral’s authorization server, gRPC listener, and MCP listener all serve cleartext. Bearer tokens cross the wire unprotected unless a TLS-terminating reverse proxy fronts every non-loopback listener. The configured issuer, public_url, and audience URLs must match the external TLS hostnames exactly.

[auth]

[auth.session]

The [auth.session] table must be present whenever [auth] is configured — a bare [auth.session] header is enough to accept every default. Generate a signing key with:
Export the output as CORAL_SESSION_SIGNING_KEY (or store the DER bytes in a file referenced by signing_key_file). PEM keys are not accepted.

[auth.authorization_server]

Every sign-in normally pauses on Coral’s approval page before redirecting to the identity provider. List a first-party client you operate yourself — for example a deployed Coral UI’s <public URL>/.well-known/oauth-client — in trusted_clients to send its sign-ins straight to the provider. Trust removes only the approval step; client metadata resolution, redirect-URI registration, PKCE, and resource validation are unchanged. Avoid listing clients with loopback redirect URIs: any local process can claim a loopback port, and the approval page is the only step that shows which port receives the authorization code. The authorization server serves GET /.well-known/oauth-authorization-server, GET/POST /oauth/authorize, POST /oauth/token, and GET /auth/oidc/callback on the http_bind_addr listener. The reverse proxy must forward the issuer origin to it at the root.

[auth.provider]

The single upstream OIDC provider Coral delegates login to. Who can sign in is decided entirely by this provider and required_claims; Coral keeps its own record of every user who has signed in, and its own per-workspace memberships, but delegates the sign-in decision itself.
Signing in grants no workspace access by itself. Access is granted per workspace, at Owner or Member level, through workspace memberships — see Shared server access control for what each role may do. The provider and required_claims decide who can sign in at all; membership decides what a signed-in user reaches.

MCP HTTP workspace URLs

Nothing under [server.mcp_http] selects a workspace: every workspace is served at its own URL, /mcp/workspace/{workspace} under the listener (and under public_url when [auth] is configured). A workspace key is rejected at startup — earlier releases used it to bind the listener to one workspace, and a value that silently stopped meaning that would be worse than an error. Whether a URL serves is a per-handshake question, never a startup one. The server starts with zero workspaces, a workspace created while it runs is immediately reachable at its URL, and a deleted one immediately answers not-found to new sessions. Only URL-safe workspace names (ASCII letters and digits plus -, ., _, ~) are reachable over HTTP; a workspace named outside that set stays fully usable over gRPC and stdio but has no URL until it is recreated under a URL-safe name. Without [auth], requests act as the built-in local user with full access to every workspace URL. With [auth], a session is admitted only when the URL’s workspace is among the calling token’s own memberships — see Shared server access control for the admission and concealment contract.

Runtime features

Experimental runtime features are configured under [features].
You can inspect available features and their effective state with:
Enable or disable a feature with:
Feature values must be booleans. Unknown feature keys are preserved in config.toml but ignored by Coral.

OpenTelemetry

External OpenTelemetry export is configured under [otel].
Trace history settings are configured under [trace_history].
See Observe with OpenTelemetry for the full telemetry setup guide.