Skip to main content
Three kinds, and they differ in where the code runs. Everything else the agent can do — shell, reading and writing files, starting services, publishing a preview, committing to a repository — is always present and is not configured here. You are choosing what it can reach outside the workspace, not what it can do inside one. All three are set with one call, which replaces the whole configuration:
At most 32 tools per session; each name is 1–64 characters.

Built-in tools

The sandbox already has these. Naming one is the whole request — they take no other field:
Naming any of them narrows the session to exactly those. Say nothing and the session gets whatever this deployment allows, which is what every session got before you could choose. Say "tools": [] and it gets none of them — no way out to the network at all, which is the shape for running code you have not read. You can only narrow. A tool the deployment has turned off does not come back by being named in a request body. If it did, the operator switch would be decoration, and the one thing it exists for — running untrusted work with no route to the internet — is exactly what it could no longer guarantee. So the list you get is the intersection, and reading the configuration back tells you what you actually have rather than what you asked for.

Function tools

You declare the shape; the agent calls it; the turn stops until you answer.
When the agent calls it, the session reports a required action and waits. Answering is required-actions.md; the loop is written for you in run-session.ts. A turn waiting on you is still in flight. The workspace is not reclaimed out from under it, however long you take. There is no deadline on the answer unless you set one — timeout_seconds on the tool, see required-actions.md — and the only ceiling either way is the workspace’s two hours, in limits.md.

MCP servers

Exactly one of url or command:
  • url — a server already running, which the sandbox connects out to.
  • command — a process started inside the sandbox, spoken to over stdio.
The connection is made from the sandbox, not from us. Your tool traffic does not pass through our control plane — which is what lets an MCP server on your private network be reachable at all, and keeps our egress out of your blast radius. A server reachable only from your VPC works if the sandbox is in it; one behind your office firewall does not.

When a server cannot be reached

By default it is skipped, and the agent simply has fewer tools. Nothing fails. The agent will usually say it cannot do the thing you asked — which looks like the model being unhelpful, when the real cause is a server that never answered. That default is deliberate: one optional server being down should not destroy a session that could still do most of its work. But it is only right when the server really is optional. You will be told, though. A skipped server produces an mcp_unavailable item in the session’s transcript and an mcp.unavailable event on the stream:
So when an agent says it could not do something, check the items before you conclude the model was weak — that is the mistake this item exists to prevent. If the session is pointless without that server, say so:
With required: true the session fails loudly instead of running crippled, and you get an error rather than a confused answer. It arrives on the first call that needs the workspace — sending a message, writing a file — and it names the server and what went wrong:
invalid_request rather than a retryable code, deliberately: waiting will not make the server reachable. This is a configuration to change, not a state to wait out.
Choose deliberately. required: false and a broken URL is the one combination that produces no signal anywhere a caller can see it. If you are not sure, required: true is the safer default for anything the task actually depends on.
required must be true or false. A string — "required": "yes" — is refused rather than read as false, because false is a real choice here and not one you would have made by writing the word required.

Reading the configuration back

The answer is in the same vocabulary you sent: one tools array, each entry carrying its type. The same shape goes back to PUT. Secrets are not returned. authorization and every header value are held back, and redacted names what was withheld — so a server with credentials configured is distinguishable from one without, which an omission alone would not tell you. redacted is not a request field. If you read the configuration, change something and PUT it back, you get a 400 naming redacted rather than a success that quietly replaced your credentials with nothing. Re-send the secrets, or build the body from your own source.

Refusals

Every refusal carries { error: { code, message, request_id } } — see errors.md.

Next