# Vaults & MCP



Agents can call tools served by remote **MCP** (Model Context Protocol) servers.
When a server needs a secret bearer token, you store it in an encrypted **vault**
and attach the vault to a session — the agent definition itself never carries a
credential.

## MCP servers [#mcp-servers]

MCP servers are configured **on the agent**, in `mcp_servers`. Streamable HTTP
servers are supported in v1.

<Tabs items="['Console', 'API']">
  <Tab value="Console">
    In the agent form, add servers under **MCP servers** — each entry has a name, a
    URL, and optional auth and tool settings.
  </Tab>

  <Tab value="API">
    Set `mcp_servers` when creating or patching an agent:

    ```json
    {
      "mcp_servers": [
        {
          "name": "gateway",
          "url": "https://mcp.example.com",
          "auth": { "type": "token_source", "provider": "hydra" },
          "allowed_tools": ["search", "lookup"],
          "tool_timeout_secs": 60
        }
      ]
    }
    ```
  </Tab>
</Tabs>

| Field               | Notes                                                                                                          |
| ------------------- | -------------------------------------------------------------------------------------------------------------- |
| `name`              | Unique within the agent; becomes the `mcp__<name>__<tool>` namespace.                                          |
| `url`               | The server's Streamable HTTP endpoint.                                                                         |
| `auth`              | `none` (default) or `token_source`. See below.                                                                 |
| `allowed_tools`     | An enable-list of tool names. Omit to allow every tool the server advertises (must be non-empty when present). |
| `tool_timeout_secs` | Per-call timeout, `1`–`600`. Defaults to `120`.                                                                |

### Auth [#auth]

Agent-level MCP auth is **secret-free** — an agent definition never stores a
token:

* **`none`** — for public, unauthenticated servers.
* **`token_source`** — mint a service bearer at connect time from a named
  `provider` (`supabase` or `hydra`). Use this for first-party servers that accept
  a platform identity.

For third-party servers that require a **static bearer token**, store the token in
a vault (next section) rather than on the agent.

### Discover tools [#discover-tools]

Probe an agent's declared MCP servers and list the tools they expose:

```bash
curl https://agents.clusterbase.dev/v1/agents/agt_3f9c2a/mcp-tools \
  -H "Authorization: Bearer $CLUSTER_TOKEN"
```

## Connectors [#connectors]

**Connectors** are a curated layer on top of raw MCP: a catalog of ready-made
integrations — hosted MCP services plus native ones such as Gmail — that you
connect once with OAuth and then enable on agents by id. Unlike `mcp_servers`
(you supply the URL) or vault tokens (you supply the credential), a connector
needs neither: the platform hosts the integration, and each user authorizes
their own account.

List the catalog:

```bash
curl https://agents.clusterbase.dev/v1/mcp-services \
  -H "Authorization: Bearer $CLUSTER_TOKEN"
```

Each entry reports its `id`, `display_name`, `backend` (`hosted_mcp` or
`native_gmail`), `auth_mode`, and whether **you** are already `connected`.

### Connect [#connect]

Connecting is a standard OAuth authorization-code flow. Start it, send the user
to the returned `authorization_url`, and complete it with the code the provider
returns:

```bash
# 1. Start — returns { "authorization_url": "...", "expires_at": "..." }
curl https://agents.clusterbase.dev/v1/mcp-services/gmail/connect/start \
  -H "Authorization: Bearer $CLUSTER_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "return_url": "https://console.clusterbase.ai/mcp/callback" }'

# 2. The user authorizes in the browser; the provider redirects back.

# 3. Complete — the `state` identifies the flow, so there is no {id} in the path
curl https://agents.clusterbase.dev/v1/mcp-services/connect/complete \
  -H "Authorization: Bearer $CLUSTER_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "code": "...", "state": "..." }'
```

The Console does all of this for you — its callback lives at
`console.clusterbase.ai/mcp/callback`. Disconnect with
`DELETE /v1/mcp-services/{id}/connection`.

### Use on an agent [#use-on-an-agent]

