---
title: Outils personnalisés dans les sandboxes auto-hébergées
url: https://platform.claude.com/docs/fr/managed-agents/self-hosted-sandboxes-custom-tools
description: Servez des outils personnalisés depuis un worker de sandbox auto-hébergée, et encapsulez un serveur MCP situé dans votre réseau sous forme d'outils personnalisés sans exécuter de tunnel.
featureMetadata:
  topic:
    title: Managed Agents
    url: https://platform.claude.com/docs/en/managed-agents/overview
  status: beta
  betaHeader: managed-agents-2026-04-01
---

Les [outils personnalisés](https://platform.claude.com/docs/fr/managed-agents/tools#custom-tools) sont des outils que votre propre code exécute : l'agent émet un événement `agent.custom_tool_use` et attend un `user.custom_tool_result` correspondant. Votre worker peut être ce code. Comme il s'exécute dans votre sandbox, l'outil accède aux services internes, aux identifiants et à la sortie réseau que vous avez configurés pour la sandbox, et à rien de plus.

La clé d'environnement autorise la publication des résultats des outils personnalisés, de sorte que votre clé API Claude reste en dehors de l'hôte du worker.

<Note>
  Servir des outils personnalisés nécessite le worker du SDK. Le worker de la CLI `ant` ne permet pas d'enregistrer une implémentation d'outil personnalisé. Dans le modèle d'une sandbox par session, [exécutez le worker du SDK dans la sandbox](https://platform.claude.com/docs/fr/managed-agents/self-hosted-sandboxes-workers#run-the-sdk-worker-inside-the-sandbox).
</Note>

## Servir un outil personnalisé

<Steps>
  <Step title="Déclarer l'outil sur l'agent">
    Ajoutez une entrée `custom` aux `tools` de l'agent dont le `name` correspond à l'outil que votre worker enregistre. Consultez [Outils personnalisés](https://platform.claude.com/docs/fr/managed-agents/tools#custom-tools) pour la structure complète de la déclaration.

    ```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="Enregistrer l'implémentation auprès du worker">
    Transmettez l'outil via la fabrique `tools` (go: `ToolsFunc`) du worker (voir [`EnvironmentWorker`](https://platform.claude.com/docs/fr/managed-agents/self-hosted-sandboxes-reference#environment-worker)), aux côtés de l'ensemble d'outils intégré :

    <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."""
          # S'exécute sur l'hôte du worker : appelez tout ce que la sandbox peut atteindre.
          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"]
        },
        // S'exécute sur l'hôte du worker : appelez tout ce que la sandbox peut atteindre.
        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#">
        Le SDK C# ne dispose pas de `EnvironmentWorker`. Pour répondre aux appels d'outils personnalisés depuis votre propre code, envoyez chaque résultat sur le flux d'événements de la session, comme dans [Renvoyer un résultat d'outil personnalisé](https://platform.claude.com/docs/fr/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"},
      		},
      		// S'exécute sur l'hôte du worker : appelez tout ce que la sandbox peut atteindre.
      		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">
        Le SDK Java ne dispose pas de `EnvironmentWorker`. Pour répondre aux appels d'outils personnalisés depuis votre propre code, envoyez chaque résultat sur le flux d'événements de la session, comme dans [Renvoyer un résultat d'outil personnalisé](https://platform.claude.com/docs/fr/managed-agents/events-and-streaming#return-a-custom-tool-result).
      </CodeGroupItem>

      <CodeGroupItem language="PHP">
        Le SDK PHP ne dispose pas de `EnvironmentWorker`. Pour répondre aux appels d'outils personnalisés depuis votre propre code, envoyez chaque résultat sur le flux d'événements de la session, comme dans [Renvoyer un résultat d'outil personnalisé](https://platform.claude.com/docs/fr/managed-agents/events-and-streaming#return-a-custom-tool-result).
      </CodeGroupItem>

      <CodeGroupItem language="Ruby">
        Le SDK Ruby ne dispose pas de `EnvironmentWorker`. Pour répondre aux appels d'outils personnalisés depuis votre propre code, envoyez chaque résultat sur le flux d'événements de la session, comme dans [Renvoyer un résultat d'outil personnalisé](https://platform.claude.com/docs/fr/managed-agents/events-and-streaming#return-a-custom-tool-result).
      </CodeGroupItem>
    </CodeGroup>
  </Step>
</Steps>

Le worker ne répond qu'aux outils enregistrés auprès de lui. Si un outil est déclaré sur l'agent mais qu'aucun worker ni client ne le sert, la session se met en pause avec une raison d'arrêt `requires_action`. Elle reste en pause jusqu'à ce que quelque chose publie le résultat. Consultez [Répondre aux appels d'outils qui mettent la session en pause](https://platform.claude.com/docs/fr/managed-agents/events-and-streaming#answer-tool-calls-that-pause-the-session) pour le flux d'événements.

## Encapsuler un serveur MCP sous forme d'outils personnalisés

Le [connecteur MCP](https://platform.claude.com/docs/fr/managed-agents/mcp-connector) se connecte aux serveurs MCP depuis le côté d'Anthropic. Un serveur doit donc exposer un point de terminaison HTTP qu'Anthropic peut atteindre, directement ou via un [tunnel MCP](https://platform.claude.com/docs/fr/agents-and-tools/mcp-tunnels/overview).

Pour utiliser un serveur que seul votre réseau peut atteindre, faites plutôt du worker le client MCP et déclarez les outils du serveur comme outils personnalisés. Le serveur MCP n'a besoin d'aucune connectivité entrante depuis l'extérieur de votre réseau. Anthropic reçoit les définitions d'outils que vous déclarez sur l'agent, l'entrée de chaque appel et le résultat que votre worker renvoie.

À l'exécution, le modèle appelle un outil encapsulé comme n'importe quel autre outil personnalisé :

1. L'agent émet un événement `agent.custom_tool_use`.
2. Le worker, dans votre sandbox, transmet l'appel via sa session MCP ouverte au serveur de votre réseau.
3. Le worker publie la réponse du serveur en tant que `user.custom_tool_result`.

### Installer un SDK MCP

Les [assistants MCP côté client](https://platform.claude.com/docs/fr/agents-and-tools/mcp-connector#client-side-mcp-helpers) du SDK convertissent les outils du serveur en outils exécutables que le worker accepte. Installez un SDK MCP aux côtés du SDK Anthropic : `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`).

Les exemples se connectent sans authentification. Pour envoyer des identifiants, configurez le `http_client` (typescript: `requestInit`; go: `HTTPClient`; csharp: `AdditionalHeaders`; java: `customizeRequest`; php, ruby: `headers`) que vous transmettez au transport MCP.

### Déclarer et servir les outils

<Steps>
  <Step title="Déclarer les outils du serveur sur l'agent">
    Listez les outils du serveur MCP et déclarez chacun d'eux comme un outil `custom`. Les champs MCP `name`, `description` et `inputSchema` correspondent un à un aux champs de l'outil personnalisé. Si le serveur pagine sa liste d'outils, déclarez chaque page ; le worker doit lister les mêmes pages.

    <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

      # Nécessite mcp >= 1.24, qui a renommé streamablehttp_client en 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:
          # Les champs MCP correspondent un à un à une déclaration d'outil personnalisé. Le cast
          # transmet le dictionnaire de schéma au paramètre typé du SDK sans modification.
          return {
              "type": "custom",
              "name": tool.name,
              "description": tool.description or tool.name,
              "input_schema": cast(Any, tool.inputSchema),
          }


      async def main() -> None:
          # Exécutez ceci là où vous créez les agents, pas sur l'hôte worker : il
          # s'authentifie avec votre clé API Claude (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";

      // Exécutez ceci là où vous créez les agents, pas sur l'hôte worker : il
      // s'authentifie avec votre clé API Claude (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" },
          // Les champs MCP correspondent un à un à une déclaration d'outil personnalisé.
          ...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";

      // Exécutez ceci là où vous créez des agents, pas sur l'hôte worker : il
      // s'authentifie avec votre clé API Claude (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;
          }

          // Les champs MCP correspondent un à un à une déclaration d'outil personnalisé. Les
          // mots-clés du schéma sont transmis au SDK sans modification.
          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 mappe une définition d'outil MCP vers une déclaration d'outil personnalisé.
      // Les champs correspondent un à un : le paramètre typé porte `properties` et
      // `required`, et tout autre mot-clé JSON Schema émis par le serveur transite dans
      // ExtraFields afin que le schéma déclaré corresponde à celui du serveur.
      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":
      			// Le type de paramètre sérialise toujours "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()

      	// Exécutez ceci là où vous créez des agents, pas sur l'hôte worker : il
      	// s'authentifie avec votre clé API Claude (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;

      // Les champs MCP correspondent un à un à une déclaration d'outil personnalisé. Le SDK Java
      // MCP ne conserve que ces mots-clés de premier niveau du schéma d'entrée d'un outil : type,
      // properties, required, additionalProperties, $defs et 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() {
          // Exécutez ceci là où vous créez des agents, pas sur l'hôte worker : il
          // s'authentifie avec votre clé API Claude (ANTHROPIC_API_KEY).
          var client = AnthropicOkHttpClient.fromEnv();

          // L'adresse du serveur MCP, puis le chemin de son endpoint 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';

      // Exécutez ceci là où vous créez les agents, pas sur l'hôte worker : il
      // s'authentifie avec votre clé API Claude (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) {
          // Les champs MCP correspondent un à un à une déclaration d'outil personnalisé. Les
          // mots-clés du schéma sont transmis au SDK sans modification.
          $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"

      # Exécutez ceci là où vous créez des agents, pas sur l'hôte worker : il
      # s'authentifie avec votre clé API Claude (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?

        # Les champs MCP correspondent un à un à une déclaration d'outil personnalisé. Les
        # mots-clés du schéma sont transmis au SDK sans modification.
        {
          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="Servir les outils depuis le worker">
    Connectez-vous au même serveur MCP au démarrage, convertissez ses outils avec `async_mcp_tool` (typescript: `mcpTools`; go: `mcp.NewBetaTools`), et enregistrez-les aux côtés de `beta_agent_toolset_20260401` (typescript: `betaAgentToolset20260401`; go: `agenttoolset.BetaAgentToolset20260401`). Gardez une seule session MCP ouverte pendant toute la durée de vie du worker.

    <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

      # Nécessite mcp >= 1.24, qui a renommé streamablehttp_client en 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"]
          # Se connecte au serveur MCP une seule fois au démarrage et garde la session ouverte pendant
          # toute la vie du worker. Le timeout transforme un appel d'outil bloqué en un résultat
          # d'erreur plutôt qu'en un appel figé.
          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());

      // Se connecte au serveur MCP une seule fois au démarrage et garde la connexion ouverte
      // pendant toute la durée de vie du 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();

      // Le type de retour de callTool du SDK MCP inclut encore une forme de résultat héritée que
      // mcpTools n'accepte pas ; on la restreint. À supprimer quand MCPClientLike s'élargira.
      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#">
        Le SDK C# ne dispose pas de `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))

      	// Se connecte au serveur MCP une seule fois au démarrage et garde la session ouverte pendant
      	// toute la durée de vie du 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">
        Le SDK Java ne dispose pas de `EnvironmentWorker`.
      </CodeGroupItem>

      <CodeGroupItem language="PHP">
        Le SDK PHP ne dispose pas de `EnvironmentWorker`.
      </CodeGroupItem>

      <CodeGroupItem language="Ruby">
        Le SDK Ruby ne dispose pas de `EnvironmentWorker`.
      </CodeGroupItem>
    </CodeGroup>
  </Step>
