Skip to main content
A shared server can declare required personal credential slots. Each authenticated user binds their own values in Credentials or from the server card. Server owners and administrators manage the declaration; users can only replace or delete their own binding. Shared users see slot names and labels without seeing the server’s connection configuration.

Deployment

MCPHUB_CREDENTIAL_ENCRYPTION_KEY is optional for a single instance in JSON mode. When it is absent, the first binding save generates 32 random bytes and atomically creates <MCPHUB_SETTING_PATH>.credentials.key next to the settings file (or next to the default settings file). The key file has mode 0600 and is reused after restart. It is excluded from configuration exports, Git, and the Docker build context. The settings directory must be writable; failure to persist the key stops the save. Database mode does not automatically generate a local key. Before saving personal credentials or starting personal upstream OAuth, configure MCPHUB_CREDENTIAL_ENCRYPTION_KEY; otherwise the operation fails before storing a binding. Existing local key files remain supported for compatibility. For an existing deployment, preserve the original key and set its value in the environment variable. Database backups alone cannot restore credentials if the key is lost when a container is replaced. An explicitly set environment variable takes precedence and must contain 32 random bytes encoded as base64, for example generated with openssl rand -base64 32. Invalid environment values or key files fail instead of being silently replaced. If the key file is missing while encrypted bindings already exist, restore the original file or environment key; MCPHub will not generate a replacement that makes old bindings unreadable. Back up the key separately and persist the settings directory in Docker; mounting only the JSON settings file does not persist its sibling key file. Multiple instances sharing a database must use the same environment key or the same shared key file, rather than generating independent keys. Keep the key outside server definitions. The environment variable is removed from child-process environments. Bindings use AES-256-GCM with the server name and username authenticated as associated data. JSON mode stores ciphertext in <MCPHUB_SETTING_PATH>.credentials.json (or next to the default settings file), with mode 0600 and atomic replacement. Database mode stores ciphertext in credential_bindings. The normal configuration export excludes bindings. File-to-database migration copies ciphertext; retain the same encryption key. JSON mode assumes a single writer process; use database mode for multiple hub processes. For existing deployments, follow Credential Key Upgrade and Recovery before replacing a container, or when bindings stop working after an upgrade.

Declare slots

Use env or args slots for stdio and headers slots for SSE, Streamable HTTP, and OpenAPI. Values are literal strings, without environment-variable expansion. Supply the entire header value, including Bearer when required. Declared personal headers override static, passthrough, and OpenAPI tool-parameter headers. Templates contain target, name, an optional label, and an allowed list for URL slots (maximum 32 slots).
The environment variables follow the upstream Tavily configuration and Context7 developer guide. Remove any credential-bearing CLI arguments when migrating: Context7’s --api-key argument takes precedence over its environment variable. For an HTTP server, declare {"target":"headers","name":"Authorization"} instead. Shared upstream OAuth cannot be combined with a personal credential template.

CLI-only credentials

For a CLI that accepts credentials only as arguments, declare an args slot and reference it with {{args.API_KEY}} in an argument. Replace API_KEY with the slot name (case-sensitive; letters, digits and underscores, starting with a letter or underscore). In the dashboard, select Arguments as the slot target and enter each argument in its own input.
Bind the value under args.API_KEY through the credentials API. A placeholder may appear multiple times, including inside an argument such as --api-key={{args.API_KEY}}; each occurrence uses the same personal value. Every args slot must be referenced, and unknown or malformed references are rejected. Env and args slots may share a name because their targets identify separate fields. MCPHub substitutes the personal value at spawn time, after ordinary environment expansion and proxy argument cleaning. Spaces, quotes, $ and placeholder-like text in a value remain literal; values are not expanded recursively. No shell wrapper is required. Do not place personal placeholders inside a shell script or other executable code: the interpreter would evaluate the substituted text. Credentials passed as arguments may be visible to operating-system process inspection tools even though hub logs and public configuration responses omit them. For personal upstream OAuth, use the separate identity mode described below. Existing POSIX shell wrappers remain supported. Their env slots must use bare lowercase references such as "$my_api_key": $MY_API_KEY and ${my_api_key} can be expanded and cleared before personal credentials are injected. For sh -c, enter -c and the complete script as two separate dashboard arguments.

