config.toml inside its platform-specific local state directory.
Managed state
Coral writes source installation state toconfig.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 namedcoral.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.
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 inconfig.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 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-runningcoral 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.
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:
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:
[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.
[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.
[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:
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].
config.toml but ignored by Coral.
OpenTelemetry
External OpenTelemetry export is configured under[otel].
[trace_history].