Enable a connector by adding its id to the agent's
[`mcp_services`](/docs/agents/agents#connectors) list on create or patch. The
ids are catalog-validated, and the definition stores nothing secret — at run
time the connector's tools resolve with the **session owner's** connection.
Connector selection lives on the agent definition only; a per-message connector
field on ordinary session events is rejected.

### Tune the tools [#tune-the-tools]

List a connector's tools and disable the ones you don't want:

```bash
curl https://agents.clusterbase.dev/v1/mcp-services/gmail/tools \
  -H "Authorization: Bearer $CLUSTER_TOKEN"

curl -X PUT https://agents.clusterbase.dev/v1/mcp-services/gmail/tool-selection \
  -H "Authorization: Bearer $CLUSTER_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "disabled_tools": ["delete_email"] }'
```

## Vaults [#vaults]

A **vault** holds encrypted credentials. Every secret is encrypted at rest and
**never** returned by any response. Three credential types are supported, in two
categories — **MCP credentials** keyed to a specific MCP server URL, and an
**environment-variable credential** keyed to a VM env-var name:

* **`static_bearer`** — a fixed bearer token (API key / PAT). No refresh.
* **`mcp_oauth`** — an OAuth 2.0 access token with an optional `refresh` block.
  When the access token expires, the runtime re-issues it automatically at the
  `token_endpoint` (using the stored refresh token) at connect time.
* **`environment_variable`** — a plaintext secret injected as a **VM environment
  variable** at provision time, for third-party keys the agent's in-VM tools read
  directly (e.g. `NOTION_API_KEY`). Unlike the MCP types — whose tokens are used
  only by the gateway — the agent process **can** read this value (the same way
  it reads `GH_TOKEN`), so use it only for keys the agent is *meant* to use. It's
  keyed on `secret_name` (not a URL) and is applied only on the `/v1/runs` lane
  (see [Connecting them](#connecting-them)).

<Callout title="Console support">
  Vaults have no dedicated Console screen yet, but the agent builder can create a
  vault for you when you ask it to (e.g. &#x2A;"set up an MCP server with a vault
  token"*). Full vault management is available over the API.
</Callout>

Create a vault, then add a credential to it:

```bash
# 1. Create a vault
curl https://agents.clusterbase.dev/v1/vaults \
  -H "Authorization: Bearer $CLUSTER_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "display_name": "Third-party APIs" }'
# → { "id": "vlt_1a2b3c", "display_name": "Third-party APIs", ... }

# 2. Add a static-bearer credential
curl https://agents.clusterbase.dev/v1/vaults/vlt_1a2b3c/credentials \
  -H "Authorization: Bearer $CLUSTER_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "display_name": "Example API token",
    "auth": {
      "type": "static_bearer",
      "mcp_server_url": "https://mcp.example.com",
      "token": "sk-the-secret-token"
    }
  }'
```

The `token` is plaintext **on the request only** — it's encrypted before it
touches storage and is never echoed back. The credential response carries only
non-secret fields — its `type`, the `mcp_server_url` or `secret_name` key, and
display fields.

For an OAuth server, use `mcp_oauth` and supply a `refresh` block so tokens
renew themselves:

```bash
curl https://agents.clusterbase.dev/v1/vaults/vlt_1a2b3c/credentials \
  -H "Authorization: Bearer $CLUSTER_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "display_name": "Slack (OAuth)",
    "auth": {
      "type": "mcp_oauth",
      "mcp_server_url": "https://mcp.slack.com/mcp",
      "access_token": "xoxp-...",
      "expires_at": "2026-01-01T00:00:00Z",
      "refresh": {
        "token_endpoint": "https://slack.com/api/oauth.v2.access",
        "client_id": "1234.5678",
        "scope": "chat:write",
        "refresh_token": "xoxe-...",
        "token_endpoint_auth": { "type": "client_secret_post", "client_secret": "..." }
      }
    }
  }'
```

`token_endpoint_auth.type` is `none` (public client), `client_secret_basic`, or
`client_secret_post`. If a refresh ever fails, the credential is dropped for that
connection (the agent proceeds unauthenticated) rather than failing the turn — to
diagnose why, call
`POST /v1/vaults/{id}/credentials/{cid}/mcp_oauth_validate`, which returns a
`status` of `valid` (works), `invalid` (re-authorize), or `unknown` (transient,
retry). Diagnosing may itself rotate and persist the refresh token.

For a secret your agent's in-VM tools read as an environment variable, use
`environment_variable` — there's no `mcp_server_url`, just the `secret_name` to
inject and its `value`:

```bash
curl https://agents.clusterbase.dev/v1/vaults/vlt_1a2b3c/credentials \
  -H "Authorization: Bearer $CLUSTER_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "display_name": "Notion API key",
    "auth": {
      "type": "environment_variable",
      "secret_name": "NOTION_API_KEY",
      "value": "ntn-the-secret-value"
    }
  }'
```

`secret_name` must be a valid POSIX env-var name (`[A-Za-z_][A-Za-z0-9_]*`, up to
64 chars) and may not use a runtime-reserved name or prefix (`PATH`, `IFS`,
`LD_*`, or the `GH_` / `GIT_` / `CCP_` / `CLUSTA_` system prefixes) — a name that
breaks these rules is a `400`. The `value` is write-only (encrypted at rest,
never echoed).

## Connecting them [#connecting-them]

Attach vaults to a session with `vault_ids` when you create it:

```bash
curl https://agents.clusterbase.dev/v1/sessions \
  -H "Authorization: Bearer $CLUSTER_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "agent": "agt_3f9c2a", "vault_ids": ["vlt_1a2b3c"] }'
```

At MCP connect time, the runtime resolves each server's credential by matching
the server `url` against credentials' `mcp_server_url`. The **first attached
vault** holding a credential whose `mcp_server_url` **exactly matches** the server
wins. You must own every vault you attach (a non-owned id returns `404`).

`environment_variable` credentials are handled differently: they're injected as
VM environment variables at **provision time**, so they apply only when you
attach the vault to a [run](/docs/agents/runs) via `vault_ids` on `POST /v1/runs`
— not on `POST /v1/sessions`, where a VM may be warm-reused (env-var creds on a
session bind are ignored, with a server-side warning). On a name collision across
attached vaults, the **first attached vault** wins, mirroring the MCP rule.

Because these secrets are load-bearing, delivery is **all-or-nothing**: if the
run's env-var secrets can't be rendered onto the VM (a rare transient
provisioning fault, with no later reconcile on an ephemeral run VM), the run
fails with a `502` and the VM is torn down — rather than starting an agent that's
silently missing them. Retry the run.

Manage vaults and credentials with the standard endpoints. `PUT /v1/vaults/{id}`
updates a vault's `display_name` and/or `metadata`; archived vaults return
`409`. `PUT /v1/vaults/{id}/credentials/{cid}` rotates a `static_bearer` secret
and/or renames it. Structural keys (`mcp_server_url` / `secret_name`) are
immutable; changing `mcp_server_url` returns `400`. Archive and recreate for
`mcp_oauth`, `environment_variable`, or structural-key changes. Remove a vault
or credential with `DELETE` (hard) or `/archive` (soft) on both
`/v1/vaults/{id}` and `/v1/vaults/{id}/credentials/{cid}`. List endpoints are
paginated (`limit`, `has_more`), return newest-first records, and exclude
archived records unless you pass `include_archived=true`. See the
[API reference](/docs/agents/api-reference#vaults).