Manage a binding through the API

Self-service endpoints use dashboard authentication (JWT in x-auth-token, an OIDC/session login) and manage only the current user. User-level MCP bearer keys remain MCP-only credentials; system bearer keys cannot use the self-service endpoints. Anonymous dashboard mode cannot manage personal bindings. Self-service and administrator-managed lists show only the last four characters of saved values; values of 5–8 characters show the last two, values of 2–4 characters show the last one, and single-character values are fully masked. Previews are keyed by slot ID (for example, env.API_KEY) and never fill replacement inputs. Administrators can select a user on Credentials, or open their credentials from Users. These endpoints accept authenticated administrators or system bearer keys with accessType: 'all': The target must already have a local user record, otherwise the response is 404; pending bindings for SSO users before their first login are not supported. Writes and deletes require a credential template and server discovery permission for the target user, otherwise they return 404. Non-admin users and anonymous dashboard guests receive 403. Administrators can change the upstream identity a target user acts as; existing values expose only masked suffix previews. Personal OAuth consent still belongs to the user. Provisioning does not change runtime identity: system keys still cannot use personal credentials. Example PUT body:
All slots are required; undeclared slots are rejected. Inputs stay blank when revisiting the dashboard. Deleting a server or user removes the corresponding bindings. Renaming a server keeps the bindings: each one is re-encrypted under the new name. If the same edit also changes a connection field (URL, command, headers, credential slots, …), the bindings are removed and users must bind again. If a binding cannot be decrypted (for example, the encryption key is missing), the rename fails and nothing changes: restore the key, or have that user replace or delete the binding, then rename again. Adding a required slot also requires users to replace their binding with values for all slots.

Copy credentials

Saved credential cards show masked values by default. Choose Update credentials to replace every field, or Copy from another server to expand the copy controls. Unavailable sources remain visible with an explanation (missing fields, unsaved credentials, or incompatible URL values). More actions → Delete binding requires confirmation and explains that subsequent requests will need credentials again. On the target server’s Credentials card, choose Copy from another server and select a compatible configured server. Both servers must be visible to the same target user. Copying creates an independent binding: later source edits or deletion do not affect the copy. Verify that you trust the target server before copying a secret to it. Existing target bindings require explicit overwrite confirmation. POST /api/credentials/:name/copy copies your own binding; administrators can use POST /api/users/:username/credentials/:name/copy for that user’s bindings. Send { "sourceServer": "source-name", "overwrite": false }; success returns { "success": true } without secret values. overwrite: true permits replacing an existing target binding; concurrent target changes cause the operation to fail so you can refresh and retry. Every target slot must exist in the source with the same target and name (header names are case-insensitive). Extra source slots are excluded. URL values must satisfy the target’s allowed list; the dashboard conservatively offers only sources whose allowed values are all accepted by the target. Cross-field mappings, token prefix conversion, cross-user copying, and personal OAuth copying are not supported.

Runtime behavior

  • MCP session/OIDC users resolve their own binding; user-level bearer keys resolve their live owner user. A route name or caller-supplied username never selects a credential.
  • Every request checks the current binding. Missing values fail with an instruction to bind credentials in Credentials; there is no implicit organization fallback.
  • Templated servers start on first discovery or invocation. A stdio child is keyed by (server, user), shared across that user’s MCP sessions, and hidden from the server list. This takes precedence over perSessionClient.
  • Active requests hold their runtime. After the last request finishes, idleTimeoutMs (default five minutes) recycles it. Updates/deletes invalidate the local runtime immediately; other hub processes observe the new binding on their next request.
  • HTTP clients are isolated per user too and replaced when bindings change. Personal tools, prompts, and resources are not added to shared embedding caches; ordinary routing works, while vector-based smart search does not index personal catalogs.
  • Personal upstream connection errors and stderr are withheld because they may contain credential values. Normal server definitions retain their existing behavior.
