18 KiB
Managed Agents — Tools & Skills
Tools
Server tools vs client tools
| Type | Who runs it | How it works |
|---|---|---|
Prebuilt Claude Agent tools (agent_toolset_20260401) |
Anthropic, on the session's container (for cloud envs; for self_hosted, your worker supplies and runs them — see shared/managed-agents-self-hosted-sandboxes.md) |
File ops, bash, web search, etc. Enable all at once or configure individually with enabled: true/false. |
MCP tools (mcp_toolset) |
Anthropic's orchestration layer | Capabilities exposed by connected MCP servers. Grant access per-server via the toolset. |
| Custom tools | You — your application handles the call and returns results | Agent emits a agent.custom_tool_use event, session goes idle, you send back a user.custom_tool_result event. |
Recommendation: Enable all prebuilt tools via agent_toolset_20260401, then disable individually as needed.
Versioning: The toolset is a versioned, static resource. When underlying tools change, a new toolset version is created (hence _20260401) so you always know exactly what you're getting.
Agent Toolset
The agent_toolset_20260401 provides these built-in tools:
| Tool | Description |
|---|---|
bash |
Execute bash commands in a shell session |
read |
Read a file from the local filesystem, including text, images, PDFs, and Jupyter notebooks |
write |
Write a file to the local filesystem |
edit |
Perform string replacement in a file |
glob |
Fast file pattern matching using glob patterns |
grep |
Text search using regex patterns |
web_fetch |
Fetch content from a URL |
web_search |
Search the web for information |
Enable the full toolset:
{
"tools": [
{ "type": "agent_toolset_20260401" }
]
}
Per-Tool Configuration
Override defaults for individual tools. This example enables everything except bash:
{
"tools": [
{
"type": "agent_toolset_20260401",
"default_config": { "enabled": true },
"configs": [
{ "name": "bash", "enabled": false }
]
}
]
}
| Field | Required | Description |
|---|---|---|
type |
✅ | "agent_toolset_20260401" |
default_config |
❌ | Applied to all tools. { "enabled": bool, "permission_policy": {...} } |
configs |
❌ | Per-tool overrides: [{ "name": "...", "enabled": bool, "permission_policy": {...} }] |
Permission Policies
Control when server-executed tools (agent toolset + MCP) run automatically vs wait for approval. Does not apply to custom tools.
| Policy | Behavior |
|---|---|
always_allow |
Tool executes automatically (default) |
always_ask |
Session emits session.status_idle and pauses until you send a tool_confirmation event |
{
"type": "agent_toolset_20260401",
"default_config": {
"enabled": true,
"permission_policy": { "type": "always_allow" }
},
"configs": [
{ "name": "bash", "permission_policy": { "type": "always_ask" } }
]
}
Responding to always_ask: Send a user.tool_confirmation event with tool_use_id from the triggering agent_tool_use/mcp_tool_use event:
{ "type": "tool_confirmation", "tool_use_id": "sevt_abc123", "result": "allow" }
{ "type": "tool_confirmation", "tool_use_id": "sevt_def456", "result": "deny", "message": "Read .env.example instead" }
The optional message on a deny is delivered to the agent so it can adjust its approach.
To enable only specific tools, flip the default off and opt-in per tool:
{
"tools": [
{
"type": "agent_toolset_20260401",
"default_config": { "enabled": false },
"configs": [
{ "name": "bash", "enabled": true },
{ "name": "read", "enabled": true }
]
}
]
}
Custom Tools (Client-Side)
Custom tools are executed by your application, not Anthropic. The flow:
- Agent decides to use the tool → session emits a
agent.custom_tool_useevent with inputs - Session goes
idlewaiting for you - Your application executes the tool
- You send back a
user.custom_tool_resultevent with the output - Session resumes
running
No permission policy needed — you're the one executing.
{
"tools": [
{
"type": "custom",
"name": "get_weather",
"description": "Fetch current weather for a city.",
"input_schema": {
"type": "object",
"properties": {
"city": { "type": "string", "description": "City name" }
},
"required": ["city"]
}
}
]
}
MCP Servers
MCP (Model Context Protocol) servers expose standardized third-party capabilities (e.g. Asana, GitHub, Linear). Configuration is split across agent and vault:
- Agent creation declares which servers to connect to (
type,name,url— no auth). The agent'smcp_serversarray has no auth field. - Vault stores the OAuth credentials. Attach via
vault_idson session create.
This keeps secrets out of reusable agent definitions. Each vault credential is tied to one MCP server URL; Anthropic matches credentials to servers by URL.
Agent side — declare servers (no auth):
| Field | Required | Description |
|---|---|---|
type |
✅ | "url" |
name |
✅ | Unique name — referenced by mcp_toolset.mcp_server_name |
url |
✅ | The MCP server's endpoint URL (Streamable HTTP transport) |
{
"mcp_servers": [
{ "type": "url", "name": "linear", "url": "https://mcp.linear.app/mcp" }
],
"tools": [
{ "type": "mcp_toolset", "mcp_server_name": "linear" }
]
}
Session side — attach vault:
{
"agent": "agent_abc123",
"environment_id": "env_abc123",
"vault_ids": ["vlt_abc123"]
}
💡 Per-tool enablement (empirical):
mcp_toolsethas been observed acceptingdefault_config: {enabled: false}+configs: [{name, enabled: true}]for an allowlist pattern. The API ref shows only the minimal{type, mcp_server_name}form.
💡 Changing tools/MCP servers on a running session:
sessions.update()can replaceagent.tools,agent.mcp_servers, andvault_idswhile the session isidle— a session-local override that doesn't touch the agent object. Seeshared/managed-agents-core.md→ Updating the agent configuration mid-session.
Large MCP tool outputs. If an MCP tool returns more than 100K tokens, the output is automatically offloaded to a file in the sandbox — the agent receives a truncated preview plus the file path and can read the full content. No configuration required.
Invalid vault credentials don't block session creation. If a vault credential is invalid for a declared MCP server, the session still creates successfully; a session.error event describes the MCP auth failure, and auth retries on the next session.status_idle → session.status_running transition.
⚠️ MCP auth tokens ≠ REST API tokens. Hosted MCP servers (
mcp.notion.com,mcp.linear.app, etc.) typically require OAuth bearer tokens, not the service's native API keys. A Notionntn_integration token authenticates against Notion's REST API but will not work as a vault credential for the Notion MCP server. These are different auth systems.
Vaults — the credential store
Vaults store credentials that Anthropic manages on your behalf. Two credential categories:
- MCP credentials (
mcp_oauth,static_bearer) — keyed bymcp_server_url. When the agent connects to a server at that URL, the token is injected automatically.mcp_oauthtokens are auto-refreshed via the standard OAuth 2.0refresh_tokengrant. This is the only way to authenticate MCP servers. - Environment variables (
environment_variable) — keyed bysecret_name(the env var name). The sandbox sees only an opaque placeholder; the real secret is substituted into the outbound request at egress. Use this for any service that authenticates through an environment variable: CLIs (aws,gcloud,stripe), SDKs, or directcurlcalls from thebashtool.
Secret fields you supply (token, access_token, refresh_token, client_secret, secret_value) are write-only — never returned in API responses.
Credentials and the sandbox
Vaults store credentials; those credentials never enter the sandbox. This is a deliberate security boundary — code running in the sandbox (including anything the agent writes) cannot read or exfiltrate a vaulted credential, even under prompt injection. Instead, credentials are injected by Anthropic-side proxies after a request leaves the sandbox:
- MCP tool calls are routed through an Anthropic-side proxy that fetches the credential from the vault and adds it to the outbound request.
- Git operations on attached GitHub repositories (
git pull,git push, GitHub REST calls) are routed through a git proxy that injects thegithub_repositoryresource'sauthorization_tokenthe same way. - Environment-variable credentials appear in the sandbox as an opaque placeholder; the real value replaces the placeholder at egress, on requests to the credential's allowed hosts only.
When vault credentials don't fit (e.g. self-hosted sandboxes — environment_variable is not yet supported there), register a custom tool: the agent emits agent.custom_tool_use, your orchestrator (which already holds the credential) executes the call and returns user.custom_tool_result over the same authenticated event stream. No public endpoint is exposed; the sandbox never sees the secret. See shared/managed-agents-client-patterns.md → Pattern 9.
Do not put API keys in the system prompt or user messages as a workaround — they persist in the session's event history.
Formerly known internally as TATs (Tool/Tenant Access Tokens).
Flow:
- Create a vault (
client.beta.vaults.create(...)) — one per tenant/user, or one shared, depending on your model - Add credentials to it (
client.beta.vaults.credentials.create(...)) — MCP credentials are keyed by MCP server URL; environment-variable credentials bysecret_name - Reference the vault on session create via
vault_ids: ["vlt_..."] - Anthropic auto-refreshes OAuth tokens before they expire and substitutes secrets at runtime
MCP OAuth credential shape:
{
"display_name": "Notion (workspace-foo)",
"auth": {
"type": "mcp_oauth",
"mcp_server_url": "https://mcp.notion.com/mcp",
"access_token": "<current access token>",
"expires_at": "2026-04-02T14:00:00Z",
"refresh": {
"refresh_token": "<refresh token>",
"client_id": "<your OAuth client_id>",
"token_endpoint": "https://api.notion.com/v1/oauth/token",
"token_endpoint_auth": { "type": "none" }
}
}
}
The refresh block is what enables auto-refresh — token_endpoint is where Anthropic posts the refresh_token grant. token_endpoint_auth is a discriminated union:
type |
Shape | Use when |
|---|---|---|
"none" |
{type: "none"} |
Public OAuth client (no secret) |
"client_secret_basic" |
{type: "client_secret_basic", client_secret: "..."} |
Confidential client, secret via HTTP Basic auth |
"client_secret_post" |
{type: "client_secret_post", client_secret: "..."} |
Confidential client, secret in request body |
Omit refresh entirely if you only have an access token with no refresh capability — it'll work until it expires, then the agent loses access.
💡 Getting an OAuth token. How you obtain the initial access and refresh tokens depends on the MCP server — consult its documentation. Once you have them, store them in a vault credential using the shape above; Anthropic auto-refreshes via the
refresh.token_endpointfrom there.
Environment-variable credential shape:
{
"display_name": "Twilio API key for sandbox",
"auth": {
"type": "environment_variable",
"secret_name": "TWILIO_API_KEY",
"secret_value": "sk-your-secret-here",
"networking": {
"type": "limited",
"allowed_hosts": ["api.twilio.com", "*.twilio.com"]
}
}
}
networking.allowed_hosts controls which outbound hosts the secret can be substituted for — {"type": "limited", "allowed_hosts": [...]} or {"type": "unrestricted"} if you can't enumerate the domains in advance. Limiting is strongly recommended: it prevents the key from ever being sent to unauthorized hosts.
⚠️ Two networking layers, both required.
networking.allowed_hostson the credential controls which requests use the secret, not which requests are allowed. The agent must also be able to reach the domain at the environment level (unrestricted, or the host listed in the environment'sallowed_hosts— seeshared/managed-agents-environments.md). A domain missing from either layer means the secret-substituted request fails.
⚠️ Client-side validation caveat. Substitution happens at egress, not inside the sandbox — clients that validate the credential format locally before making a network request (e.g. a CLI that checks the key starts with
sk-) will see the opaque placeholder and may fail at startup. If a client rejects the credential before any network call, that's why.
💡 Scope the key minimally. The agent can do anything the key allows; a key with broader permissions than the task needs increases the blast radius if the agent behaves unexpectedly.
Not supported with self-hosted sandboxes — environment_variable credentials require Anthropic-managed egress. See shared/managed-agents-self-hosted-sandboxes.md.
Constraints (all credential types):
- Unique key per vault.
mcp_server_url(MCP credentials) andsecret_name(environment-variable credentials) must be unique among active credentials in a vault; duplicates return a 409. - Keys are immutable. Secret values and
display_namecan be updated (rotation); to changemcp_server_url,secret_name,token_endpoint, orclient_id, archive the credential and create a new one. Archiving purges the secret and frees the key for a replacement. - Maximum 20 credentials per vault.
- Credentials are stored as provided and not validated until session runtime — an invalid credential surfaces as an authentication or downstream error during the session, which is emitted but does not block the session from continuing.
Scoping: Vaults are workspace-scoped. Anyone with developer+ role in the API workspace can create, read (metadata only — secrets are write-only), and attach vaults. vault_ids can be set at session create time but not via session update (the SDK docstring says "Not yet supported; requests setting this field are rejected").
Skills
Skills are reusable, filesystem-based resources that provide your agent with domain-specific expertise: workflows, context, and best practices that transform general-purpose agents into specialists. Unlike prompts (conversation-level instructions for one-off tasks), skills load on-demand and eliminate the need to repeatedly provide the same guidance across multiple conversations.
Two types — both work the same way; the agent automatically uses them when relevant to the task at hand:
| Type | What it is |
|---|---|
| Pre-built Anthropic skills | Common document tasks (PowerPoint, Excel, Word, PDF). Reference by name (e.g. xlsx). |
| Custom skills | Skills you've created in your organization via the Skills API. Reference by skill_id + optional version. |
Max 20 skills per agent. Agent creation uses managed-agents-2026-04-01; the separate Skills API (for managing custom skill definitions) uses skills-2025-10-02.
Enabling skills on a session
Skills are attached to the agent definition via agents.create():
const agent = await client.beta.agents.create(
{
name: "Financial Agent",
model: "{{OPUS_ID}}",
system: "You are a financial analysis agent.",
skills: [
{ type: "anthropic", skill_id: "xlsx" },
{ type: "custom", skill_id: "skill_abc123", version: "latest" },
],
}
);
Python:
agent = client.beta.agents.create(
name="Financial Agent",
model="{{OPUS_ID}}",
system="You are a financial analysis agent.",
skills=[
{"type": "anthropic", "skill_id": "xlsx"},
{"type": "custom", "skill_id": "skill_abc123", "version": "latest"},
]
)
Skill reference fields:
| Field | Anthropic skill | Custom skill |
|---|---|---|
type |
"anthropic" |
"custom" |
skill_id |
Skill name (e.g. "xlsx", "docx", "pptx", "pdf") |
Skill ID from Skills API (e.g. "skill_abc123") |
version |
— | "latest" or a specific version number |
Skills API
| Operation | Method | Path |
|---|---|---|
| Create Skill | POST |
/v1/skills |
| List Skills | GET |
/v1/skills |
| Get Skill | GET |
/v1/skills/{id} |
| Delete Skill | DELETE |
/v1/skills/{id} |
| Create Version | POST |
/v1/skills/{id}/versions |
| List Versions | GET |
/v1/skills/{id}/versions |
| Get Version | GET |
/v1/skills/{id}/versions/{version} |
| Delete Version | DELETE |
/v1/skills/{id}/versions/{version} |