self-hosted-sandboxes-custom-tools changedmanaged-agents/self-hosted-sandboxes-custom-tools
Nearest release: v2.1.301, published 5 hours before this site recorded the change. Shown because the two are within 24 hours of each other. Nothing here says the release caused the edit.
Recorded here
Lines+209added
Lines−45removed
From line
43
where the diff opens
First seen
2 Oct 2026
this site's first read of the page
Recorded edits8to this page, all time
The whole hunk
from line 43, old and new numbered
/
from line 43
4343 <Step title="Register the implementation with the worker">
4444 Pass the tool through the worker's `tools` (go: `ToolsFunc`) factory (see [`EnvironmentWorker`](https://platform.claude.com/docs/en/managed-agents/self-hosted-sandboxes-reference#environment-worker)), alongside the built-in toolset:
4545
46 <CodeGroup exclude="shell">
46 <CodeGroup exclude="shell, csharp, java, php, ruby">
4747 ```python Python
4848 import asyncio
4949 import os
from line 109
109109 }).run();
110110 ```
111111
112 ```csharp C#
113 // EnvironmentWorker is not currently available in the C# SDK.
114 // To answer custom tool calls directly, see the session event stream.
115 ```
112 <CodeGroupItem language="C#">
113 The C# SDK does not have `EnvironmentWorker`. To answer custom tool calls from your own code, send each result on the session's event stream, as in [Return a custom tool result](https://platform.claude.com/docs/en/managed-agents/events-and-streaming#return-a-custom-tool-result).
114 </CodeGroupItem>
116115
117116 ```go Go
118117 package main
from line 175
176175
177176 ```
178177
179 ```java Java
180 // EnvironmentWorker is not currently available in the Java SDK.
181 // To answer custom tool calls directly, see the session event stream.
182 ```
178 <CodeGroupItem language="Java">
179 The Java SDK does not have `EnvironmentWorker`. To answer custom tool calls from your own code, send each result on the session's event stream, as in [Return a custom tool result](https://platform.claude.com/docs/en/managed-agents/events-and-streaming#return-a-custom-tool-result).
180 </CodeGroupItem>
183181
184 ```php PHP
185 // EnvironmentWorker is not currently available in the PHP SDK.
186 // To answer custom tool calls directly, see the session event stream.
187 ```
182 <CodeGroupItem language="PHP">
183 The PHP SDK does not have `EnvironmentWorker`. To answer custom tool calls from your own code, send each result on the session's event stream, as in [Return a custom tool result](https://platform.claude.com/docs/en/managed-agents/events-and-streaming#return-a-custom-tool-result).
184 </CodeGroupItem>
188185
189 ```ruby Ruby
190 # EnvironmentWorker is not currently available in the Ruby SDK.
191 # To answer custom tool calls directly, see the session event stream.
192 ```
186 <CodeGroupItem language="Ruby">
187 The Ruby SDK does not have `EnvironmentWorker`. To answer custom tool calls from your own code, send each result on the session's event stream, as in [Return a custom tool result](https://platform.claude.com/docs/en/managed-agents/events-and-streaming#return-a-custom-tool-result).
188 </CodeGroupItem>
193189 </CodeGroup>
194190 </Step>
195191</Steps>
from line 206
210206
211207### Install an MCP SDK
212208
213The SDK's [Client-side MCP helpers](https://platform.claude.com/docs/en/agents-and-tools/mcp-connector#client-side-mcp-helpers) convert the server's tools into the runnable tools the worker accepts. Install an MCP SDK alongside the Anthropic SDK: `pip install "anthropic[mcp]" "mcp>=1.24"` (typescript: `npm install @modelcontextprotocol/sdk`; go: `go get github.com/modelcontextprotocol/go-sdk`).
209The SDK's [Client-side MCP helpers](https://platform.claude.com/docs/en/agents-and-tools/mcp-connector#client-side-mcp-helpers) convert the server's tools into the runnable tools the worker accepts. Install an MCP SDK alongside the Anthropic SDK: `pip install "anthropic[mcp]" "mcp>=1.24"` (typescript: `npm install @modelcontextprotocol/sdk`; go: `go get github.com/modelcontextprotocol/go-sdk`; csharp: `dotnet add package ModelContextProtocol`; java: `com.anthropic:anthropic-java-mcp`; php: `composer require mcp/sdk`; ruby: `bundle add mcp faraday event_stream_parser`).
214210
215The examples connect without authentication. To send credentials, configure the `http_client` (typescript: `requestInit`; go: `HTTPClient`) you hand to the MCP transport.
211The examples connect without authentication. To send credentials, configure the `http_client` (typescript: `requestInit`; go: `HTTPClient`; csharp: `AdditionalHeaders`; java: `customizeRequest`; php, ruby: `headers`) you hand to the MCP transport.
216212
217213### Declare and serve the tools
218214
from line 299
303299 ```
304300
305301 ```csharp C#
306 // See the Python, TypeScript, and Go tabs. Declaring custom tools from
307 // C# works the same way once you list the server's tools with an MCP client.
302 using System.Text.Json;
303 using Anthropic;
304 using Anthropic.Models.Beta.Agents;
305 using ModelContextProtocol.Client;
306
307 const string McpServerUrl = "http://mcp.internal.example.com:8000/mcp";
308
309 // Run this wherever you create agents, not on the worker host: it
310 // authenticates with your Claude API key (ANTHROPIC_API_KEY).
311 var client = new AnthropicClient();
312
313 await using var mcpClient = await McpClient.CreateAsync(
314 new HttpClientTransport(new HttpClientTransportOptions { Endpoint = new Uri(McpServerUrl) })
315 );
316 var mcpTools = await mcpClient.ListToolsAsync();
317
318 List<Tool> tools =
319 [
320 new BetaManagedAgentsAgentToolset20260401Params { Type = "agent_toolset_20260401" },
321 ];
322 foreach (var tool in mcpTools)
323 {
324 var description = tool.Description;
325 if (string.IsNullOrEmpty(description))
326 {
327 description = tool.Name;
328 }
329
330 // The MCP fields map one to one onto a custom tool declaration. The
331 // schema's keywords pass to the SDK unchanged.
332 tools.Add(
333 new BetaManagedAgentsCustomToolParams
334 {
335 Type = "custom",
336 Name = tool.Name,
337 Description = description,
338 InputSchema = new(
339 tool.ProtocolTool.InputSchema.Deserialize<Dictionary<string, JsonElement>>()!
340 ),
341 }
342 );
343 }
344
345 var agent = await client.Beta.Agents.Create(new()
346 {
347 Name = "Internal tools agent",
348 Model = BetaManagedAgentsModel.ClaudeOpus5_5,
349 Tools = tools,
350 });
351 Console.WriteLine(agent.ID);
308352 ```
309353
310354 ```go Go
from line 460
416460 ```
417461
418462 ```java Java
419 // See the Python, TypeScript, and Go tabs. Declaring custom tools from
420 // Java works the same way once you list the server's tools with an MCP client.
463 import com.anthropic.client.okhttp.AnthropicOkHttpClient;
464 import com.anthropic.core.JsonValue;
465 import com.anthropic.models.beta.agents.AgentCreateParams;
466 import com.anthropic.models.beta.agents.BetaManagedAgentsAgentToolset20260401Params;
467 import com.anthropic.models.beta.agents.BetaManagedAgentsCustomToolInputSchema;
468 import com.anthropic.models.beta.agents.BetaManagedAgentsCustomToolParams;
469 import com.anthropic.models.beta.agents.BetaManagedAgentsModel;
470 import io.modelcontextprotocol.client.McpClient;
471 import io.modelcontextprotocol.client.transport.HttpClientStreamableHttpTransport;
472 import io.modelcontextprotocol.spec.McpSchema;
473
474 // The MCP fields map one to one onto a custom tool declaration. The MCP Java
475 // SDK keeps only these top-level keywords of a tool's input schema: type,
476 // properties, required, additionalProperties, $defs and definitions.
477 BetaManagedAgentsCustomToolParams toCustomTool(McpSchema.Tool tool) {
478 var description = tool.description();
479 if (description == null || description.isEmpty()) {
480 description = tool.name();
481 }
482
483 return BetaManagedAgentsCustomToolParams.builder()
484 .type(BetaManagedAgentsCustomToolParams.Type.CUSTOM)
485 .name(tool.name())
486 .description(description)
487 .inputSchema(JsonValue.from(tool.inputSchema())
488 .convert(BetaManagedAgentsCustomToolInputSchema.class))
489 .build();
490 }
491
492 void main() {
493 // Run this wherever you create agents, not on the worker host: it
494 // authenticates with your Claude API key (ANTHROPIC_API_KEY).
495 var client = AnthropicOkHttpClient.fromEnv();
496
497 // The MCP server's address, then the path of its MCP endpoint.
498 var transport = HttpClientStreamableHttpTransport.builder("http://mcp.internal.example.com:8000")
499 .endpoint("/mcp")
500 .build();
501 try (var mcpClient = McpClient.sync(transport)
502 .clientInfo(new McpSchema.Implementation("declare-agent-tools", "1.0.0"))
503 .build()) {
504 mcpClient.initialize();
505
506 var params = AgentCreateParams.builder()
507 .name("Internal tools agent")
508 .model(BetaManagedAgentsModel.CLAUDE_OPUS_5_5)
509 .addTool(BetaManagedAgentsAgentToolset20260401Params.builder()
510 .type(BetaManagedAgentsAgentToolset20260401Params.Type.AGENT_TOOLSET_20260401)
511 .build());
512 for (var tool : mcpClient.listTools().tools()) {
513 params.addTool(toCustomTool(tool));
514 }
515
516 var agent = client.beta().agents().create(params.build());
517 IO.println(agent.id());
518 }
519 }
421520 ```
422521
423522 ```php PHP
424 // See the Python, TypeScript, and Go tabs. Declaring custom tools from
425 // PHP works the same way once you list the server's tools with an MCP client.
523 use Anthropic\Beta\Agents\BetaManagedAgentsModel;
524 use Anthropic\Client as Anthropic;
525 use Mcp\Client as McpClient;
526 use Mcp\Client\Transport\HttpTransport;
527
528 $mcpServerUrl = 'http://mcp.internal.example.com:8000/mcp';
529
530 // Run this wherever you create agents, not on the worker host: it
531 // authenticates with your Claude API key (ANTHROPIC_API_KEY).
532 $client = new Anthropic();
533
534 $mcpClient = McpClient::builder()->build();
535 $mcpClient->connect(new HttpTransport($mcpServerUrl));
536
537 $tools = [['type' => 'agent_toolset_20260401']];
538 foreach ($mcpClient->listTools()->tools as $tool) {
539 // The MCP fields map one to one onto a custom tool declaration. The
540 // schema's keywords pass to the SDK unchanged.
541 $tools[] = [
542 'type' => 'custom',
543 'name' => $tool->name,
544 'description' => $tool->description ?: $tool->name,
545 'inputSchema' => $tool->inputSchema,
546 ];
547 }
548
549 $agent = $client->beta->agents->create(
550 name: 'Internal tools agent',
551 model: BetaManagedAgentsModel::CLAUDE_OPUS_5_5,
552 tools: $tools,
553 );
554 echo $agent->id, "\n";
555
556 $mcpClient->disconnect();
426557 ```
427558
428559 ```ruby Ruby
429 # See the Python, TypeScript, and Go tabs. Declaring custom tools from
430 # Ruby works the same way once you list the server's tools with an MCP client.
560 require "anthropic"
561 require "mcp"
562
563 MCP_SERVER_URL = "http://mcp.internal.example.com:8000/mcp"
564
565 # Run this wherever you create agents, not on the worker host: it
566 # authenticates with your Claude API key (ANTHROPIC_API_KEY).
567 client = Anthropic::Client.new
568
569 transport = MCP::Client::HTTP.new(url: MCP_SERVER_URL)
570 mcp_client = MCP::Client.new(transport: transport)
571 mcp_client.connect
572
573 custom_tools = mcp_client.tools.map do |tool|
574 description = tool.description
575 description = tool.name if description.nil? || description.empty?
576
577 # The MCP fields map one to one onto a custom tool declaration. The
578 # schema's keywords pass to the SDK unchanged.
579 {
580 type: :custom,
581 name: tool.name,
582 description: description,
583 input_schema: tool.input_schema
584 }
585 end
586
587 agent = client.beta.agents.create(
588 name: "Internal tools agent",
589 model: Anthropic::Beta::BetaManagedAgentsModel::CLAUDE_OPUS_5_5,
590 tools: [{type: :agent_toolset_20260401}, *custom_tools]
591 )
592 puts agent.id
593
594 transport.close
431595 ```
432596 </CodeGroup>
433597 </Step>
from line 599
435599 <Step title="Serve the tools from the worker">
436600 Connect to the same MCP server at startup, convert its tools with `async_mcp_tool` (typescript: `mcpTools`; go: `mcp.NewBetaTools`), and register them alongside `beta_agent_toolset_20260401` (typescript: `betaAgentToolset20260401`; go: `agenttoolset.BetaAgentToolset20260401`). Keep one MCP session open for the life of the worker.
437601
438 <CodeGroup exclude="shell">
602 <CodeGroup exclude="shell, csharp, java, php, ruby">
439603 ```python Python
440604 import asyncio
441605 import os
from line 683
519683 }).run();
520684 ```
521685
522 ```csharp C#
523 // EnvironmentWorker is not currently available in the C# SDK.
524 ```
686 <CodeGroupItem language="C#">
687 The C# SDK does not have `EnvironmentWorker`.
688 </CodeGroupItem>
525689
526690 ```go Go
527691 package main
from line 749
585749
586750 ```
587751
588 ```java Java
589 // EnvironmentWorker is not currently available in the Java SDK.
590 ```
752 <CodeGroupItem language="Java">
753 The Java SDK does not have `EnvironmentWorker`.
754 </CodeGroupItem>
591755
592 ```php PHP
593 // EnvironmentWorker is not currently available in the PHP SDK.
594 ```
756 <CodeGroupItem language="PHP">
757 The PHP SDK does not have `EnvironmentWorker`.
758 </CodeGroupItem>
595759
596 ```ruby Ruby
597 # EnvironmentWorker is not currently available in the Ruby SDK.
598 ```
760 <CodeGroupItem language="Ruby">
761 The Ruby SDK does not have `EnvironmentWorker`.
762 </CodeGroupItem>
599763 </CodeGroup>
600764 </Step>
601765</Steps>
No line in this hunk matches that.