For staging, bind different Alice/Bob keys, call both services concurrently using each user’s bearer key, replace/delete Alice’s binding, and verify Bob’s calls remain unaffected. Test idle recycling and email-style OIDC usernames as well. The automated integration suite covers real local stdio, HTTP, and OpenAPI servers; real Tavily/Context7 API calls require your staging credentials.

Personal upstream OAuth

For OAuth-protected HTTP MCP servers, set oauth.identityMode to personal in the server configuration (or select Each user authorizes independently in the dashboard). Existing configurations default to shared OAuth. Personal OAuth and manual credential slots are separate modes; do not supply shared tokens or pending authorization in a personal-mode declaration.
Each user connects from their server card in Servers. MCP calls using a user-associated Bearer key use that key’s owner, including /mcp/<group> aggregate routes. Shared/system keys and anonymous callers cannot use personal OAuth. An unbound caller receives Personal authorization required; there is no shared-token fallback. Authorizing an MCP client to access MCPHub does not connect the user’s upstream account. Complete personal upstream OAuth separately in Dashboard → Servers, then retry tool discovery. If missing personal authorization leaves tools/list with no tools, MCPHub returns a JSON-RPC error (-32000) with data.code: "PERSONAL_AUTHORIZATION_REQUIRED" and data.servers listing the connectors that require authorization. A group with other available tools continues to return those tools. The private-broker authorization relay only forwards an existing personal flow; it does not start personal authorization from the client’s MCPHub consent flow. The MCP SDK discovers the upstream OAuth endpoints and dynamically registers a client for the user’s binding. When registration is disabled upstream, configure a pre-registered oauth.clientId (and oauth.clientSecret for a confidential client) and the callback redirect URI instead. The existing administrator-controlled allowInsecureTokenEndpoint exception still applies to self-managed HTTP deployments; HTTPS remains the default. For private upstreams with a separate authorization server, explicit authorization server metadata overrides SDK authorization-server discovery while keeping per-user storage and renewal. The upstream must accept tokens from that server and the SDK resource indicator. Tokens, client registration and pending PKCE/state are encrypted in the same private binding store used by credential slots. Back up its encryption key as described above. Pending flows expire after ten minutes, survive a restart within that period, and can be consumed only once. Starting another flow supersedes the previous one. Changes to the server definition invalidate its personal authorizations and require reconnecting. Disconnect takes effect locally immediately, invalidates only that user’s runtime, and attempts upstream token revocation when an endpoint is known. Failed revocation does not restore the local binding. Refresh/callback writes from an obsolete binding are rejected. File storage assumes one hub process owns the settings directory; database storage uses conditional writes to reject stale revisions. Dashboard-authenticated APIs operate only on the current user: The shared /oauth/callback endpoint resolves personal flow ownership from stored opaque state; request parameters cannot select a different user.

Personal HTTP endpoints

SSE and Streamable HTTP servers can declare URL slots for regional or tenant endpoints:
Select URL in the server’s credential slot editor and enter allowed values one per line. Users choose a value in Credentials, or bind url.region through the API alongside all other slots. URL slots cannot be combined with OAuth. Each reference must fill a complete hostname label or path segment. Host values use ASCII letters, digits and internal hyphens (maximum 63 characters); path values use ASCII letters, digits, _, -, ., ~, excluding . and ... No URL encoding or free-form input is accepted. Slots cannot appear in schemes, ports, user information, queries or fragments. Every URL slot must be referenced. Allowed lists contain 1–256 unique values, each at most 256 characters. Bindings are checked on save and every runtime acquisition. Removing a choice makes old bindings unconfigured and rejects their next request, including when a user already has a cached client. Calls already in progress are not retroactively cancelled. URL-slot servers always use the SSRF blocklist, including administrator-owned servers; network.allowedCidrs still applies to connections and redirects.