---
title: Benutzerdefinierte Tools in selbst gehosteten Sandboxes
url: https://platform.claude.com/docs/de/managed-agents/self-hosted-sandboxes-custom-tools
description: Stelle benutzerdefinierte Tools über einen Worker in einer selbst gehosteten Sandbox bereit und kapsle einen MCP-Server in deinem Netzwerk als benutzerdefinierte Tools, ohne einen Tunnel zu betreiben.
featureMetadata:
  topic:
    title: Managed Agents
    url: https://platform.claude.com/docs/en/managed-agents/overview
  status: beta
  betaHeader: managed-agents-2026-04-01
---

[Benutzerdefinierte Tools](https://platform.claude.com/docs/de/managed-agents/tools#custom-tools) sind Tools, die dein eigener Code ausführt: Der Agent sendet ein `agent.custom_tool_use`-Event und wartet auf ein passendes `user.custom_tool_result`. Dein Worker kann dieser Code sein. Da er innerhalb deiner Sandbox läuft, erreicht das Tool die internen Dienste, Anmeldedaten und den ausgehenden Netzwerkverkehr („network egress“), die du für die Sandbox konfiguriert hast, und nichts darüber hinaus.

Der Umgebungsschlüssel autorisiert das Senden von Ergebnissen benutzerdefinierter Tools, sodass dein Claude-API-Key nicht auf dem Worker-Host liegen muss.

<Note>
  Das Bereitstellen benutzerdefinierter Tools erfordert den SDK-Worker. Der `ant`-CLI-Worker bietet keine Möglichkeit, die Implementierung eines benutzerdefinierten Tools zu registrieren. Im Muster „eine Sandbox pro Sitzung“ [führst du den SDK-Worker innerhalb der Sandbox aus](https://platform.claude.com/docs/de/managed-agents/self-hosted-sandboxes-workers#run-the-sdk-worker-inside-the-sandbox).
</Note>

## Ein benutzerdefiniertes Tool bereitstellen

<Steps>
  <Step title="Das Tool am Agenten deklarieren">
    Füge den `tools` des Agenten einen `custom`-Eintrag hinzu, dessen `name` mit dem Tool übereinstimmt, das dein Worker registriert. Die vollständige Struktur der Deklaration findest du unter [Benutzerdefinierte Tools](https://platform.claude.com/docs/de/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="Die Implementierung beim Worker registrieren">
    Übergib das Tool über die `tools` (go: `ToolsFunc`)-Factory des Workers (siehe [`EnvironmentWorker`](https://platform.claude.com/docs/de/managed-agents/self-hosted-sandboxes-reference#environment-worker)), zusammen mit dem integrierten Toolset:

    <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."""
          # Läuft auf dem Worker-Host: Rufe alles auf, was die Sandbox erreichen kann.
          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"]
        },
        // Läuft auf dem Worker-Host: Rufe alles auf, was die Sandbox erreichen kann.
        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#">
        Das C#-SDK hat keinen `EnvironmentWorker`. Um Aufrufe benutzerdefinierter Tools aus deinem eigenen Code zu beantworten, sende jedes Ergebnis über den Event-Stream der Sitzung, wie unter [Ein Custom-Tool-Ergebnis zurückgeben](https://platform.claude.com/docs/de/managed-agents/events-and-streaming#return-a-custom-tool-result) beschrieben.
      </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"},
      		},
      		// Läuft auf dem Worker-Host: Rufe alles auf, was die Sandbox erreichen kann.
      		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">
        Das Java-SDK hat keinen `EnvironmentWorker`. Um Aufrufe benutzerdefinierter Tools aus deinem eigenen Code zu beantworten, sende jedes Ergebnis über den Event-Stream der Sitzung, wie unter [Ein Custom-Tool-Ergebnis zurückgeben](https://platform.claude.com/docs/de/managed-agents/events-and-streaming#return-a-custom-tool-result) beschrieben.
      </CodeGroupItem>

      <CodeGroupItem language="PHP">
        Das PHP-SDK hat keinen `EnvironmentWorker`. Um Aufrufe benutzerdefinierter Tools aus deinem eigenen Code zu beantworten, sende jedes Ergebnis über den Event-Stream der Sitzung, wie unter [Ein Custom-Tool-Ergebnis zurückgeben](https://platform.claude.com/docs/de/managed-agents/events-and-streaming#return-a-custom-tool-result) beschrieben.
      </CodeGroupItem>

      <CodeGroupItem language="Ruby">
        Das Ruby-SDK hat keinen `EnvironmentWorker`. Um Aufrufe benutzerdefinierter Tools aus deinem eigenen Code zu beantworten, sende jedes Ergebnis über den Event-Stream der Sitzung, wie unter [Ein Custom-Tool-Ergebnis zurückgeben](https://platform.claude.com/docs/de/managed-agents/events-and-streaming#return-a-custom-tool-result) beschrieben.
      </CodeGroupItem>
    </CodeGroup>
  </Step>
</Steps>

Der Worker beantwortet nur die bei ihm registrierten Tools. Wenn ein Tool am Agenten deklariert ist, aber kein Worker oder Client es bereitstellt, pausiert die Sitzung mit dem Stop-Grund `requires_action`. Sie bleibt pausiert, bis etwas das Ergebnis sendet. Den Event-Ablauf findest du unter [Tool-Aufrufe beantworten, die die Session pausieren](https://platform.claude.com/docs/de/managed-agents/events-and-streaming#answer-tool-calls-that-pause-the-session).

## Einen MCP-Server als benutzerdefinierte Tools kapseln

Der [MCP-Connector](https://platform.claude.com/docs/de/managed-agents/mcp-connector) verbindet sich von Anthropics Seite aus mit MCP-Servern. Ein Server muss daher einen HTTP-Endpunkt bereitstellen, den Anthropic erreichen kann, direkt oder über einen [MCP-Tunnel](https://platform.claude.com/docs/de/agents-and-tools/mcp-tunnels/overview).

Um einen Server zu nutzen, den nur dein Netzwerk erreichen kann, machst du stattdessen den Worker zum MCP-Client und deklarierst die Tools des Servers als benutzerdefinierte Tools. Der MCP-Server benötigt keine eingehende Verbindung von außerhalb deines Netzwerks. Anthropic erhält die Tool-Definitionen, die du am Agenten deklarierst, die Eingabe jedes Aufrufs und das Ergebnis, das dein Worker zurücksendet.

Zur Laufzeit ruft das Modell ein gekapseltes Tool wie jedes andere benutzerdefinierte Tool auf:

1. Der Agent sendet ein `agent.custom_tool_use`-Event.
2. Der Worker leitet den Aufruf innerhalb deiner Sandbox über seine offene MCP-Sitzung an den Server in deinem Netzwerk weiter.
3. Der Worker sendet die Antwort des Servers als `user.custom_tool_result`.

### Ein MCP-SDK installieren

Die [clientseitigen MCP-Helfer](https://platform.claude.com/docs/de/agents-and-tools/mcp-connector#client-side-mcp-helpers) des SDK wandeln die Tools des Servers in ausführbare Tools um, die der Worker akzeptiert. Installiere ein MCP-SDK zusätzlich zum 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`).

Die Beispiele verbinden sich ohne Authentifizierung. Um Anmeldedaten zu senden, konfiguriere den `http_client` (typescript: `requestInit`; go: `HTTPClient`; csharp: `AdditionalHeaders`; java: `customizeRequest`; php, ruby: `headers`), den du an den MCP-Transport übergibst.

### Die Tools deklarieren und bereitstellen

<Steps>
  <Step title="Die Tools des Servers am Agenten deklarieren">
    Liste die Tools des MCP-Servers auf und deklariere jedes als `custom`-Tool. Die MCP-Felder `name`, `description` und `inputSchema` lassen sich eins zu eins auf die Felder des benutzerdefinierten Tools abbilden. Wenn der Server seine Tool-Liste paginiert, deklariere jede Seite; der Worker muss dieselben Seiten auflisten.

    <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

      # Erfordert mcp >= 1.24, das streamablehttp_client in streamable_http_client umbenannt hat.
      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:
          # Die MCP-Felder lassen sich eins zu eins auf eine Custom-Tool-Deklaration abbilden. Der Cast
          # übergibt das Schema-Dictionary unverändert an den typisierten Parameter des SDK.
          return {
              "type": "custom",
              "name": tool.name,
              "description": tool.description or tool.name,
              "input_schema": cast(Any, tool.inputSchema),
          }


      async def main() -> None:
          # Führe dies dort aus, wo du Agenten erstellst, nicht auf dem Worker-Host:
          # es authentifiziert sich mit deinem Claude-API-Key (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";

      // Führe dies dort aus, wo du Agenten erstellst, nicht auf dem Worker-Host: es
      // authentifiziert sich mit deinem Claude-API-Key (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" },
          // Die MCP-Felder lassen sich eins zu eins auf eine benutzerdefinierte Tool-Deklaration abbilden.
          ...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";

      // Führe dies dort aus, wo du Agenten erstellst, nicht auf dem Worker-Host: Es
      // authentifiziert sich mit deinem Claude API-Key (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;
          }

          // Die MCP-Felder entsprechen eins zu eins einer Custom-Tool-Deklaration. Die
          // Schlüsselwörter des Schemas werden unverändert an das SDK übergeben.
          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 bildet eine MCP-Tool-Definition auf eine benutzerdefinierte Tool-Deklaration ab.
      // Die Felder werden eins zu eins abgebildet: der typisierte Parameter trägt `properties` und
      // `required`, und jedes andere JSON-Schema-Schlüsselwort, das der Server ausgibt, wandert in
      // ExtraFields, sodass das deklarierte Schema dem Schema des Servers entspricht.
      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":
      			// Der Parametertyp serialisiert immer "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()

      	// Führe dies dort aus, wo du Agenten erstellst, nicht auf dem Worker-Host: es
      	// authentifiziert sich mit deinem Claude-API-Key (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;

      // Die MCP-Felder entsprechen eins zu eins einer Custom-Tool-Deklaration. Das MCP Java
      // SDK behält nur diese Top-Level-Keywords des Input-Schemas eines Tools: type,
      // properties, required, additionalProperties, $defs und 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() {
          // Führe dies dort aus, wo du Agenten erstellst, nicht auf dem Worker-Host: es
          // authentifiziert sich mit deinem Claude API-Key (ANTHROPIC_API_KEY).
          var client = AnthropicOkHttpClient.fromEnv();

          // Die Adresse des MCP-Servers, dann der Pfad seines MCP-Endpunkts.
          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';

      // Führe dies dort aus, wo du Agenten erstellst, nicht auf dem Worker-Host: Es
      // authentifiziert sich mit deinem Claude API-Key (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) {
          // Die MCP-Felder entsprechen eins zu eins einer Custom-Tool-Deklaration. Die
          // Schlüsselwörter des Schemas werden unverändert an das SDK übergeben.
          $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"

      # Führe dies dort aus, wo du Agenten erstellst, nicht auf dem Worker-Host: Es
      # authentifiziert sich mit deinem Claude API-Key (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?

        # Die MCP-Felder entsprechen eins zu eins einer Custom-Tool-Deklaration. Die
        # Schlüsselwörter des Schemas werden unverändert an das SDK übergeben.
        {
          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="Die Tools über den Worker bereitstellen">
    Verbinde dich beim Start mit demselben MCP-Server, wandle seine Tools mit `async_mcp_tool` (typescript: `mcpTools`; go: `mcp.NewBetaTools`) um und registriere sie zusammen mit `beta_agent_toolset_20260401` (typescript: `betaAgentToolset20260401`; go: `agenttoolset.BetaAgentToolset20260401`). Halte eine MCP-Sitzung für die gesamte Lebensdauer des Workers offen.

    <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

      # Erfordert mcp >= 1.24, das streamablehttp_client in streamable_http_client umbenannt hat.
      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"]
          # Verbinde dich beim Start einmal mit dem MCP-Server und halte die Session offen für
          # die Lebensdauer des Workers. Das Timeout wandelt einen hängenden Tool-Aufruf
          # in ein Fehlerergebnis statt eines blockierten Aufrufs um.
          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());

      // Verbinde dich beim Start einmal mit dem MCP-Server und halte die Verbindung
      // für die Lebensdauer des Workers offen.
      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();

      // Der Rückgabetyp von callTool im MCP SDK enthält noch eine Legacy-Ergebnisform, die
      // mcpTools nicht akzeptiert; grenze sie ein. Entferne dies, sobald MCPClientLike erweitert wird.
      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#">
        Das C#-SDK hat keinen `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))

      	// Verbinde dich beim Start einmal mit dem MCP-Server und halte die Session offen für
      	// die gesamte Lebensdauer des Workers.
      	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">
        Das Java-SDK hat keinen `EnvironmentWorker`.
      </CodeGroupItem>

      <CodeGroupItem language="PHP">
        Das PHP-SDK hat keinen `EnvironmentWorker`.
      </CodeGroupItem>

      <CodeGroupItem language="Ruby">
        Das Ruby-SDK hat keinen `EnvironmentWorker`.
      </CodeGroupItem>
    </CodeGroup>
  </Step>
</Steps>

## Grenzen und Verhalten

### Tools werden deklariert, nicht zur Laufzeit erkannt

Der Worker listet die Tools des MCP-Servers einmal beim Start auf und kann einer laufenden Sitzung keine Tools hinzufügen. Wenn sich die Tools des Servers ändern:

1. Deklariere sie erneut, am Agenten oder an einer inaktiven Sitzung über [Aktualisieren der Agent-Konfiguration](https://platform.claude.com/docs/de/managed-agents/session-operations#updating-the-agent-configuration).
2. Starte den Worker neu.

### Deklarationen müssen zur Managed Agents API passen

Die MCP-Helfer übernehmen die Namen und Beschreibungen des Servers, und die meisten Schemas werden unverändert durchgereicht. Benenne um, kürze oder bette inline ein, wo eine Deklaration gegen eine dieser Regeln verstößt:

| Feld                          | Regel                                                                                                                                                                                                                                                                                                                                                 |
| ----------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `name`                        | Eindeutig pro Agent. Buchstaben, Ziffern, Unterstriche und Bindestriche, 1–128 Zeichen. Darf nicht mit einem integrierten Agenten-Tool wie `bash` oder `read` übereinstimmen und nicht das reservierte Präfix `mcp__` oder das Präfix `ant__` verwenden.                                                                                              |
| `description`                 | Erforderlich und nicht leer.                                                                                                                                                                                                                                                                                                                          |
| `input_schema`                | Akzeptiert die JSON-Schema-Schlüsselwörter, die MCP-Server üblicherweise ausgeben, etwa `additionalProperties` und `title`. Lehnt Referenz-Schlüsselwörter wie `$ref` an jeder Stelle sowie `oneOf`, `anyOf` und `allOf` auf oberster Ebene ab. Eigenschaftsnamen verwenden Buchstaben, Ziffern, Unterstriche, Punkte und Bindestriche, 1–64 Zeichen. |
| Das `tools`-Array des Agenten | Höchstens 256 Einträge. Jedes gekapselte Tool ist ein Eintrag, und das integrierte Toolset ist ein weiterer.                                                                                                                                                                                                                                          |

Zwei Fälle erfordern zusätzlichen Aufwand:

* **Zwei Server stellen denselben Tool-Namen bereit:** Definiere den Wrapper selbst unter einem Namen mit Präfix und lass ihn den ursprünglichen Tool-Namen des Servers aufrufen.
* **Ein Generator wie pydantic lagert Schemas in `$defs` aus:** Bette diese Schemas inline ein, bevor du das Tool deklarierst.

### Tool-Fehler erscheinen als Fehlerergebnisse

Wenn der MCP-Server einen Tool-Fehler meldet, sendet der Worker ein Fehlerergebnis, auf das das Modell reagieren kann. MCP-Inhalte ohne Entsprechung in Tool-Ergebnissen, wie Audio-Blöcke und Ressourcen-Links, erscheinen ebenfalls als Fehler.

Lege ein Timeout für den MCP-Client fest, um einen schnelleren und klareren Fehler zu erhalten, wie es das Python-Worker-Beispiel mit `read_timeout_seconds` tut. Was ohne Timeout passiert, erfährst du unter [Ein gekapselter MCP-Tool-Aufruf hängt](https://platform.claude.com/docs/de/managed-agents/self-hosted-sandboxes-operations#a-wrapped-mcp-tool-call-hangs).

### Kapsle nur Server, die du betreibst oder denen du vertraust

Name, Beschreibung und Ergebnisse eines gekapselten Tools gelangen wie die jedes anderen Tools in den Kontext des Modells. Sie sind nicht vertrauenswürdige Eingaben, die beeinflussen können, was der Agent mit seinen anderen Tools tut, einschließlich `bash` auf dem Worker-Host. Deklariere nur die Tools, die der Agent verwenden soll.

### Berechtigungsrichtlinien gelten nicht

[Berechtigungsrichtlinien](https://platform.claude.com/docs/de/managed-agents/permission-policies#custom-tools) steuern die integrierten und die MCP-Toolsets. Der Worker führt jeden gekapselten Tool-Aufruf aus, den das Modell tätigt, also baue jeden Genehmigungsschritt in deinen eigenen Tool-Code ein.
