The two sides of this change are more than 400 edits apart, too far apart to line up, so this is the differ's own diff of it and the words inside a line are not marked.
from line 16
1616
1717## Available tools
1818
19The agent toolset includes the following tools. All are enabled by default when you include the toolset in your agent configuration. Each entry in the `configs` array is identified by its `name`, using the values in the Name column, and accepts an optional `type` field with the same value. The `web_search` and `web_fetch` entries accept additional settings; see [Restrict web search and web fetch domains](https://platform.claude.com/docs/en/managed-agents/tools#restrict-web-search-and-web-fetch-domains).
19The agent toolset includes the following tools. All are enabled by default when you include the toolset in the agent configuration.
2020
2121| Tool | Name | Description |
2222| ---------- | ------------ | ---------------------------------------------- |
from line 33
3333
3434## Configuring the toolset
3535
36Enable the full toolset with `agent_toolset_20260401` when creating an agent. Use the `configs` array to disable specific tools or override their settings. Each config entry can also set a `permission_policy` that controls whether the tool's calls run without confirmation, require confirmation, or are evaluated individually by the server. See [Permission policies](https://platform.claude.com/docs/en/managed-agents/permission-policies) for the available policy types.
37
38Config entries for `web_search` and `web_fetch` also accept domain filters and other web settings; see [Restrict web search and web fetch domains](https://platform.claude.com/docs/en/managed-agents/tools#restrict-web-search-and-web-fetch-domains).
36Enable the full toolset with `agent_toolset_20260401` when creating an agent. Use the `configs` array to disable specific tools or override their settings. Each entry is identified by its `name`, which takes a value from the Name column in [Available tools](https://platform.claude.com/docs/en/managed-agents/tools#available-tools). An entry also accepts an optional `type` field with the same value.
37
38Each config entry can also set a `permission_policy`. The policy controls whether the tool's calls run without confirmation, require confirmation, or are evaluated individually by the server. See [Permission policies](https://platform.claude.com/docs/en/managed-agents/permission-policies) for the available policy types.
39
40The following example enables the toolset and disables `web_fetch`:
3941
4042<CodeGroup defaultLanguage="CLI">
4143 ```bash cURL
from line 206
204206
205207### Disabling specific tools
206208
207To disable a tool, set `enabled: false` in its config entry in the toolset object of your agent's `tools` array:
209To disable a tool, set `enabled: false` in its `configs` entry:
208210
209211```json
210212{
from line 234
232234}
233235```
234236
235### Restrict web search and web fetch domains
236
237To control which sites the agent's web tools can reach, set `allowed_domains` (the tool can reach only these hosts) or `blocked_domains` (the tool can never reach these hosts) on the `web_search` and `web_fetch` entries of the toolset's `configs` array. Each tool carries its own list, so `web_search` and `web_fetch` can have different restrictions. A listed domain covers that host and all of its subdomains. At runtime, a `web_fetch` call for a URL that its lists do not permit returns an error result to the agent (`is_error: true` on the `agent.tool_result` event, with content that names the error code `url_not_allowed`), and `web_search` omits results that its lists do not permit.
238
239The following toolset limits `web_search` to two sites and localizes its results, and blocks one host for `web_fetch` while capping how much fetched content enters the context:
240
241```json
242{
243 "type": "agent_toolset_20260401",
244 "configs": [
245 {
246 "type": "web_search",
247 "name": "web_search",
248 "allowed_domains": ["docs.example.com", "arxiv.org"],
249 "user_location": {
250 "type": "approximate",
251 "country": "US",
252 "timezone": "America/Los_Angeles"
253 }
254 },
255 {
256 "type": "web_fetch",
257 "name": "web_fetch",
258 "blocked_domains": ["ads.example.com"],
259 "max_content_tokens": 50000
260 }
261 ]
262}
263```
264
265<Note>
266 In the Python, TypeScript, Go, Java, C#, Ruby, and PHP SDKs, each `configs` entry is typed per tool: a union with one member per built-in tool, discriminated by `type`. `type` is optional when you construct an entry (the server infers it from `name`) and always present on responses. This typing does not change the JSON that an entry serializes to, so a request whose entries set only `name`, `enabled`, and `permission_policy` is valid with or without `type`. In SDKs where you construct entries from typed values rather than plain dictionaries or hashes (Go, Java, C#, and PHP), the element type of `configs` is the union itself: build each entry from its per-tool member type.
267</Note>
268
269The following request creates an agent with this toolset and prints the `configs` array from the response:
270
271<CodeGroup defaultLanguage="CLI">
272 ```bash cURL
273 agent=$(curl -fsSL https://api.anthropic.com/v1/agents \
274 -H "x-api-key: $ANTHROPIC_API_KEY" \
275 -H "anthropic-version: 2023-06-01" \
276 -H "anthropic-beta: managed-agents-2026-04-01" \
277 -H "content-type: application/json" \
278 -d @- <<'EOF'
279 {
280 "name": "Research Agent",
281 "model": "claude-opus-5-5",
282 "tools": [
283 {
284 "type": "agent_toolset_20260401",
285 "configs": [
286 {
287 "type": "web_search",
288 "name": "web_search",
289 "allowed_domains": ["docs.example.com", "arxiv.org"],
290 "user_location": {
291 "type": "approximate",
292 "country": "US",
293 "timezone": "America/Los_Angeles"
294 }
295 },
296 {
297 "type": "web_fetch",
298 "name": "web_fetch",
299 "blocked_domains": ["ads.example.com"],
300 "max_content_tokens": 50000
301 }
302 ]
303 }
304 ]
305 }
306 EOF
307 )
308 jq '.tools[0].configs' <<< "$agent"
309 ```
310
311 <CodeGroupItem>
312 ```bash CLI
313 ant apply agent.md
314 ```
315
316 <File filename="agent.md">
317 ```markdown
318 ---
319 name: Research Agent
320 model: claude-opus-5-5
321 tools:
322 - type: agent_toolset_20260401
323 configs:
324 - type: web_search
325 name: web_search
326 allowed_domains: [docs.example.com, arxiv.org]
327 user_location:
328 type: approximate
329 country: US
330 timezone: America/Los_Angeles
331 - type: web_fetch
332 name: web_fetch
333 blocked_domains: [ads.example.com]
334 max_content_tokens: 50000
335 ---
336 ```
337 </File>
338
339 [`ant apply`](https://platform.claude.com/docs/en/cli-sdks-libraries/cli/apply) creates the agent and prints its ID, not the `configs` array.
340 </CodeGroupItem>
341
342 ```python Python
343 client = Anthropic()
344
345 agent = client.beta.agents.create(
346 name="Research Agent",
347 model="claude-opus-5-5",
348 tools=[
349 {
350 "type": "agent_toolset_20260401",
351 "configs": [
352 {
353 "name": "web_search",
354 "allowed_domains": ["docs.example.com", "arxiv.org"],
355 "user_location": {
356 "type": "approximate",
357 "country": "US",
358 "timezone": "America/Los_Angeles",
359 },
360 },
361 {
362 "name": "web_fetch",
363 "blocked_domains": ["ads.example.com"],
364 "max_content_tokens": 50_000,
365 },
366 ],
367 }
368 ],
369 )
370
371 for tool in agent.tools:
372 if tool.type == "agent_toolset_20260401":
373 print(json.dumps([config.to_dict() for config in tool.configs], indent=2))
374 ```
375
376 ```typescript TypeScript
377 const client = new Anthropic();
378
379 const agent = await client.beta.agents.create({
380 name: "Research Agent",
381 model: "claude-opus-5-5",
382 tools: [
383 {
384 type: "agent_toolset_20260401",
385 configs: [
386 {
387 name: "web_search",
388 allowed_domains: ["docs.example.com", "arxiv.org"],
389 user_location: {
390 type: "approximate",
391 country: "US",
392 timezone: "America/Los_Angeles"
393 }
394 },
395 {
396 name: "web_fetch",
397 blocked_domains: ["ads.example.com"],
398 max_content_tokens: 50_000
399 }
400 ]
401 }
402 ]
403 });
404
405 for (const tool of agent.tools) {
406 if (tool.type === "agent_toolset_20260401") {
407 console.log(JSON.stringify(tool.configs, null, 2));
408 }
409 }
410 ```
411
412 ```csharp C#
413 using Anthropic.Models.Beta.Agents;
414
415 AnthropicClient client = new();
416
417 var agent = await client.Beta.Agents.Create(new()
418 {
419 Name = "Research Agent",
420 Model = BetaManagedAgentsModel.ClaudeOpus5_5,
421 Tools =
422 [
423 new BetaManagedAgentsAgentToolset20260401Params
424 {
425 Type = BetaManagedAgentsAgentToolset20260401ParamsType.AgentToolset20260401,
426 Configs =
427 [
428 new BetaManagedAgentsWebSearchToolConfigParams
429 {
430 AllowedDomains = ["docs.example.com", "arxiv.org"],
431 UserLocation = new()
432 {
433 Country = "US",
434 Timezone = "America/Los_Angeles",
435 },
436 },
437 new BetaManagedAgentsWebFetchToolConfigParams
438 {
439 BlockedDomains = ["ads.example.com"],
440 MaxContentTokens = 50_000,
441 },
442 ],
443 },
444 ],
445 });
446
447 JsonSerializerOptions jsonOptions = new() { WriteIndented = true };
448 foreach (var tool in agent.Tools)
449 {
450 if (tool.TryPickBetaManagedAgentsAgentToolset20260401(out var toolset))
451 {
452 Console.WriteLine(JsonSerializer.Serialize(toolset.Configs, jsonOptions));
453 }
454 }
455 ```
456
457 ```go Go
458 client := anthropic.NewClient()
459 ctx := context.Background()
460
461 agent, err := client.Beta.Agents.New(ctx, anthropic.BetaAgentNewParams{
462 Name: "Research Agent",
463 Model: anthropic.BetaManagedAgentsModelConfigParams{
464 ID: anthropic.BetaManagedAgentsModelClaudeOpus5_5,
465 },
466 Tools: []anthropic.BetaAgentNewParamsToolUnion{{
467 OfAgentToolset20260401: &anthropic.BetaManagedAgentsAgentToolset20260401Params{
468 Type: anthropic.BetaManagedAgentsAgentToolset20260401ParamsTypeAgentToolset20260401,
469 Configs: []anthropic.BetaManagedAgentsAgentToolConfigParamsUnion{
470 {OfWebSearch: &anthropic.BetaManagedAgentsWebSearchToolConfigParams{
471 AllowedDomains: []string{"docs.example.com", "arxiv.org"},
472 UserLocation: anthropic.BetaManagedAgentsUserLocationParam{
473 Country: anthropic.String("US"),
474 Timezone: anthropic.String("America/Los_Angeles"),
475 },
476 }},
477 {OfWebFetch: &anthropic.BetaManagedAgentsWebFetchToolConfigParams{
478 BlockedDomains: []string{"ads.example.com"},
479 MaxContentTokens: anthropic.Int(50000),
480 }},
481 },
482 },
483 }},
484 })
485 if err != nil {
486 panic(err)
487 }
488
489 for _, tool := range agent.Tools {
490 switch toolset := tool.AsAny().(type) {
491 case anthropic.BetaManagedAgentsAgentToolset20260401:
492 configs := make([]json.RawMessage, len(toolset.Configs))
493 for i, config := range toolset.Configs {
494 configs[i] = json.RawMessage(config.RawJSON())
495 }
496 output, err := json.MarshalIndent(configs, "", " ")
497 if err != nil {
498 panic(err)
499 }
500 fmt.Println(string(output))
501 }
502 }
503 ```
504
505 ```java Java
506 import com.anthropic.models.beta.agents.AgentCreateParams;
507 import com.anthropic.models.beta.agents.BetaManagedAgentsAgentToolset20260401Params;
508 import com.anthropic.models.beta.agents.BetaManagedAgentsModel;
509 import com.anthropic.models.beta.agents.BetaManagedAgentsUserLocation;
510 import com.anthropic.models.beta.agents.BetaManagedAgentsWebFetchToolConfigParams;
511 import com.anthropic.models.beta.agents.BetaManagedAgentsWebSearchToolConfigParams;
512
513 void main() {
514 var client = AnthropicOkHttpClient.fromEnv();
515
516 var agent = client.beta().agents().create(AgentCreateParams.builder()
517 .name("Research Agent")
518 .model(BetaManagedAgentsModel.CLAUDE_OPUS_5_5)
519 .addTool(BetaManagedAgentsAgentToolset20260401Params.builder()
520 .type(BetaManagedAgentsAgentToolset20260401Params.Type.AGENT_TOOLSET_20260401)
521 .addConfig(BetaManagedAgentsWebSearchToolConfigParams.builder()
522 .allowedDomains(List.of("docs.example.com", "arxiv.org"))
523 .userLocation(BetaManagedAgentsUserLocation.builder()
524 .country("US")
525 .timezone("America/Los_Angeles")
526 .build())
527 .build())
528 .addConfig(BetaManagedAgentsWebFetchToolConfigParams.builder()
529 .blockedDomains(List.of("ads.example.com"))
530 .maxContentTokens(50_000)
531 .build())
532 .build())
533 .build());
534
535 for (var tool : agent.tools()) {
536 if (tool.isAgentToolset20260401()) {
537 var configs = tool.asAgentToolset20260401().configs();
538 IO.println(ObjectMappers.jsonMapper().valueToTree(configs));
539 }
540 }
541 }
542 ```
543
544 ```php PHP
545 use Anthropic\Beta\Agents\BetaManagedAgentsAgentToolset20260401;
546 use Anthropic\Beta\Agents\BetaManagedAgentsAgentToolset20260401Params;
547 use Anthropic\Beta\Agents\BetaManagedAgentsUserLocation;
548 use Anthropic\Beta\Agents\BetaManagedAgentsWebFetchToolConfigParams;
549 use Anthropic\Beta\Agents\BetaManagedAgentsWebSearchToolConfigParams;
550 // ...
551
552 $client = new Client();
553
554 $agent = $client->beta->agents->create(
555 name: 'Research Agent',
556 model: 'claude-opus-5-5',
557 tools: [
558 BetaManagedAgentsAgentToolset20260401Params::with(
559 type: 'agent_toolset_20260401',
560 configs: [
561 BetaManagedAgentsWebSearchToolConfigParams::with(
562 allowedDomains: ['docs.example.com', 'arxiv.org'],
563 userLocation: BetaManagedAgentsUserLocation::with(
564 country: 'US',
565 timezone: 'America/Los_Angeles',
566 ),
567 ),
568 BetaManagedAgentsWebFetchToolConfigParams::with(
569 blockedDomains: ['ads.example.com'],
570 maxContentTokens: 50_000,
571 ),
572 ],
573 ),
574 ],
575 );
576
577 foreach ($agent->tools as $tool) {
578 if ($tool instanceof BetaManagedAgentsAgentToolset20260401) {
579 echo json_encode($tool->configs, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES), PHP_EOL;
580 }
581 }
582 ```
583
584 ```ruby Ruby
585 client = Anthropic::Client.new
586
587 agent = client.beta.agents.create(
588 name: "Research Agent",
589 model: "claude-opus-5-5",
590 tools: [
591 {
592 type: :agent_toolset_20260401,
593 configs: [
594 {
595 name: :web_search,
596 allowed_domains: ["docs.example.com", "arxiv.org"],
597 user_location: {type: :approximate, country: "US", timezone: "America/Los_Angeles"}
598 },
599 {
600 name: :web_fetch,
601 blocked_domains: ["ads.example.com"],
602 max_content_tokens: 50_000
603 }
604 ]
605 }
606 ]
607 )
608
609 case agent.tools.first
610 in Anthropic::Models::Beta::BetaManagedAgentsAgentToolset20260401 => toolset
611 puts JSON.pretty_generate(toolset.configs.map(&:to_h))
612 end
613 ```
614</CodeGroup>
615
616In the Claude Console, set allowed or blocked domains from the `web_search` and `web_fetch` rows of the **Built-in tools** card on the agent form; set `max_content_tokens` and `user_location` in the **Raw** view of the agent's configuration.
617
618In addition to `enabled` and `permission_policy`, the web tool entries accept the following settings:
619
620| Setting | Applies to | Description |
621| -------------------- | ------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
622| `allowed_domains` | `web_search`, `web_fetch` | The only hosts the tool can reach. Cannot be combined with `blocked_domains` on the same entry. |
623| `blocked_domains` | `web_search`, `web_fetch` | Hosts the tool cannot reach. |
624| `max_content_tokens` | `web_fetch` | Caps the amount of fetched page content included in the context. Must be a positive integer. See [content limits](https://platform.claude.com/docs/en/agents-and-tools/tool-use/web-fetch-tool#content-limits). |
625| `user_location` | `web_search` | Localizes search results. An object with the same fields as the Messages API [`user_location`](https://platform.claude.com/docs/en/agents-and-tools/tool-use/web-search-tool#localization) parameter. |
626
627<Note>
628 An environment's [`networking`](https://platform.claude.com/docs/en/managed-agents/environments#networking) settings control the sandbox's own outbound traffic. They do not affect `web_search` or `web_fetch`, which run on Anthropic's servers whether the environment is a cloud or self-hosted sandbox. The per-tool `allowed_domains` and `blocked_domains` lists are the way to restrict what these tools can reach.
629</Note>
630
631<Note>
632 Organization-level web search and web fetch settings in the Claude Console apply to the Messages API and do not apply to Managed Agents sessions. To restrict an agent's web tools, configure `allowed_domains` or `blocked_domains` on its toolset instead.
633</Note>
634
635#### Domain list rules
636
637* Set either `allowed_domains` or `blocked_domains` on an entry, not both. An entry that sets both is rejected.
638* Each list holds 1 to 64 domains, each 1 to 255 characters. An empty list is rejected: to apply no restriction, omit the field or send `null`.
639* Each domain is a registrable domain name, or a subdomain of one, written as a plain hostname: ASCII letters, digits, hyphens, underscores, and dots, with no scheme, port, credentials, wildcard, or whitespace, no label that begins or ends with a hyphen, and no path other than the optional `web_search` path suffix described later in this list. Use `example.com`, not `https://example.com`, `example.com:443`, or `*.example.com`. Hostnames are compared without regard to case, and a single trailing `/` is ignored.
640* A listed domain matches that host and its subdomains: `example.com` covers `docs.example.com`, but `docs.example.com` does not cover `example.com` or `api.example.com`. A leading `www.` is a subdomain like any other, so `www.example.com` does not cover `example.com`; list the bare domain to cover both.
641* IP addresses are not accepted in any form, whether IPv4, IPv6, bracketed, or numeric shorthand such as `127.1`. List the site's domain name instead.
642* A bare top-level domain or registry suffix such as `com`, `co.uk`, or `gov.uk` is rejected, and so is a single-label name such as `intranet`. List a full domain such as `example.co.uk`.
643* `localhost` and hosts ending in `.localhost`, `.local`, `.internal`, `.localdomain`, or `.invalid` are rejected.
644* Use the `xn--` (Punycode) form for internationalized domain names; a domain that contains non-ASCII characters is rejected.
645* A `web_fetch` domain cannot include a path: use `example.com`, not `example.com/*`. A `web_search` domain can carry a path suffix such as `example.com/blog`, in which the path cannot contain spaces, `?`, `#`, or any of the characters `$ , | ^ !`. Prefer plain hostnames for `web_search` too, because the search provider matches path suffixes as URL patterns rather than as strict host rules.
646* Duplicate domains within a list are rejected. `www.example.com` and `example.com` count as different domains; see the earlier matching rule for what each covers.
647
648#### When settings are validated
649
650Format and limit violations are rejected with a 400 `invalid_request_error` when you [create an agent](https://platform.claude.com/docs/en/managed-agents/agent-setup#create-an-agent) or [update an agent](https://platform.claude.com/docs/en/managed-agents/agent-setup#update-an-agent), and when you create or update a session that supplies `tools`. For example, the message for an entry that sets both lists includes `Only one of allowed_domains or blocked_domains may be set.`, and the message for an empty list includes `allowed_domains: Empty list of domains is ambiguous. Provide at least one domain or null.` The message for a domain that breaks a format rule names its list and zero-based position, for example `allowed_domains.0: IP addresses are not supported; provide a plain hostname like "example.com"`.
651
652The same requests also reject three settings that depend on the search and fetch providers: a domain in `allowed_domains` that Anthropic's crawler is not permitted to access, a `user_location.country` that the search provider does not support (the message ends in `user_location.country: not a country the search provider supports`), and a `user_location.timezone` that is not a valid IANA name. The session checks the configuration again when it first initializes the tool; if a setting that was accepted earlier is no longer valid at that point, the session emits a [`session.error`](https://platform.claude.com/docs/en/managed-agents/events-and-streaming) event and returns to `idle` without retrying. Fix the setting by [updating the session's tools](https://platform.claude.com/docs/en/managed-agents/session-operations#updating-the-agent-configuration), update the agent as well so that new sessions start with the corrected configuration, then send a new `user.message` to continue.
653
654#### Multiagent sessions, outcomes, and mid-session updates
655
656In a [multiagent session](https://platform.claude.com/docs/en/managed-agents/multiagent-orchestration), every domain list that applies to a thread is enforced at the same time: an agent in the roster of the coordinator is bound by its own `allowed_domains` and `blocked_domains`, by those of any agent that called it, and by the coordinator's current lists.
657
658* Allowlists combine to the domains that all of them cover, and blocklists add together, so a roster agent can narrow what a tool reaches but never widen it. For example, a roster agent that sets `blocked_domains` keeps the coordinator's `allowed_domains` and blocks those hosts within it, and a roster agent that sets its own `allowed_domains` can reach only the hosts that both its list and the coordinator's list cover.
659* If the combined allowlists have no domain in common, the tool stays available to that agent but every call fails with a `url_not_allowed` error stating that no domain is permitted, and the tool description tells the model so. Keep each roster agent's allowlist inside the coordinator's to avoid this.
660* `max_content_tokens` and `user_location` are not combined: a thread uses the value from its own tool configuration if set, otherwise from the agent that called it, otherwise from the coordinator's current configuration.
661* A `{"type": "self"}` roster entry has no web settings of its own and follows the coordinator's current settings.
662* The grader in [outcome-driven sessions](https://platform.claude.com/docs/en/managed-agents/define-outcomes) runs without `web_search` and `web_fetch`, regardless of these settings.
663* You can change the lists on an idle session by [updating its tools](https://platform.claude.com/docs/en/managed-agents/session-operations#updating-the-agent-configuration). The new lists apply to the rest of the session; in a multiagent session, every thread applies them from its next turn, while a roster agent's own lists stay as its agent definition set them when the session was created.
664
665#### Differences from the Messages API tools
666
667These settings use the same `allowed_domains` and `blocked_domains` vocabulary as [domain filtering](https://platform.claude.com/docs/en/agents-and-tools/tool-use/server-tools#domain-filtering) on the Messages API server tools, with the following differences on Managed Agents:
668
669* Each list is capped at 64 domains.
670* Domains listed for `web_fetch` cannot include a path.
671* Domains must be ASCII: use the `xn--` (Punycode) form for internationalized domain names. The Messages API accepts Unicode entries, though it recommends against them.
672* `max_uses`, `citations`, and `cache_control` are not available on the toolset.
237### Restricting web search and web fetch
238
239The `web_search` and `web_fetch` entries also accept domain lists, a cap on fetched content, and a search location. See [Restrict web search and web fetch domains](https://platform.claude.com/docs/en/managed-agents/tools-web-restrictions).
240
241### Config entry types in the SDKs
242
243The Python, TypeScript, Go, Java, C#, Ruby, and PHP SDKs type each `configs` entry per tool. The type is a union with one member per built-in tool, discriminated by `type`.
244
245In Go, Java, C#, and PHP, you construct entries from typed values rather than plain dictionaries or hashes. In those SDKs, the element type of `configs` is the union itself, so build each entry from its per-tool member type.
246
247You can omit `type` when you construct an entry, because the server infers it from `name`. Responses always include it. This typing does not change the JSON that an entry serializes to. A request whose entries set only `name`, `enabled`, and `permission_policy` is valid with or without `type`.
673248
674249## Custom tools
675250
676In addition to built-in tools, you can define custom tools. Custom tools are analogous to [user-defined client tools](https://platform.claude.com/docs/en/agents-and-tools/tool-use/how-tool-use-works#user-defined-tools-client-executed) in the Messages API.
677
678Each custom tool defines a contract: you specify what operations are available and what they return, and Claude determines when and how to call them. The model never executes anything on its own. It emits a structured request, your code runs the operation, and the result flows back into the conversation. See [Session event stream](https://platform.claude.com/docs/en/managed-agents/events-and-streaming#handling-custom-tool-calls) for how to receive custom tool calls and return results during a session.
679
680If your sessions run in a self-hosted sandbox, the environment worker can [serve custom tools from your sandbox](https://platform.claude.com/docs/en/managed-agents/self-hosted-sandboxes-custom-tools), including tools that wrap an MCP server inside your network.
251Custom tools are analogous to [user-defined client tools](https://platform.claude.com/docs/en/agents-and-tools/tool-use/how-tool-use-works#user-defined-tools-client-executed) in the Messages API. You specify what operations are available and what they return, and Claude determines when and how to call them.
252
253Claude does not run a custom tool itself. It emits a structured request, your code runs the operation, and you return the result to the session.
254
255The following example creates an agent with the built-in toolset and one custom tool, `get_weather`:
681256
682257<CodeGroup defaultLanguage="CLI">
683258 ```bash cURL
from line 498
923498 ```
924499</CodeGroup>
925500
926Once you've defined custom tools on the agent, the agent invokes them during a session.
501The agent calls its custom tools during a session. To receive the calls and return results, see [Session event stream](https://platform.claude.com/docs/en/managed-agents/events-and-streaming#handling-custom-tool-calls).
502
503For sessions that run in a self-hosted sandbox, the environment worker can [serve custom tools from the sandbox](https://platform.claude.com/docs/en/managed-agents/self-hosted-sandboxes-custom-tools). These can include tools that wrap an MCP server inside your network.
927504
928505### Best practices for custom tool definitions
929506
930* **Provide extremely detailed descriptions.** This is by far the most important factor in tool performance. Your descriptions should explain what the tool does and when to use it (and when not to). Explain what each parameter means and how it affects the tool's behavior. Call out any important caveats or limitations. The more context you can give Claude about your tools, the better it is at determining when and how to use them. Aim for three to four sentences for each tool description, more if the tool is complex.
931* **Consolidate related operations into fewer tools.** Rather than creating a separate tool for every action (`create_pr`, `review_pr`, `merge_pr`), group them into a single tool with an `action` parameter. Fewer, more capable tools reduce selection ambiguity and make your tool surface easier for Claude to navigate.
507* **Provide extremely detailed descriptions.** This is by far the most important factor in tool performance. The more context you can give Claude about your tools, the better it is at determining when and how to use them. Aim for three to four sentences for each tool description, more if the tool is complex. Your descriptions should cover:
508
509 * What the tool does
510 * When to use it, and when not to
511 * What each parameter means and how it affects the tool's behavior
512 * Any important caveats or limitations
513
514* **Consolidate related operations into fewer tools.** Group actions such as `create_pr`, `review_pr`, and `merge_pr` into a single tool with an `action` parameter. Fewer, more capable tools reduce selection ambiguity and make your tool surface easier for Claude to navigate.
515
932516* **Use meaningful namespacing in tool names.** When your tools span multiple services or resources, prefix names with the resource (for example, `db_query` or `storage_read`). This makes tool selection unambiguous as your library grows.
933* **Design tool responses to return only high-signal information.** Return semantic, stable identifiers (for example, slugs or UUIDs) rather than opaque internal references, and include only the fields Claude needs to determine its next step. Bloated responses waste context and make it harder for Claude to extract what matters.
517
518* **Design tool responses to return only high-signal information.** Return semantic, stable identifiers (for example, slugs or UUIDs) rather than opaque internal references. Include only the fields Claude needs to determine its next step. Bloated responses waste context and make it harder for Claude to extract what matters.
934519
935520## Next steps
936521
937522<CardGroup cols={2}>
523 <Card title="Restrict web search and web fetch domains" icon="shield" href="https://platform.claude.com/docs/en/managed-agents/tools-web-restrictions">
524 Control which sites the web tools can reach, cap fetched content, and localize search results.
525 </Card>
526
938527 <Card title="MCP connector" icon="link" href="https://platform.claude.com/docs/en/managed-agents/mcp-connector">
939528 Connect MCP servers to your agents for access to external tools and data sources.
940529 </Card>
941530