Vaults & MCP
Connect agents to remote MCP servers for external tools, with credentials held in encrypted vaults.
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 are configured on the agent, in mcp_servers. Streamable HTTP
servers are supported in v1.
In the agent form, add servers under MCP servers — each entry has a name, a URL, and optional auth and tool settings.
Set mcp_servers when creating or patching an agent:
{
"mcp_servers": [
{
"name": "gateway",
"url": "https://mcp.example.com",
"auth": { "type": "token_source", "provider": "hydra" },
"allowed_tools": ["search", "lookup"],
"tool_timeout_secs": 60
}
]
}| 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
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 namedprovider(supabaseorhydra). 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
Probe an agent's declared MCP servers and list the tools they expose:
curl https://agents.clusterbase.dev/v1/agents/agt_3f9c2a/mcp-tools \
-H "Authorization: Bearer $CLUSTER_TOKEN"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 optionalrefreshblock. When the access token expires, the runtime re-issues it automatically at thetoken_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 readsGH_TOKEN), so use it only for keys the agent is meant to use. It's keyed onsecret_name(not a URL) and is applied only on the/v1/runslane (see Connecting them).
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. "set up an MCP server with a vault token"). Full vault management is available over the API.
Create a vault, then add a credential to it:
# 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:
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:
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
Attach vaults to a session with vault_ids when you create it:
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 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.