Choose a configuration and apply changes
This reference targets v2.4.0. Configuration is one YAML document with strict field checking. Do not paste management API JSON directly into YAML: YAML headers is a map; API headers is an object array. A managed backend's API enabled field is not a YAML backend field. HTTP tool groups and OpenAPI imports are managed only through the UI/API.
| Scenario | Starting point | Prerequisites |
|---|---|---|
| Default local console + SQLite | Default template, startup steps | 2 variables: MCP URL and encryption key; add backends in the UI |
| Advanced YAML-only deployment | Independent example | Fill each backend address and credential in YAML; explicitly publish reviewed read tools |
| Local administration, SQLite | Complete configuration, startup steps | MCP URL, persistent encryption key |
| Remote team administration | Deployment examples and variables | Admin URL, SQLite or PostgreSQL, HTTPS proxy; enterprise identity is optional |
| SSO / Vault personal accounts | SSO, Vault | Add the required modules to a managed deployment; Feishu also needs real-tenant acceptance |
| Configuration | Edit in | Takes effect through |
|---|---|---|
server.listen, server.public_url, all auth, admin, client_authorization, vault |
YAML / service environment | Restart; SIGHUP rejects changes to these fields |
Other server fields |
YAML | SIGHUP; see reload |
backends with admin disabled |
YAML | SIGHUP; at least one backend is required |
backends with admin enabled |
YAML for initial import, then UI/API | Imported once into an empty database; later YAML backends and their variables do not override stored records |
| HTTP tool groups, OpenAPI, user policies, client grant records | Admin UI / user portal / API | Save or complete required approval; never imported from YAML |
Strict YAML decoding still applies in managed mode: unknown fields in ignored backend entries remain errors. Do not delete the database to force a reimport; it also stores tool groups, grants and audit history.
Environment variables and secrets
${NAME} expands only in the fields below, not in every string. Values come from the MCPHub process environment. Exporting variables in another shell cannot change a running service's environment; update its service environment and restart for rotation. MCPHub does not automatically load .env.
| Expansion location | Fields |
|---|---|
server, auth |
server.listen, public_url, allowed_origins[]; auth.issuer |
admin |
listen, mode, public_url, client_id, client_secret_env, database_driver, database_dsn_env, database_path, encryption_key_env, required_scopes[] |
admin.approvals |
required_scopes[], step_up_acr_values[]; policy_changes.required_scopes[], policy_changes.subjects[]; notifications.url, notifications.secret; audit_archive.url, audit_archive.signing_key, audit_archive.key_id |
client_authorization |
client_id, client_secret_env |
YAML backends[] |
id, url, headers values, required_scopes[], published_tools[]; all oauth strings and scopes[] |
YAML backends[].tool_rules[] |
match, required_scopes[]; resource_rules[].argument, allowed_values[] |
auth.sso.*, vault.*, backends[].credentials.*, tool_rules[].approval.* and effect do not expand; supply literal settings. Durations, numbers and booleans do not accept placeholders. Names must match [A-Za-z_][A-Za-z0-9_]*; unset variables in supported fields fail validation. ${NAME:-default} and $NAME are not fallback/expansion syntax and may remain literal; do not use them.
A *_env field contains an environment variable name, not the secret:
# Fragment: place each setting in its respective section.
admin:
encryption_key_env: MCPHUB_CONFIG_KEY # Read the Base64-encoded 32-byte key from this variable.
# Backend credentials are configured separately for each service in the UI.
Do not write encryption_key_env: ${MCPHUB_CONFIG_KEY}: that treats the key value as a variable name. Generate the database encryption key once and retain it with backups. SSO and Vault likewise reference secrets through their *_env fields; inject values through the service manager or secret store.
required: false only tolerates connection failure. That backend's URL, OAuth fields and supported environment variables must still be valid and complete; it does not disable the configuration. Remove an unused backend entry from the active YAML.
server
| Field | Default | Description |
|---|---|---|
listen |
:8080 |
HTTP listen address. It cannot be changed by SIGHUP; restart to change it. |
public_url |
none | Required absolute HTTPS URL with an MCP path (for example, https://hub.example.com/mcp), with no query or fragment. The path may not contain percent-encoded characters and may not be /healthz, /readyz, or /.well-known/oauth-protected-resource. It is both the MCP URL and the JWT audience. It cannot be changed by SIGHUP. |
page_size |
1000 |
Aggregated MCP catalog page size; must be positive. |
request_timeout |
60s |
Default timeout for ordinary requests and backend calls; must be positive. Long-lived subscriptions use a separate lifecycle after body reading; see below. |
drain_timeout |
15s |
Maximum wait for active requests during SIGTERM/SIGINT shutdown and while replacing the old runtime after SIGHUP; must be positive. |
refresh_interval |
5m |
Maximum backend catalog refresh interval. A shorter backend TTL causes an earlier refresh, with an effective interval no shorter than 5 seconds. Must be positive. |
catalog_ttl |
30s |
Private TTL advertised for MCP catalog/discovery results; may be zero but not negative. |
max_request_body_bytes |
4194304 (4 MiB) |
MCP POST body limit; larger requests return 413. Must be positive. |
allowed_origins |
[] |
Additional browser HTTPS Origins. Each entry must be https://authority only, with no path, query, fragment, or wildcard. The origin of MCPHub's own public_url is allowed automatically. |
Durations use Go time.ParseDuration syntax, such as 500ms, 60s, and 5m. The path of public_url is the MCP entry path; the example uses /mcp. Percent-encoded path characters are rejected, and /healthz, /readyz, and /.well-known/oauth-protected-resource are reserved paths.
Request deadlines and long-lived subscriptions
Default timeout for ordinary MCP requests and backend calls; must be positive. Every MCP-listener HTTP route keeps a request-body read deadline from this value until the body is consumed or closed, including unauthenticated and rejected requests. Once MCP handling proceeds, ordinary MCP POSTs also apply it to response-write deadlines and the request context; the newer subscriptions/listen POST keeps long-lived connection semantics after its body is read and is not given those ordinary response-write or context timeouts. Runtime or client context cancellation still expires its underlying write deadline, so a slow subscription write is interrupted when its generation drains or the client disconnects.
vault
Optional; requires managed database storage. address is required; mount defaults to secret, prefix to mcphub, and auth_mount to approle. Choose either role_id_env + secret_id_env or token_env, never both. All fields are literal and do not expand ${...}; every global Vault setting requires a restart.
Put credential references in backends[].credentials. Personal mode also requires the user portal and that backend's own require_client_grant: true; global enforcement alone does not satisfy this configuration check. See the Vault guide for fields, paths, policies and callbacks.
admin
The default template enables administration. It serves an embedded UI and JSON API from a separate listener. Local mode is loopback-only and requires built-in account sign-in; remote mode uses built-in accounts or optional enterprise authentication over HTTPS. It manages backend and tool-group configuration; process settings remain in YAML with the restart/reload rules in the ownership table.
| Field | Default | Description |
|---|---|---|
enabled |
false |
Enables management with the selected database as configuration source of truth. |
mode |
local |
local is loopback-only and still requires built-in account sign-in; remote uses built-in or enterprise authentication over HTTPS. |
listen |
127.0.0.1:8081 |
Numeric loopback in local mode; remote mode may bind a private address behind an HTTPS proxy. |
public_url |
none | Required HTTPS admin origin in remote mode, with no path or trailing slash; also the admin JWT audience. |
client_id |
none | Browser OAuth client ID; built-in mode defaults to mcphub-admin. |
client_secret_env |
none | Optional confidential-client secret environment variable; defaults to a public client. |
required_scopes |
[mcphub:admin] |
All required admin scopes; cannot be empty for built-in or remote administration. |
database_driver |
sqlite |
sqlite or postgres. |
database_dsn_env |
MCPHUB_DATABASE_URL |
PostgreSQL DSN environment variable. Do not also set database_path for PostgreSQL. |
database_path |
none | Required for SQLite. Relative paths resolve from the YAML directory. |
encryption_key_env |
MCPHUB_CONFIG_KEY |
Environment variable containing a Base64-encoded 32-byte AES key. Losing or changing this key makes stored secrets unreadable. |
request_retention |
720h (30 days) |
Retention for completed MCP POST request history, from 24h to 8760h; expired records are pruned automatically. See administrator diagnostics and export. |
On the first start with an empty database, expanded YAML backends are imported in one transaction. The selected database becomes the sole backend source after the bootstrap marker is written; later YAML backend edits have no effect. Header values and OAuth client secrets are encrypted with AES-256-GCM and are never returned by the management API. SQLite reserves its write lock before each read-modify-write transaction; concurrent local administration waits up to five seconds, then reports failure without replaying the change.
The UI is available at http://127.0.0.1:8081/ by default. It can register, probe, edit, enable, disable, and delete backends without restarting the process. Required backend failures reject a change without replacing the current runtime; an unavailable optional backend is saved and continues reconnecting in the background.
The JSON API is rooted at /api/v1. Individual backend responses include an ETag; update and delete requests must send that revision in If-Match, and stale writes fail with 409 revision_conflict. Secret fields are returned only as configured markers. Omitting a secret value during an edit preserves it, while omitting the Header or OAuth configuration removes it. Audit actors are the authenticated user subject, local for unauthenticated advanced local management or system for background refreshes; outcomes are redacted.
Other configuration guides
| Configuration or task | Reference |
|---|---|
auth.sso, user policies, department/group synchronization and administrator recovery |
SSO and user management |
client_authorization, portal callbacks and required client grants |
Administrator manual: client authorization |
vault, backends[].credentials, shared/personal accounts |
Vault configuration and operations |
| HTTPS, remote administration and databases | Deployment guide |
| Complete YAML | Base example, remote SQLite, remote PostgreSQL |
Starting from the full YAML example
The default config.example.yaml is identical in the v2.4.0 server archive and online template. It enables the local console and SQLite with backends: []. Only MCPHUB_PUBLIC_URL and MCPHUB_CONFIG_KEY are required; configure addresses and credentials per service in the UI.
Download the template into a new private deployment directory, replace the two HTTPS addresses, and start:
curl -fL https://samuelsupe.github.io/mcphub/examples/config.example.yaml -o config.yaml
export MCPHUB_PUBLIC_URL=https://hub.example.com/mcp
umask 077
mkdir -p secrets
test -f secrets/config.key || openssl rand -base64 32 > secrets/config.key
export MCPHUB_CONFIG_KEY="$(cat secrets/config.key)"
mcphub validate --config config.yaml
mcphub init-admin --config config.yaml
mcphub serve --config config.yaml
Open http://127.0.0.1:8081/ and follow start the console → add backends → test connections → publish tools. Configure a separate service URL and Header/OAuth credential for each backend. Publish only reviewed tools explicitly classified as read, and assign required scopes. Restarting with the same key restores service settings, credentials and tool policies from SQLite.
validate is a read-only configuration check; it does not verify identity login, backend connections or tool calls. serve initializes fresh storage. Check /healthz and /readyz, then make a real call using deployment verification.
Remote administrators use the remote templates, adding an administrator origin, registered identity client and scopes. Deployments without a console use the separate advanced YAML-only example; fill each backend's actual address, credential, publication list and read policy in the file.
Users do not hold direct permissions. Create groups in Users & groups, assign roles, scopes, tools and resources to groups, then add users as members. Initialization creates the Administrators group. The identity API separates user membership updates from group permission updates; see groups and API contracts.
Name and URI mapping
- Tools and prompts are exposed as
<backend-id>.<original-name>. Original names must match[A-Za-z0-9_.-]{1,128}. If the namespaced name exceeds 128 characters, MCPHub truncates it while retaining the backend prefix and appends a short SHA-256 suffix of the original name. Invalid metadata and mapped collisions are omitted and logged. - Static resources and
ResourceLink/embedded resources in results are encoded asmcphub://<backend-id>/r/<base64url-no-padding(original-uri)>. MCPHub decodes that URI to read from the corresponding backend and recursively rewrites resource URIs in results. - Resource templates are encoded as
mcphub://<backend-id>/t/<sha256(original-template)>, retaining URI-template variables as a query expression (for example,{?id}). Reads and completions restore the backend's original template. ResourceLink,EmbeddedResource, andResourceContentsURIs in backend tool, prompt, or resource results are rewritten and recorded as issued resources for that backend. They remain readable and subscribable while their URI digest is retained, but are not added individually to the publicresourcescatalog. Each backend retains at most 16,384 distinct issued-URI SHA-256 digests; once the oldest digest is evicted, a new read or subscription for that URI may fail, while existing subscription cancellation and session cleanup still use the session map.resource updatednotifications never create catalog entries.- Resource subscriptions are tracked and deduplicated per upstream MCP session, while backend references are shared and reference-counted by original URI; an unpaired unsubscribe is ignored, session close cleans up, and reconnect restores all tracked subscriptions, waiting for
notifications/subscriptions/acknowledgedwhen the backend protocol supports that acknowledgement, before the backend is marked ready. An acknowledgement's subscription ID maps back to the original subscription URI(s), so a 2026 resource update fans out to those URIs even when the event URI differs; timeout, cancellation, and session/reconnect cleanup remove the mapping. When a modernsubscriptions/listenstream is canceled or disconnects, cleanup detaches from the canceled upstream context but remains bounded by backendrequest_timeoutand session lifecycle, so legacy backends still receiveresources/unsubscribe. If any restore fails, the backend remains unavailable and the reconnect loop tries again.
Each token's scope set selects an independent backend view, so one MCP connection sees only the capabilities allowed for that token.