Configuration reference
config.yaml is deliberately minimal: bootstrap only. It says how to
find and secure Runku's own admin database, which target databases to
protect, and how to verify OIDC tokens. It does not say which tables
are exposed, what's masked, what writes are allowed, or who's allowed to
do what — that all lives in Runku's own admin database (see
auth-and-permissions.md and
policy.md), managed live via the runku auth/runku policy CLI, never by hand-editing this file and redeploying.
Parsing is strict: unknown fields are a hard error at startup, not a
silently-ignored typo. ${VAR} anywhere in the file is expanded from
the environment before parsing.
Minimal example
# HTTP listener shared by the portal and MCP endpoints.
server:
# TCP port exposed by the runku process.
port: 8080
# Private Runku state: policy, identities, roles and API-key hashes.
# This database is never published as a connector.
store:
# PostgreSQL is recommended for production and multiple instances.
driver: postgres
# Dedicated admin-store DSN, injected instead of committed to this file.
dsn: ${RUNKU_MCP_STORE_DSN}
# External sources published through governed MCP endpoints.
connectors:
# Stable identifier for this independently governed source.
- id: main
database:
# External source engine: postgres, mysql or sqlserver.
driver: postgres
# Source credentials; deliberately separate from RUNKU_MCP_STORE_DSN.
dsn: ${DATABASE_URL}store and connectors are different boundaries
They may both use PostgreSQL, but they must not be treated as the same database or credential:
| Configuration | Purpose | Typical contents | Accessed by agents |
|---|---|---|---|
store |
Runku's private control state | Policies, roles, identities, API-key hashes and connector metadata | Never |
connectors[] |
External sources governed by Runku | Your application or business data | Only through generated tools allowed by policy |
flowchart LR Operator[Operator] -->|manages policy and identities| Store[(Runku admin store)] Agent[MCP client] -->|typed tool call| Runku[Runku MCP Forge] Store -->|policy and authorization| Runku Runku -->|validated, parameterized operation| Source[(Connected source)] Source -->|allowed result| Runku Runku -->|masked result| Agent
The admin store belongs to the Runku deployment. A connector belongs to the operator and represents a source Runku protects. Use a dedicated database and credentials for the PostgreSQL admin store, even when a connected source also uses PostgreSQL.
server
| Field | Type | Default | Notes |
|---|---|---|---|
port |
int | 8080 |
|
tls |
object, optional | disabled | see below |
# Listener and optional transport encryption.
server:
# TCP port used by the service.
port: 8080
tls:
# Serve HTTPS directly. Disable when a trusted reverse proxy terminates TLS.
enabled: true
# Mounted PEM certificate chain and matching private key.
cert_file: /etc/runku/tls/cert.pem
key_file: /etc/runku/tls/key.pemtls.enabled: true switches the listener to ListenAndServeTLS —
cert_file/key_file are then required. Without it, Runku serves plain
HTTP, same as every version before TLS support existed — the common case
being a reverse proxy in front of it that already terminates TLS.
store
Runku's own admin database — roles, identities, API key hashes, role bindings, and each connector's policy. Never one of the databases Runku protects; a completely separate engine and connection.
| Field | Type | Notes |
|---|---|---|
driver |
string | "sqlite" or "postgres"; both are implemented. |
path |
string | SQLite only. Required when driver: sqlite; forbidden otherwise. |
dsn |
string | Postgres only. Required when driver: postgres; forbidden otherwise. |
PostgreSQL is the recommended production backend and supports shared or multi-instance deployments. Keep its DSN in the environment or a secrets manager:
# Dedicated database for Runku's internal operational state.
store:
# Recommended for production, HA and multi-instance deployments.
driver: postgres
# Secret DSN for the admin database; not a connector DSN.
dsn: ${RUNKU_MCP_STORE_DSN}SQLite is fully supported for evaluation, local operation and small single-instance deployments:
# Simple single-instance admin store for evaluation or small installations.
store:
# Embedded storage; no external database server is required.
driver: sqlite
# Persistent file containing Runku policy, roles and identities.
path: data/runku.dbExactly one engine, never a hybrid — store is unrelated to
connectors[].database, which is the database(s) being protected.
connectors
At least one required. Each connector is one target database, exposed on
its own MCP endpoint — a single runku serve process can serve several
simultaneously.
| Field | Type | Default | Notes |
|---|---|---|---|
id |
string | "default" for the first connector if omitted |
Must be unique. |
path |
string | /mcp/<id> |
Must start with /; must be unique. |
database.driver |
string | — | postgres | mysql | sqlserver |
database.dsn |
string | — | Required |
# Each connector is an external source with an independent MCP URL and policy.
connectors:
# First source: a PostgreSQL business database.
- id: postgres-main
# Explicit MCP route; defaults to /mcp/<id> when omitted.
path: /mcp/postgres-main
database:
# Driver used to introspect and query this external source.
driver: postgres
# Credentials belong to the source, not Runku's admin store.
dsn: ${DATABASE_URL}
# Second source: a separate MySQL business database.
- id: mysql-secondary
database:
driver: mysql
dsn: ${MYSQL_DSN}DSN formats per engine:
- postgres:
postgres://user:pass@host:5432/db?sslmode=disable - mysql:
user:pass@tcp(host:3306)/db?parseTime=true&multiStatements=true - sqlserver:
sqlserver://user:pass@host:1433?database=db&encrypt=disable
What each connector actually exposes (tables, columns, mutations, procedures) is not configured here — see policy.md.
audit
Local audit buffering, optional outbound shipping, optional rotated file logging. The stdout audit line (one JSON object per tool call) is always on, unconditionally, regardless of anything in this section — this section only adds sinks on top of it.
| Field | Type | Default | Notes |
|---|---|---|---|
sqlite_path |
string | disabled if empty | Local SQLite buffer — powers /reports/* and the portal. |
shipper |
object | disabled | See below. |
runtime_metrics |
object | disabled | Same shape as shipper, a separate channel for runtime/tool-analysis metrics. |
log_file |
object, optional | disabled | Writes the same trail to a rotated file, in addition to stdout. |
# Optional audit sinks in addition to the always-enabled stdout audit stream.
audit:
# Local outbox and portal/reporting buffer; separate from the admin store.
sqlite_path: data/runku-audit.db
shipper:
# HTTPS receiver for batched audit events.
endpoint: ${RUNKU_SHIPPER_ENDPOINT}
# Delivery cadence and maximum events per request.
interval: 30s
batch_size: 200
headers:
# Optional static authentication header.
X-Api-Key: ${SHIPPER_API_KEY}
oauth2:
# Optional OAuth2 client-credentials authentication for the receiver.
token_url: https://idp.example.com/oauth2/token
client_id: ${SHIPPER_CLIENT_ID}
client_secret: ${SHIPPER_CLIENT_SECRET}
scopes: [audit:write]
log_file:
# Optional rotated local JSON audit file.
path: data/runku-audit.log
# Rotation size, retained files, age and compression behavior.
max_size_mb: 100
max_backups: 5
max_age_days: 30
compress: falseshipper is a plain webhook POST — it has no idea any particular
backend exists; point it at whatever ingests JSON over HTTPS. Entries
are only deleted from the local buffer after a confirmed successful
delivery (an outbox pattern — a failing shipper never loses data, it
just accumulates locally). headers are static, sent on every request.
oauth2 is the standard client-credentials grant (RFC 6749 §4.4) —
token fetch, caching and refresh are handled entirely by the underlying
OAuth2 library. Both may be combined; both are optional; with neither
set, the shipper POSTs unauthenticated.
Leaving shipper.endpoint empty disables shipping. Entries remain in the local SQLite buffer for reports and the portal until retention removes them.
auth
Authenticates every request to /mcp and /reports/* — who's calling,
verified cryptographically, not self-reported. Disabled by default.
| Field | Type | Default | Notes |
|---|---|---|---|
enabled |
bool | false |
|
oidc |
object, optional | unset | Only how to verify incoming tokens — issuing/storing credentials is never Runku's job. |
# Request authentication. Authorization remains in roles/policy in the store.
auth:
# Require a valid API key or verified OIDC bearer token.
enabled: true
oidc:
# Trusted issuer discovered through its OIDC metadata endpoint.
issuer: https://idp.example.com/realms/runku
# Audience that incoming access tokens must contain.
audience: runku-mcp
# Public PKCE client used only by browser portal login.
portal_client_id: runku-portalissuer/audience— standard OIDC discovery (/.well-known/openid-configuration) + JWKS verification.audiencemust match a claim your provider actually issues (with Keycloak, this means the client needs an audience protocol mapper).portal_client_id, optional — a public OIDC client (Authorization Code + PKCE, no secret) the browser-based portal uses for its "log in with SSO" button. Distinct fromaudience, which is only ever used to verify tokens on incoming requests. Unset just disables that button — the token-paste login path still works regardless.
API keys are not configured here — see
auth-and-permissions.md; they live in the
admin store and are managed with runku auth apikey.
Configuration checklist
Before starting the service, validate the listen address, persistent store path or DSN, connector DSNs, audit retention, TLS and authentication settings. Unknown fields fail startup so configuration mistakes are visible immediately.