> ## Documentation Index
> Fetch the complete documentation index at: https://docs.mcphub.app/llms.txt
> Use this file to discover all available pages before exploring further.

# Per-user Credentials

> Use one shared MCP server definition with a separate credential binding for each user

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. 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.

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.

## Declare slots

Use `env` 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 only `target`, `name`, and an optional `label` (maximum 32 slots).

```json theme={null}
{
  "mcpServers": {
    "tavily": {
      "type": "stdio",
      "command": "npx",
      "args": ["-y", "tavily-mcp"],
      "owner": "admin",
      "visibility": "public",
      "idleTimeoutMs": 300000,
      "credentialTemplate": [
        { "target": "env", "name": "TAVILY_API_KEY", "label": "Tavily API key" }
      ]
    },
    "context7": {
      "type": "stdio",
      "command": "npx",
      "args": ["-y", "@upstash/context7-mcp"],
      "owner": "admin",
      "visibility": "public",
      "credentialTemplate": [
        { "target": "env", "name": "CONTEXT7_API_KEY", "label": "Context7 API key" }
      ]
    }
  }
}
```

The environment variables follow the upstream [Tavily configuration](https://github.com/tavily-ai/tavily-mcp) and [Context7 developer guide](https://github.com/upstash/context7/blob/master/docs/resources/developer.mdx). 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.

## Manage a binding through the API

Use dashboard authentication (JWT in `x-auth-token`, an OIDC/session login, or a user OAuth access token). User-level MCP bearer keys remain MCP-only credentials; system bearer keys and anonymous dashboard mode cannot manage personal bindings. No endpoint accepts a target username.

| Method | Endpoint                 | Behavior                                                                                                  |
| ------ | ------------------------ | --------------------------------------------------------------------------------------------------------- |
| GET    | `/api/credentials`       | All visible templated servers, slot metadata, `configured`, `configuredSlots`, and `updatedAt`; no values |
| PUT    | `/api/credentials/:name` | Replace the current user's complete binding                                                               |
| DELETE | `/api/credentials/:name` | Delete the current user's binding                                                                         |

Example PUT body:

```json theme={null}
{ "values": { "env.TAVILY_API_KEY": "your-personal-api-key" } }
```

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 removes bindings under its old name, so users must bind again. Adding a required slot also requires users to replace their binding with values for all slots.

## 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.
