---
title: 自行託管沙箱中的自訂工具
url: https://platform.claude.com/docs/zh-TW/managed-agents/self-hosted-sandboxes-custom-tools
description: 從自行託管的沙箱 worker 提供自訂工具，並將您網路內的 MCP 伺服器包裝為自訂工具，而無需執行通道。
featureMetadata:
  topic:
    title: Managed Agents
    url: https://platform.claude.com/docs/en/managed-agents/overview
  status: beta
  betaHeader: managed-agents-2026-04-01
---

[自訂工具](https://platform.claude.com/docs/zh-TW/managed-agents/tools#custom-tools)是由您自己的程式碼執行的工具：代理會發出 `agent.custom_tool_use` 事件，並等待相符的 `user.custom_tool_result`。這段程式碼可以是您的 worker。由於它在您的沙箱內執行，該工具可以存取您為沙箱設定的內部服務、憑證和網路出口（network egress），除此之外別無其他。

環境金鑰授權發布自訂工具結果，因此您的 Claude API 金鑰不會出現在 worker 主機上。

<Note>
  提供自訂工具需要使用 SDK worker。`ant` CLI worker 無法註冊自訂工具實作。在每個工作階段一個沙箱（sandbox-per-session）的模式中，請[在沙箱內執行 SDK worker](https://platform.claude.com/docs/zh-TW/managed-agents/self-hosted-sandboxes-workers#run-the-sdk-worker-inside-the-sandbox)。
</Note>

## 提供自訂工具

<Steps>
  <Step title="在代理上宣告工具">
    在代理的 `tools` 中新增一個 `custom` 項目，其 `name` 須與您的 worker 所註冊的工具相符。完整的宣告結構請參閱[自訂工具](https://platform.claude.com/docs/zh-TW/managed-agents/tools#custom-tools)。

    ```json
    {
      "type": "custom",
      "name": "get_order_status",
      "description": "Look up an order in the internal fulfillment system by order ID.",
      "input_schema": {
        "type": "object",
        "properties": {
          "order_id": { "type": "string", "description": "The order ID" }
        },
        "required": ["order_id"]
      }
    }
    ```
  </Step>

  <Step title="向 worker 註冊實作">
    透過 worker 的 `tools` (go: `ToolsFunc`) 工廠（請參閱 [`EnvironmentWorker`](https://platform.claude.com/docs/zh-TW/managed-agents/self-hosted-sandboxes-reference#environment-worker)）傳入工具，並與內建工具集一起使用：

    <CodeGroup exclude="shell, csharp, java, php, ruby">
      ```python Python
      import asyncio
      import os
      from anthropic import AsyncAnthropic, beta_async_tool
      from anthropic.lib.environments import EnvironmentWorker
      from anthropic.lib.tools.agent_toolset import beta_agent_toolset_20260401


      @beta_async_tool
      async def get_order_status(order_id: str) -> str:
          """Look up an order in the internal fulfillment system by order ID."""
          # 在 worker 主機上執行：可呼叫沙箱能存取的任何資源。
          return f"Order {order_id}: shipped"


      async def main() -> None:
          environment_key = os.environ["ANTHROPIC_ENVIRONMENT_KEY"]
          environment_id = os.environ["ANTHROPIC_ENVIRONMENT_ID"]
          async with AsyncAnthropic(auth_token=environment_key) as client:
              await EnvironmentWorker(
                  client,
                  environment_id=environment_id,
                  environment_key=environment_key,
                  workdir="/workspace",
                  tools=lambda env: [*beta_agent_toolset_20260401(env), get_order_status],
              ).run()


      asyncio.run(main())
      ```

      ```typescript TypeScript
      import Anthropic from "@anthropic-ai/sdk";
      import { EnvironmentWorker } from "@anthropic-ai/sdk/helpers/beta/environments";
      import { betaTool } from "@anthropic-ai/sdk/helpers/beta/json-schema";
      import { betaAgentToolset20260401 } from "@anthropic-ai/sdk/tools/agent-toolset/node";

      const getOrderStatus = betaTool({
        name: "get_order_status",
        description: "Look up an order in the internal fulfillment system by order ID.",
        inputSchema: {
          type: "object",
          properties: { order_id: { type: "string", description: "The order ID" } },
          required: ["order_id"]
        },
        // 在 worker 主機上執行：可呼叫沙箱能存取的任何資源。
        run: async ({ order_id }) => `Order ${order_id}: shipped`
      });

      const environmentKey = process.env.ANTHROPIC_ENVIRONMENT_KEY!;
      const environmentId = process.env.ANTHROPIC_ENVIRONMENT_ID!;
      const client = new Anthropic({ authToken: environmentKey });
      const controller = new AbortController();
      process.once("SIGTERM", () => controller.abort());

      await new EnvironmentWorker({
        client,
        environmentId,
        environmentKey,
        workdir: "/workspace",
        signal: controller.signal,
        tools: (ctx) => [...betaAgentToolset20260401(ctx), getOrderStatus]
      }).run();
      ```

      <CodeGroupItem language="C#">
        C# SDK 沒有 `EnvironmentWorker`。若要從您自己的程式碼回應自訂工具呼叫，請在工作階段的事件串流上傳送每個結果，如[傳回自訂工具結果](https://platform.claude.com/docs/zh-TW/managed-agents/events-and-streaming#return-a-custom-tool-result)所示。
      </CodeGroupItem>

      ```go Go
      package main

      import (
      	"context"
      	"log"
      	"os"
      	"os/signal"
      	"syscall"

      	"github.com/anthropics/anthropic-sdk-go"
      	"github.com/anthropics/anthropic-sdk-go/lib/environments"
      	"github.com/anthropics/anthropic-sdk-go/option"
      	"github.com/anthropics/anthropic-sdk-go/toolrunner"
      	"github.com/anthropics/anthropic-sdk-go/tools/agenttoolset"
      )

      type orderStatusInput struct {
      	OrderID string `json:"order_id"`
      }

      func main() {
      	environmentKey := os.Getenv("ANTHROPIC_ENVIRONMENT_KEY")
      	environmentID := os.Getenv("ANTHROPIC_ENVIRONMENT_ID")

      	ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
      	defer stop()

      	getOrderStatus := toolrunner.NewBetaTool(
      		"get_order_status",
      		"Look up an order in the internal fulfillment system by order ID.",
      		anthropic.BetaToolInputSchemaParam{
      			Properties: map[string]any{
      				"order_id": map[string]any{"type": "string", "description": "The order ID"},
      			},
      			Required: []string{"order_id"},
      		},
      		// 在 worker 主機上執行：可呼叫沙箱能存取的任何資源。
      		func(ctx context.Context, input orderStatusInput) (anthropic.BetaToolResultBlockParamContentUnion, error) {
      			return anthropic.BetaToolResultBlockParamContentUnion{
      				OfText: &anthropic.BetaTextBlockParam{Text: "Order " + input.OrderID + ": shipped"},
      			}, nil
      		},
      	)

      	client := anthropic.NewClient(option.WithAuthToken(environmentKey))

      	worker := environments.NewEnvironmentWorker(client, environments.EnvironmentWorkerOptions{
      		EnvironmentID:  environmentID,
      		EnvironmentKey: environmentKey,
      		Workdir:        "/workspace",
      		ToolsFunc: func(env *agenttoolset.AgentToolContext) []anthropic.BetaTool {
      			return append(agenttoolset.BetaAgentToolset20260401(env), getOrderStatus)
      		},
      	})
      	if err := worker.Run(ctx); err != nil {
      		log.Fatalf("worker: %v", err)
      	}
      }
      ```

      <CodeGroupItem language="Java">
        Java SDK 沒有 `EnvironmentWorker`。若要從您自己的程式碼回應自訂工具呼叫，請在工作階段的事件串流上傳送每個結果，如[傳回自訂工具結果](https://platform.claude.com/docs/zh-TW/managed-agents/events-and-streaming#return-a-custom-tool-result)所示。
      </CodeGroupItem>

      <CodeGroupItem language="PHP">
        PHP SDK 沒有 `EnvironmentWorker`。若要從您自己的程式碼回應自訂工具呼叫，請在工作階段的事件串流上傳送每個結果，如[傳回自訂工具結果](https://platform.claude.com/docs/zh-TW/managed-agents/events-and-streaming#return-a-custom-tool-result)所示。
      </CodeGroupItem>

      <CodeGroupItem language="Ruby">
        Ruby SDK 沒有 `EnvironmentWorker`。若要從您自己的程式碼回應自訂工具呼叫，請在工作階段的事件串流上傳送每個結果，如[傳回自訂工具結果](https://platform.claude.com/docs/zh-TW/managed-agents/events-and-streaming#return-a-custom-tool-result)所示。
      </CodeGroupItem>
    </CodeGroup>
  </Step>
</Steps>

worker 只會回應向其註冊的工具。如果某個工具已在代理上宣告，但沒有任何 worker 或用戶端提供它，工作階段會以 `requires_action` 停止原因暫停。它會保持暫停，直到有程式發布結果為止。事件流程請參閱[回應使工作階段暫停的工具呼叫](https://platform.claude.com/docs/zh-TW/managed-agents/events-and-streaming#answer-tool-calls-that-pause-the-session)。

## 將 MCP 伺服器包裝為自訂工具

[MCP 連接器](https://platform.claude.com/docs/zh-TW/managed-agents/mcp-connector)從 Anthropic 端連線至 MCP 伺服器。因此，伺服器必須公開一個 Anthropic 可以直接或透過 [MCP 通道](https://platform.claude.com/docs/zh-TW/agents-and-tools/mcp-tunnels/overview)存取的 HTTP 端點。

若要使用只有您的網路才能存取的伺服器，請改為讓 worker 擔任 MCP 用戶端，並將伺服器的工具宣告為自訂工具。MCP 伺服器不需要來自您網路外部的傳入連線。Anthropic 會收到您在代理上宣告的工具定義、每次呼叫的輸入，以及您的 worker 回傳的結果。

在執行階段，模型會像呼叫其他自訂工具一樣呼叫包裝後的工具：

1. 代理發出 `agent.custom_tool_use` 事件。
2. 位於您沙箱內的 worker 透過其開啟的 MCP 工作階段，將呼叫轉送至您網路上的伺服器。
3. worker 將伺服器的回應作為 `user.custom_tool_result` 發布。

### 安裝 MCP SDK

SDK 的[用戶端 MCP 輔助函式](https://platform.claude.com/docs/zh-TW/agents-and-tools/mcp-connector#client-side-mcp-helpers)會將伺服器的工具轉換為 worker 可接受的可執行工具。請在 Anthropic SDK 之外一併安裝 MCP 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`)。

範例在連線時未使用驗證。若要傳送憑證，請設定您交給 MCP 傳輸層的 `http_client` (typescript: `requestInit`; go: `HTTPClient`; csharp: `AdditionalHeaders`; java: `customizeRequest`; php, ruby: `headers`)。

### 宣告並提供工具

<Steps>
  <Step title="在代理上宣告伺服器的工具">
    列出 MCP 伺服器的工具，並將每個工具宣告為 `custom` 工具。MCP 的 `name`、`description` 和 `inputSchema` 會一對一對應到自訂工具的欄位。如果伺服器對其工具清單進行分頁，請宣告每一頁；worker 也必須列出相同的頁面。

    <CodeGroup exclude="shell">
      ```python Python
      import asyncio
      from typing import Any, cast
      from anthropic import AsyncAnthropic
      from anthropic.types.beta import BetaManagedAgentsCustomToolParams
      from mcp import ClientSession, types

      # 需要 mcp >= 1.24，該版本將 streamablehttp_client 更名為 streamable_http_client。
      from mcp.client.streamable_http import streamable_http_client

      MCP_SERVER_URL = "http://mcp.internal.example.com:8000/mcp"


      def to_custom_tool(tool: types.Tool) -> BetaManagedAgentsCustomToolParams:
          # MCP 欄位與自訂工具宣告一一對應。cast
          # 會將 schema 字典原封不動地傳給 SDK 的型別化參數。
          return {
              "type": "custom",
              "name": tool.name,
              "description": tool.description or tool.name,
              "input_schema": cast(Any, tool.inputSchema),
          }


      async def main() -> None:
          # 請在您建立代理程式的地方執行，而非在 worker 主機上：
          # 它會使用您的 Claude API 金鑰（ANTHROPIC_API_KEY）進行驗證。
          async with (
              streamable_http_client(MCP_SERVER_URL) as (read, write, _),
              ClientSession(read, write) as mcp_session,
              AsyncAnthropic() as client,
          ):
              await mcp_session.initialize()
              listed = await mcp_session.list_tools()
              agent = await client.beta.agents.create(
                  name="Internal tools agent",
                  model="claude-opus-5-5",
                  tools=[
                      {"type": "agent_toolset_20260401"},
                      *[to_custom_tool(tool) for tool in listed.tools],
                  ],
              )
              print(agent.id)


      asyncio.run(main())
      ```

      ```typescript TypeScript
      import Anthropic from "@anthropic-ai/sdk";
      import { Client } from "@modelcontextprotocol/sdk/client/index.js";
      import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";

      const MCP_SERVER_URL = "http://mcp.internal.example.com:8000/mcp";

      // 請在您建立代理程式的地方執行此程式，而非在 worker 主機上：
      // 它會使用您的 Claude API 金鑰（ANTHROPIC_API_KEY）進行驗證。
      const client = new Anthropic();

      const mcpClient = new Client({ name: "declare-agent-tools", version: "1.0.0" });
      await mcpClient.connect(new StreamableHTTPClientTransport(new URL(MCP_SERVER_URL)));
      const { tools } = await mcpClient.listTools();

      const agent = await client.beta.agents.create({
        name: "Internal tools agent",
        model: "claude-opus-5-5",
        tools: [
          { type: "agent_toolset_20260401" },
          // MCP 欄位與自訂工具宣告一一對應。
          ...tools.map((tool) => ({
            type: "custom" as const,
            name: tool.name,
            description: tool.description || tool.name,
            input_schema: tool.inputSchema
          }))
        ]
      });
      console.log(agent.id);

      await mcpClient.close();
      ```

      ```csharp C#
      using System.Text.Json;
      using Anthropic;
      using Anthropic.Models.Beta.Agents;
      using ModelContextProtocol.Client;

      const string McpServerUrl = "http://mcp.internal.example.com:8000/mcp";

      // 請在您建立 agent 的地方執行此程式，而非在 worker 主機上：它
      // 使用您的 Claude API 金鑰（ANTHROPIC_API_KEY）進行驗證。
      var client = new AnthropicClient();

      await using var mcpClient = await McpClient.CreateAsync(
          new HttpClientTransport(new HttpClientTransportOptions { Endpoint = new Uri(McpServerUrl) })
      );
      var mcpTools = await mcpClient.ListToolsAsync();

      List<Tool> tools =
      [
          new BetaManagedAgentsAgentToolset20260401Params { Type = "agent_toolset_20260401" },
      ];
      foreach (var tool in mcpTools)
      {
          var description = tool.Description;
          if (string.IsNullOrEmpty(description))
          {
              description = tool.Name;
          }

          // MCP 欄位與自訂工具宣告一一對應。
          // schema 的關鍵字會原封不動地傳給 SDK。
          tools.Add(
              new BetaManagedAgentsCustomToolParams
              {
                  Type = "custom",
                  Name = tool.Name,
                  Description = description,
                  InputSchema = new(
                      tool.ProtocolTool.InputSchema.Deserialize<Dictionary<string, JsonElement>>()!
                  ),
              }
          );
      }

      var agent = await client.Beta.Agents.Create(new()
      {
          Name = "Internal tools agent",
          Model = BetaManagedAgentsModel.ClaudeOpus5_5,
          Tools = tools,
      });
      Console.WriteLine(agent.ID);
      ```

      ```go Go
      package main

      import (
      	"context"
      	"encoding/json"
      	"fmt"
      	"log"

      	"github.com/anthropics/anthropic-sdk-go"
      	mcpsdk "github.com/modelcontextprotocol/go-sdk/mcp"
      )

      const mcpServerURL = "http://mcp.internal.example.com:8000/mcp"

      // toCustomTool 將一個 MCP 工具定義對應到一個自訂工具宣告。
      // 欄位是一對一對應的：型別化參數帶有 `properties` 和
      // `required`，而伺服器發出的其他所有 JSON Schema 關鍵字則放在
      // ExtraFields 中，使宣告的結構描述與伺服器的結構描述相符。
      func toCustomTool(tool *mcpsdk.Tool) (anthropic.BetaAgentNewParamsToolUnion, error) {
      	raw, err := json.Marshal(tool.InputSchema)
      	if err != nil {
      		return anthropic.BetaAgentNewParamsToolUnion{}, err
      	}
      	var schema map[string]any
      	if err := json.Unmarshal(raw, &schema); err != nil {
      		return anthropic.BetaAgentNewParamsToolUnion{}, err
      	}

      	inputSchema := anthropic.BetaManagedAgentsCustomToolInputSchemaParam{ExtraFields: map[string]any{}}
      	for keyword, value := range schema {
      		switch keyword {
      		case "type":
      			// 參數型別一律序列化為 "type": "object"。
      		case "properties":
      			properties, _ := value.(map[string]any)
      			inputSchema.Properties = properties
      		case "required":
      			entries, _ := value.([]any)
      			for _, entry := range entries {
      				if name, isString := entry.(string); isString {
      					inputSchema.Required = append(inputSchema.Required, name)
      				}
      			}
      		default:
      			inputSchema.ExtraFields[keyword] = value
      		}
      	}

      	description := tool.Description
      	if description == "" {
      		description = tool.Name
      	}
      	return anthropic.BetaAgentNewParamsToolUnion{
      		OfCustom: &anthropic.BetaManagedAgentsCustomToolParams{
      			Type:        anthropic.BetaManagedAgentsCustomToolParamsTypeCustom,
      			Name:        tool.Name,
      			Description: description,
      			InputSchema: inputSchema,
      		},
      	}, nil
      }

      func main() {
      	ctx := context.Background()

      	// 請在您建立代理的地方執行此程式，而非在工作主機上：它會
      	// 使用您的 Claude API 金鑰（ANTHROPIC_API_KEY）進行驗證。
      	client := anthropic.NewClient()

      	mcpClient := mcpsdk.NewClient(&mcpsdk.Implementation{Name: "declare-agent-tools", Version: "1.0.0"}, nil)
      	session, err := mcpClient.Connect(ctx, &mcpsdk.StreamableClientTransport{Endpoint: mcpServerURL}, nil)
      	if err != nil {
      		log.Fatalf("connect to MCP server: %v", err)
      	}
      	defer session.Close()

      	listed, err := session.ListTools(ctx, nil)
      	if err != nil {
      		log.Fatalf("list MCP tools: %v", err)
      	}

      	tools := []anthropic.BetaAgentNewParamsToolUnion{
      		{OfAgentToolset20260401: &anthropic.BetaManagedAgentsAgentToolset20260401Params{
      			Type: anthropic.BetaManagedAgentsAgentToolset20260401ParamsTypeAgentToolset20260401,
      		}},
      	}
      	for _, tool := range listed.Tools {
      		custom, err := toCustomTool(tool)
      		if err != nil {
      			log.Fatalf("convert MCP tool %s: %v", tool.Name, err)
      		}
      		tools = append(tools, custom)
      	}

      	agent, err := client.Beta.Agents.New(ctx, anthropic.BetaAgentNewParams{
      		Name:  "Internal tools agent",
      		Model: anthropic.BetaManagedAgentsModelConfigParams{ID: anthropic.BetaManagedAgentsModelClaudeOpus5_5},
      		Tools: tools,
      	})
      	if err != nil {
      		log.Fatalf("create agent: %v", err)
      	}
      	fmt.Println(agent.ID)
      }
      ```

      ```java Java
      import com.anthropic.client.okhttp.AnthropicOkHttpClient;
      import com.anthropic.core.JsonValue;
      import com.anthropic.models.beta.agents.AgentCreateParams;
      import com.anthropic.models.beta.agents.BetaManagedAgentsAgentToolset20260401Params;
      import com.anthropic.models.beta.agents.BetaManagedAgentsCustomToolInputSchema;
      import com.anthropic.models.beta.agents.BetaManagedAgentsCustomToolParams;
      import com.anthropic.models.beta.agents.BetaManagedAgentsModel;
      import io.modelcontextprotocol.client.McpClient;
      import io.modelcontextprotocol.client.transport.HttpClientStreamableHttpTransport;
      import io.modelcontextprotocol.spec.McpSchema;

      // MCP 欄位與自訂工具宣告一一對應。MCP Java
      // SDK 僅保留工具輸入結構描述中的這些頂層關鍵字：type、
      // properties、required、additionalProperties、$defs 與 definitions。
      BetaManagedAgentsCustomToolParams toCustomTool(McpSchema.Tool tool) {
          var description = tool.description();
          if (description == null || description.isEmpty()) {
              description = tool.name();
          }

          return BetaManagedAgentsCustomToolParams.builder()
              .type(BetaManagedAgentsCustomToolParams.Type.CUSTOM)
              .name(tool.name())
              .description(description)
              .inputSchema(JsonValue.from(tool.inputSchema())
                  .convert(BetaManagedAgentsCustomToolInputSchema.class))
              .build();
      }

      void main() {
          // 請在您建立代理程式的地方執行，而非在 worker 主機上：它
          // 使用您的 Claude API 金鑰（ANTHROPIC_API_KEY）進行驗證。
          var client = AnthropicOkHttpClient.fromEnv();

          // MCP 伺服器的位址，接著是其 MCP 端點的路徑。
          var transport = HttpClientStreamableHttpTransport.builder("http://mcp.internal.example.com:8000")
              .endpoint("/mcp")
              .build();
          try (var mcpClient = McpClient.sync(transport)
                  .clientInfo(new McpSchema.Implementation("declare-agent-tools", "1.0.0"))
                  .build()) {
              mcpClient.initialize();

              var params = AgentCreateParams.builder()
                  .name("Internal tools agent")
                  .model(BetaManagedAgentsModel.CLAUDE_OPUS_5_5)
                  .addTool(BetaManagedAgentsAgentToolset20260401Params.builder()
                      .type(BetaManagedAgentsAgentToolset20260401Params.Type.AGENT_TOOLSET_20260401)
                      .build());
              for (var tool : mcpClient.listTools().tools()) {
                  params.addTool(toCustomTool(tool));
              }

              var agent = client.beta().agents().create(params.build());
              IO.println(agent.id());
          }
      }
      ```

      ```php PHP
      use Anthropic\Beta\Agents\BetaManagedAgentsModel;
      use Anthropic\Client as Anthropic;
      use Mcp\Client as McpClient;
      use Mcp\Client\Transport\HttpTransport;

      $mcpServerUrl = 'http://mcp.internal.example.com:8000/mcp';

      // 請在您建立 agent 的地方執行此程式，而非在 worker 主機上：它
      // 使用您的 Claude API 金鑰（ANTHROPIC_API_KEY）進行驗證。
      $client = new Anthropic();

      $mcpClient = McpClient::builder()->build();
      $mcpClient->connect(new HttpTransport($mcpServerUrl));

      $tools = [['type' => 'agent_toolset_20260401']];
      foreach ($mcpClient->listTools()->tools as $tool) {
          // MCP 欄位與自訂工具宣告一一對應。
          // schema 的關鍵字會原封不動地傳給 SDK。
          $tools[] = [
              'type' => 'custom',
              'name' => $tool->name,
              'description' => $tool->description ?: $tool->name,
              'inputSchema' => $tool->inputSchema,
          ];
      }

      $agent = $client->beta->agents->create(
          name: 'Internal tools agent',
          model: BetaManagedAgentsModel::CLAUDE_OPUS_5_5,
          tools: $tools,
      );
      echo $agent->id, "\n";

      $mcpClient->disconnect();
      ```

      ```ruby Ruby
      require "anthropic"
      require "mcp"

      MCP_SERVER_URL = "http://mcp.internal.example.com:8000/mcp"

      # 請在您建立 agent 的地方執行此程式，而非在 worker 主機上：它
      # 使用您的 Claude API 金鑰（ANTHROPIC_API_KEY）進行驗證。
      client = Anthropic::Client.new

      transport = MCP::Client::HTTP.new(url: MCP_SERVER_URL)
      mcp_client = MCP::Client.new(transport: transport)
      mcp_client.connect

      custom_tools = mcp_client.tools.map do |tool|
        description = tool.description
        description = tool.name if description.nil? || description.empty?

        # MCP 欄位與自訂工具宣告一一對應。
        # schema 的關鍵字會原封不動地傳給 SDK。
        {
          type: :custom,
          name: tool.name,
          description: description,
          input_schema: tool.input_schema
        }
      end

      agent = client.beta.agents.create(
        name: "Internal tools agent",
        model: Anthropic::Beta::BetaManagedAgentsModel::CLAUDE_OPUS_5_5,
        tools: [{type: :agent_toolset_20260401}, *custom_tools]
      )
      puts agent.id

      transport.close
      ```
    </CodeGroup>
  </Step>

  <Step title="從 worker 提供工具">
    在啟動時連線至同一個 MCP 伺服器，使用 `async_mcp_tool` (typescript: `mcpTools`; go: `mcp.NewBetaTools`) 轉換其工具，並將它們與 `beta_agent_toolset_20260401` (typescript: `betaAgentToolset20260401`; go: `agenttoolset.BetaAgentToolset20260401`) 一起註冊。在 worker 的整個生命週期中保持一個 MCP 工作階段開啟。

    <CodeGroup exclude="shell, csharp, java, php, ruby">
      ```python Python
      import asyncio
      import os
      from datetime import timedelta
      from anthropic import AsyncAnthropic
      from anthropic.lib.environments import EnvironmentWorker
      from anthropic.lib.tools.agent_toolset import beta_agent_toolset_20260401
      from anthropic.lib.tools.mcp import async_mcp_tool
      from mcp import ClientSession

      # 需要 mcp >= 1.24，該版本將 streamablehttp_client 更名為 streamable_http_client。
      from mcp.client.streamable_http import streamable_http_client

      MCP_SERVER_URL = "http://mcp.internal.example.com:8000/mcp"


      async def main() -> None:
          environment_key = os.environ["ANTHROPIC_ENVIRONMENT_KEY"]
          environment_id = os.environ["ANTHROPIC_ENVIRONMENT_ID"]
          # 啟動時連線 MCP 伺服器一次，並在 worker 的整個生命週期中
          # 保持工作階段開啟。逾時設定會將卡住的工具呼叫轉為錯誤
          # 結果，而非停滯的呼叫。
          async with (
              streamable_http_client(MCP_SERVER_URL) as (read, write, _),
              ClientSession(
                  read, write, read_timeout_seconds=timedelta(seconds=60)
              ) as mcp_session,
              AsyncAnthropic(auth_token=environment_key) as client,
          ):
              await mcp_session.initialize()
              listed = await mcp_session.list_tools()
              mcp_tools = [async_mcp_tool(tool, mcp_session) for tool in listed.tools]
              await EnvironmentWorker(
                  client,
                  environment_id=environment_id,
                  environment_key=environment_key,
                  workdir="/workspace",
                  tools=lambda env: [*beta_agent_toolset_20260401(env), *mcp_tools],
              ).run()


      asyncio.run(main())
      ```

      ```typescript TypeScript
      import Anthropic from "@anthropic-ai/sdk";
      import { EnvironmentWorker } from "@anthropic-ai/sdk/helpers/beta/environments";
      import {
        mcpTools,
        type MCPCallToolResultLike,
        type MCPClientLike
      } from "@anthropic-ai/sdk/helpers/beta/mcp";
      import { betaAgentToolset20260401 } from "@anthropic-ai/sdk/tools/agent-toolset/node";
      import { Client } from "@modelcontextprotocol/sdk/client/index.js";
      import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";

      const MCP_SERVER_URL = "http://mcp.internal.example.com:8000/mcp";

      const environmentKey = process.env.ANTHROPIC_ENVIRONMENT_KEY!;
      const environmentId = process.env.ANTHROPIC_ENVIRONMENT_ID!;
      const client = new Anthropic({ authToken: environmentKey });
      const controller = new AbortController();
      process.once("SIGTERM", () => controller.abort());

      // 在啟動時連線 MCP 伺服器一次，並在 worker 的整個生命週期中
      // 保持連線開啟。
      const mcpClient = new Client({ name: "sandbox-worker", version: "1.0.0" });
      await mcpClient.connect(new StreamableHTTPClientTransport(new URL(MCP_SERVER_URL)));
      const { tools } = await mcpClient.listTools();

      // MCP SDK 的 callTool 回傳型別仍包含 mcpTools 不接受的舊版結果形狀；
      // 需將其收窄。待 MCPClientLike 放寬型別後即可移除。
      const mcpClientForTools: MCPClientLike = {
        callTool: (params) => mcpClient.callTool(params) as Promise<MCPCallToolResultLike>
      };

      await new EnvironmentWorker({
        client,
        environmentId,
        environmentKey,
        workdir: "/workspace",
        signal: controller.signal,
        tools: (ctx) => [...betaAgentToolset20260401(ctx), ...mcpTools(tools, mcpClientForTools)]
      }).run();
      ```

      <CodeGroupItem language="C#">
        C# SDK 沒有 `EnvironmentWorker`。
      </CodeGroupItem>

      ```go Go
      package main

      import (
      	"context"
      	"log"
      	"os"
      	"os/signal"
      	"syscall"

      	"github.com/anthropics/anthropic-sdk-go"
      	"github.com/anthropics/anthropic-sdk-go/lib/environments"
      	"github.com/anthropics/anthropic-sdk-go/mcp"
      	"github.com/anthropics/anthropic-sdk-go/option"
      	"github.com/anthropics/anthropic-sdk-go/tools/agenttoolset"
      	mcpsdk "github.com/modelcontextprotocol/go-sdk/mcp"
      )

      const mcpServerURL = "http://mcp.internal.example.com:8000/mcp"

      func main() {
      	environmentKey := os.Getenv("ANTHROPIC_ENVIRONMENT_KEY")
      	environmentID := os.Getenv("ANTHROPIC_ENVIRONMENT_ID")

      	ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
      	defer stop()

      	client := anthropic.NewClient(option.WithAuthToken(environmentKey))

      	// 在啟動時連線 MCP 伺服器一次，並在 worker 的整個生命週期中
      	// 保持工作階段開啟。
      	mcpClient := mcpsdk.NewClient(&mcpsdk.Implementation{Name: "sandbox-worker", Version: "1.0.0"}, nil)
      	session, err := mcpClient.Connect(ctx, &mcpsdk.StreamableClientTransport{Endpoint: mcpServerURL}, nil)
      	if err != nil {
      		log.Fatalf("connect to MCP server: %v", err)
      	}
      	defer session.Close()

      	listed, err := session.ListTools(ctx, nil)
      	if err != nil {
      		log.Fatalf("list MCP tools: %v", err)
      	}
      	mcpTools, err := mcp.NewBetaTools(listed.Tools, session)
      	if err != nil {
      		log.Fatalf("convert MCP tools: %v", err)
      	}

      	worker := environments.NewEnvironmentWorker(client, environments.EnvironmentWorkerOptions{
      		EnvironmentID:  environmentID,
      		EnvironmentKey: environmentKey,
      		Workdir:        "/workspace",
      		ToolsFunc: func(env *agenttoolset.AgentToolContext) []anthropic.BetaTool {
      			return append(agenttoolset.BetaAgentToolset20260401(env), mcpTools...)
      		},
      	})
      	if err := worker.Run(ctx); err != nil {
      		log.Fatalf("worker: %v", err)
      	}
      }
      ```

      <CodeGroupItem language="Java">
        Java SDK 沒有 `EnvironmentWorker`。
      </CodeGroupItem>

      <CodeGroupItem language="PHP">
        PHP SDK 沒有 `EnvironmentWorker`。
      </CodeGroupItem>

      <CodeGroupItem language="Ruby">
        Ruby SDK 沒有 `EnvironmentWorker`。
      </CodeGroupItem>
    </CodeGroup>
  </Step>
</Steps>

## 限制與行為

### 工具是宣告的，而非在執行階段探索

worker 在啟動時只會列出一次 MCP 伺服器的工具，且無法將工具新增至執行中的工作階段。當伺服器的工具變更時：

1. 重新宣告它們，可在代理上宣告，或透過[更新代理設定](https://platform.claude.com/docs/zh-TW/managed-agents/session-operations#updating-the-agent-configuration)在閒置的工作階段上宣告。
2. 重新啟動 worker。

### 宣告必須符合 Managed Agents API

MCP 輔助工具會保留伺服器的名稱和描述，且大多數結構描述會原封不動地傳遞。當宣告違反下列任一規則時，請重新命名、精簡或內嵌：

| 欄位             | 規則                                                                                                                                                              |
| -------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `name`         | 每個代理中必須唯一。可使用字母、數字、底線和連字號，長度為 1–128 個字元。不可與內建代理工具（例如 `bash` 或 `read`）相同，也不可使用保留的 `mcp__` 前綴或 `ant__` 前綴。                                                        |
| `description`  | 必填且不可為空。                                                                                                                                                        |
| `input_schema` | 接受 MCP 伺服器常見輸出的 JSON Schema 關鍵字，例如 `additionalProperties` 和 `title`。拒絕任何位置的參照關鍵字（例如 `$ref`），以及頂層的 `oneOf`、`anyOf` 和 `allOf`。屬性名稱可使用字母、數字、底線、點和連字號，長度為 1–64 個字元。 |
| 代理的 `tools` 陣列 | 最多 256 個項目。每個包裝後的工具算一個項目，內建工具集再算一個。                                                                                                                             |

有兩種情況需要額外處理：

* **兩個伺服器公開相同的工具名稱：** 請自行以加上前綴的名稱定義包裝器，並讓它呼叫伺服器的原始工具名稱。
* **像 pydantic 這類產生器將結構描述拆分至 `$defs`：** 請在宣告工具之前將這些結構描述內嵌。

### 工具失敗會以錯誤工具結果呈現

當 MCP 伺服器回報工具錯誤時，worker 會發布一個模型可以回應的錯誤工具結果。沒有對應工具結果形式的 MCP 內容（例如音訊區塊和資源連結）也會以錯誤呈現。

請在 MCP 用戶端上設定逾時，以獲得更快速且更明確的失敗，如同 Python worker 範例使用 `read_timeout_seconds` 的做法。若未設定逾時會發生什麼情況，請參閱[包裝的 MCP 工具呼叫停滯](https://platform.claude.com/docs/zh-TW/managed-agents/self-hosted-sandboxes-operations#a-wrapped-mcp-tool-call-hangs)。

### 只包裝您營運或信任的伺服器

包裝後工具的名稱、描述和結果會像其他工具一樣進入模型的上下文。它們是不受信任的輸入，可能影響代理如何使用其他工具，包括 worker 主機上的 `bash`。請只宣告您打算讓代理使用的工具。

### 權限政策不適用

[權限政策](https://platform.claude.com/docs/zh-TW/managed-agents/permission-policies#custom-tools)管理內建工具集和 MCP 工具集。worker 會執行模型發出的每一個包裝工具呼叫，因此請將任何核准步驟放在您自己的工具程式碼中。
