Managed Agents

Vaults & MCP

Connect agents to external tools — raw MCP servers, curated connectors, and credentials held in encrypted vaults.

View as Markdown

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",
      "allowed_tools": ["search", "lookup"],
      "tool_timeout_secs": 60
    }
  ]
}
FieldNotes
nameUnique within the agent; becomes the mcp__<name>__<tool> namespace.
urlThe server's Streamable HTTP endpoint.
authnone (default). See below.
allowed_toolsAn enable-list of tool names. Omit to allow every tool the server advertises (must be non-empty when present).
tool_timeout_secsPer-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.

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

token_source is retired

The token_source auth type (a platform-minted service bearer) has been retired. New or updated agent definitions that set auth.type: token_source are rejected with a 400; use a vault credential or an OAuth connector instead. Existing records that predate this change remain readable but can no longer mint that credential.

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"

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, Google Calendar, Google Drive, Slack and Outlook — 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:

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

Each entry reports its id, display_name, backend (hosted_mcp or hosted_slack, native_gmail, native_google_drive, native_google_calendar, or native_outlook), auth_mode, and whether you are already connected.

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:

# 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" \
  -H "Origin: https://console.clusterbase.ai" \
  -d '{ "return_url": "https://console.clusterbase.ai/" }'

# 2. The user authorizes in the browser; the provider redirects to Agents'
#    own server-owned callback, which then redirects to `return_url` with
#    an opaque `state` ticket (never a `code`).

# 3. Complete — the `state` identifies the flow, so there is no {id} in the
#    path and no `code`: Agents already holds the provider's authorization
#    code server-side.
curl https://agents.clusterbase.dev/v1/mcp-services/connect/complete \
  -H "Authorization: Bearer $CLUSTER_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "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.

Providers redirect to a single Agents-owned callback, /v1/mcp-services/connect/callback, configured by the deployment's MCP_OAUTH_PROVIDER_CALLBACK_URL — register that URL with the OAuth provider, not an app origin. Agents keeps the provider's authorization code server-side and redirects the browser to return_url with only the opaque state (and error/error_description on failure). Web and Console both use the public path /mcp/callback on their respective origins as return_url; Console's host routing rewrites this path to its internal /console/mcp/callback page. The return_url is the destination after completion, not the provider callback; its origin must match the request's Origin header and an Agents-approved origin.

Use on an agent

Enable a connector by adding its id to the agent's mcp_services 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

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

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"] }'

Use from an external MCP client

POST /v1/mcp-services/{id}/mcp bridges a connected service as stateless MCP JSON-RPC (initialize, tools/list, tools/call) for clients other than an agent — for example the Build CLI reusing your Cluster login instead of connecting the service again. It requires a signed-in (non-guest) bearer and resolves your current connection and disabled-tool selection on every call, so a disconnect or tool change takes effect immediately. Provider credentials never leave the server. Requests are capped at 1 MiB, tool calls at two minutes, and the whole request at 125 seconds; closing the HTTP connection cancels any in-flight discovery or call.

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

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.

On this page