</Steps>

## Limites et comportement

### Les outils sont déclarés, et non découverts à l'exécution

Le worker liste les outils du serveur MCP une seule fois au démarrage et ne peut pas ajouter d'outils à une session en cours. Lorsque les outils du serveur changent :

1. Déclarez-les à nouveau, sur l'agent ou sur une session inactive via [Mise à jour de la configuration de l'agent](https://platform.claude.com/docs/fr/managed-agents/session-operations#updating-the-agent-configuration).
2. Redémarrez le worker.

### Les déclarations doivent respecter l'API Managed Agents

Les assistants MCP conservent les noms et descriptions du serveur, et la plupart des schémas passent sans modification. Renommez, raccourcissez ou intégrez en ligne lorsqu'une déclaration enfreint l'une de ces règles :

| Champ                         | Règle                                                                                                                                                                                                                                                                                                                                                                                                           |
| ----------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `name`                        | Unique par agent. Lettres, chiffres, traits de soulignement et traits d'union, de 1 à 128 caractères. Ne peut pas correspondre à un outil d'agent intégré tel que `bash` ou `read`, ni utiliser le préfixe réservé `mcp__` ou le préfixe `ant__`.                                                                                                                                                               |
| `description`                 | Obligatoire et non vide.                                                                                                                                                                                                                                                                                                                                                                                        |
| `input_schema`                | Accepte les mots-clés JSON Schema que les serveurs MCP émettent couramment, tels que `additionalProperties` et `title`. Rejette les mots-clés de référence tels que `$ref` à n'importe quel endroit, ainsi que `oneOf`, `anyOf` et `allOf` au niveau supérieur. Les noms de propriétés utilisent des lettres, des chiffres, des traits de soulignement, des points et des traits d'union, de 1 à 64 caractères. |
| Le tableau `tools` de l'agent | 256 entrées au maximum. Chaque outil encapsulé constitue une entrée, et l'ensemble d'outils intégré en constitue une de plus.                                                                                                                                                                                                                                                                                   |

Deux cas nécessitent un travail supplémentaire :

* **Deux serveurs exposent le même nom d'outil :** définissez vous-même l'encapsulation sous un nom préfixé et faites-lui appeler le nom d'outil d'origine du serveur.
* **Un générateur tel que pydantic factorise les schémas dans `$defs` :** intégrez ces schémas en ligne avant de déclarer l'outil.

### Les échecs d'outils apparaissent sous forme de résultats d'outils en erreur

Lorsque le serveur MCP signale une erreur d'outil, le worker publie un résultat d'outil en erreur auquel le modèle peut réagir. Le contenu MCP sans équivalent en résultat d'outil, comme les blocs audio et les liens de ressources, apparaît également sous forme d'erreur.

Définissez un délai d'expiration sur le client MCP pour obtenir un échec plus rapide et plus clair, comme le fait l'exemple de worker Python avec `read_timeout_seconds`. Consultez [Un appel d'outil MCP encapsulé reste bloqué](https://platform.claude.com/docs/fr/managed-agents/self-hosted-sandboxes-operations#a-wrapped-mcp-tool-call-hangs) pour savoir ce qui se passe sans délai d'expiration.

### N'encapsulez que des serveurs que vous exploitez ou auxquels vous faites confiance

Le nom, la description et les résultats d'un outil encapsulé entrent dans le contexte du modèle comme ceux de n'importe quel autre outil. Il s'agit d'entrées non fiables qui peuvent influencer ce que l'agent fait avec ses autres outils, y compris `bash` sur l'hôte du worker. Ne déclarez que les outils que vous souhaitez que l'agent utilise.

### Les politiques d'autorisation ne s'appliquent pas

Les [politiques d'autorisation](https://platform.claude.com/docs/fr/managed-agents/permission-policies#custom-tools) régissent les ensembles d'outils intégrés et MCP. Le worker exécute chaque appel d'outil encapsulé effectué par le modèle ; placez donc toute étape d'approbation dans votre propre code d'outil.
