engine.yaml: authentication and the MCP
Every key of engine.yaml's auth section (local staff accounts, OpenID Connect single sign-on, staff and member sessions, member sign-in) and its mcp section (the remote content MCP endpoint, its clients, tokens and limits), with type, default and meaning.
Staff and members are two separate realms with their own sign-in and sessions. Staff sign in to the admin with a local account (password plus a second factor) or through your identity provider over OpenID Connect; members (engine-dmo) sign in to the portal with a magic link or a passkey. The mcp section switches on the content MCP endpoint that lets a person connect an AI agent to the admin.
auth.staff.local
auth:
staff:
local:
enabled: true
require_mfa: true
breach_check: local | Key | Type | Default | Meaning |
|---|---|---|---|
auth.staff.local.enabled | boolean | true | Staff may sign in with an email address and password |
auth.staff.local.allowlist | string | Empty | Comma-separated email addresses. When set, only these may sign in with a local account |
auth.staff.local.require_mfa | boolean | true | Every local account must enrol a second factor. false is refused in production |
auth.staff.local.password_min_length | integer | 12 | The shortest password accepted. At least 12 |
auth.staff.local.breach_check | string | local | local checks a new password against the breached-password filter shipped in the binary, with no network request; hibp checks that, then the Have I Been Pwned range API; off checks nothing (a warning in production) |
auth.staff.local.hibp_url | string | Empty | The range endpoint for hibp, to which the SHA-1 prefix is appended, such as a mirror behind an egress proxy. Empty is the public service. https (or http outside production) |
auth.staff.local.argon2.memory_kb | integer | 65536 | Argon2id memory per hash, in KiB. At least 19456 |
auth.staff.local.argon2.time | integer | 3 | Argon2id iterations. At least 2 |
auth.staff.local.argon2.threads | integer | 2 | Argon2id parallelism. At least 1 |
engine-dmo doctor reports the measured time of one password hash with these parameters.
auth.staff.oidc
Single sign-on through your identity provider (Microsoft Entra ID, Google Workspace, Okta, Keycloak, Auth0 and any other OpenID Connect provider). A user’s groups in the provider map to Meridian staff groups.
auth:
staff:
oidc:
enabled: true
issuer: https://login.microsoftonline.com/<tenant id>/v2.0
client_id: ${OIDC_CLIENT_ID}
client_secret: ${OIDC_CLIENT_SECRET}
group_claim: groups
group_map:
"a1b2c3d4-web-editors": editors
"e5f6a7b8-marketing": [editors, analysts] | Key | Type | Default | Meaning |
|---|---|---|---|
auth.staff.oidc.enabled | boolean | false | Offer sign-in through the provider. Needs issuer and client_id |
auth.staff.oidc.issuer | string | Empty | The provider’s issuer URL; its discovery document is read from it |
auth.staff.oidc.client_id | string | Empty | The client registered with the provider |
auth.staff.oidc.client_secret | secret | Empty | Its secret |
auth.staff.oidc.scopes | list of strings | [openid, profile, email] | The scopes requested |
auth.staff.oidc.group_claim | string | groups | The ID token claim that lists the user’s groups |
auth.staff.oidc.group_map | map | {} | Provider group value to one Meridian group key, or a list of keys |
auth.staff.oidc.unmatched_groups | string | ignore | A provider group not in group_map: ignore or warn (logged) |
auth.staff.oidc.no_mapped_group | string | refuse | A user none of whose groups is mapped: refuse the sign-in, or provision them with provision.groups alone |
auth.staff.oidc.auto_provision | boolean | true | Create a staff user on first sign-in |
auth.staff.oidc.link_existing_by_email | string | verified_only | Link a first sign-in to an existing staff user with the same email: verified_only when the provider says the email is verified, or never |
auth.staff.oidc.provision.display_name_claim | string | name | The claim a new user’s display name is read from |
auth.staff.oidc.provision.locale_claim | string | locale | The claim their admin language is read from |
auth.staff.oidc.provision.groups | list of strings | [] | Groups every provisioned user joins |
auth.staff.oidc.deprovision_on | string | absent | Group memberships always follow the provider at each sign-in, and a change ends the user’s other sessions. absent: a user who signs in with no mapped group at all, and holds no admin membership, is suspended (a later sign-in with a mapped group restores them); never: not suspended |
auth.staff.oidc.required_acr | string | Empty | An authentication context class the provider must assert (sent as acr_values and checked in the token), to require the provider’s own MFA |
auth.staff.session
| Key | Type | Default | Meaning |
|---|---|---|---|
auth.staff.session.absolute_lifetime | duration | 12h | The longest a staff session lives |
auth.staff.session.idle_timeout | duration | 2h | A session unused for this long ends. Positive and not longer than absolute_lifetime |
auth.member
Members are the people behind partner organisations, signing in to the member portal (engine-dmo).
| Key | Type | Default | Meaning |
|---|---|---|---|
auth.member.enabled | boolean | true | Members may sign in |
auth.member.magic_link.enabled | boolean | true | Sign in with a single-use link sent by email |
auth.member.magic_link.ttl | duration | 15m | How long a link works |
auth.member.passkeys.enabled | boolean | true | Members may add and use passkeys |
auth.member.password.enabled | boolean | false | Member passwords are not built; true is refused |
auth.member.registration | string | claim | How a member relationship starts: claim (staff invitations and the public claim form) or invite_only (staff invitations alone). open is refused |
auth.member.session.absolute_lifetime | duration | 720h | The longest a member session lives (30 days) |
auth.member.session.idle_timeout | duration | 336h | A member session unused for this long ends (14 days) |
mcp
The content MCP endpoint lets a staff member connect an AI agent (a Model Context Protocol client) to the admin, acting as themselves with their own permissions, after a consent screen. It is off unless mcp.enabled is true; off, there is no endpoint, no metadata and no admin screen for it. It is served on the admin’s origin at <admin_path>/mcp. engine-dmo mcp over standard input and output, for a local agent in development, does not read this section except personal_data.
mcp:
enabled: true
personal_data: refuse
clients:
- id: acme-agents
name: Acme agent platform
redirect_uris: ["https://agents.acme.example/callback"] | Key | Type | Default | Meaning |
|---|---|---|---|
mcp.enabled | boolean | false | Serve the endpoint and its OAuth authorisation server |
mcp.registration.metadata_documents | boolean | true | Accept clients that identify themselves by a client ID metadata document (a URL client_id) |
mcp.registration.dynamic | boolean | false | Accept dynamic client registration |
mcp.clients | list | [] | Pre-registered public clients using PKCE |
mcp.clients[].id | string | Required | The client_id the client sends, such as acme-agents. Not a URL; unique |
mcp.clients[].name | string | Required | The name the consent screen shows |
mcp.clients[].redirect_uris | list of strings | Required | https URLs, or http on a loopback address, with no fragment |
mcp.tokens.access_ttl | duration | 1h | An access token’s life, 5m to 24h. Every call looks the token up, so revocation is immediate |
mcp.tokens.refresh_idle | retention | 30d | How long a refresh token may go unused, 1d to 180d |
mcp.tokens.connection_max | retention | 90d | The longest a connection or a personal access token lives, 1d to 180d |
mcp.personal_data | string | refuse | refuse keeps CRM personal data away from agents; allow lets a person choose the toolsets that return it on the consent screen |
mcp.media.fetch | boolean | true | Allow uploading media by URL; off refuses every upload by URL |
mcp.media.fetch_hosts | list of strings | [] | When set, only these hosts (and their subdomains) may be fetched from |
mcp.limits.calls_per_minute | integer | 600 | A connection’s weighted calls a minute (a read weighs 1, a search 2, a content check 3, a write 5, a bulk plan 1 an item), counted per replica. At most 1200 |
mcp.limits.writes_per_minute | integer | 60 | A person’s writes a minute across all their connections. At most 120 |
mcp.limits.writes_per_day | integer | 2000 | A person’s writes a day. At most 5000 |
mcp.limits.bulk_items | integer | 200 | The most operations one bulk plan carries. At most 500 |
mcp.limits.media_by_url_per_hour | integer | 20 | A connection’s uploads by URL an hour. At most 100 |
A limit left out (or 0) takes its default. See The content MCP.
engine.yaml
Every key of config/engine.yaml for the server, database, migrations, schema, templates, public files, logging, caches, storage, jobs, telemetry, retention, audit and extensions, with type, default and meaning; other sections are on their own pages.
engine.yaml: content, media and search
Every key of engine.yaml's content, media, events, tags, approvals, endpoints, forms, search, maps, admin and notifications sections, with type, default and meaning.