---
title: Strumenti personalizzati nelle sandbox self-hosted
url: https://platform.claude.com/docs/it/managed-agents/self-hosted-sandboxes-custom-tools
description: Servi strumenti personalizzati da un worker di sandbox self-hosted e incapsula un server MCP all'interno della tua rete come strumenti personalizzati senza eseguire un tunnel.
featureMetadata:
  topic:
    title: Managed Agents
    url: https://platform.claude.com/docs/en/managed-agents/overview
  status: beta
  betaHeader: managed-agents-2026-04-01
---

Gli [strumenti personalizzati](https://platform.claude.com/docs/it/managed-agents/tools#custom-tools) sono strumenti eseguiti dal tuo codice: l'agente emette un evento `agent.custom_tool_use` e attende un `user.custom_tool_result` corrispondente. Il tuo worker può essere quel codice. Poiché viene eseguito all'interno della tua sandbox, lo strumento raggiunge i servizi interni, le credenziali e l'"egress" (traffico di rete in uscita) che hai configurato per la sandbox, e nient'altro.

La chiave dell'ambiente autorizza l'invio dei risultati degli strumenti personalizzati, quindi la tua chiave API di Claude resta fuori dall'host del worker.

<Note>
  Servire strumenti personalizzati richiede il worker dell'SDK. Il worker della CLI `ant` non ha modo di registrare l'implementazione di uno strumento personalizzato. Nel pattern sandbox-per-sessione, [esegui il worker dell'SDK all'interno della sandbox](https://platform.claude.com/docs/it/managed-agents/self-hosted-sandboxes-workers#run-the-sdk-worker-inside-the-sandbox).
</Note>

## Servire uno strumento personalizzato

<Steps>
  <Step title="Dichiara lo strumento sull'agente">
    Aggiungi una voce `custom` ai `tools` dell'agente il cui `name` corrisponda allo strumento registrato dal tuo worker. Consulta [Strumenti personalizzati](https://platform.claude.com/docs/it/managed-agents/tools#custom-tools) per la struttura completa della dichiarazione.

    ```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="Registra l'implementazione con il worker">
    Passa lo strumento attraverso la factory `tools` (go: `ToolsFunc`) del worker (consulta [`EnvironmentWorker`](https://platform.claude.com/docs/it/managed-agents/self-hosted-sandboxes-reference#environment-worker)), insieme al set di strumenti integrato:

    <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."""
          # Viene eseguito sull'host del worker: chiama qualsiasi cosa raggiungibile dalla sandbox.
          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"]
        },
        // Viene eseguito sull'host del worker: chiama qualsiasi cosa raggiungibile dalla sandbox.
        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#">
        L'SDK C# non dispone di `EnvironmentWorker`. Per rispondere alle chiamate agli strumenti personalizzati dal tuo codice, invia ogni risultato sul flusso di eventi della sessione, come in [Restituire il risultato di uno strumento personalizzato](https://platform.claude.com/docs/it/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"},
      		},
      		// Viene eseguito sull'host del worker: chiama qualsiasi cosa raggiungibile dalla sandbox.
      		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">
        L'SDK Java non dispone di `EnvironmentWorker`. Per rispondere alle chiamate agli strumenti personalizzati dal tuo codice, invia ogni risultato sul flusso di eventi della sessione, come in [Restituire il risultato di uno strumento personalizzato](https://platform.claude.com/docs/it/managed-agents/events-and-streaming#return-a-custom-tool-result).
      </CodeGroupItem>

      <CodeGroupItem language="PHP">
        L'SDK PHP non dispone di `EnvironmentWorker`. Per rispondere alle chiamate agli strumenti personalizzati dal tuo codice, invia ogni risultato sul flusso di eventi della sessione, come in [Restituire il risultato di uno strumento personalizzato](https://platform.claude.com/docs/it/managed-agents/events-and-streaming#return-a-custom-tool-result).
      </CodeGroupItem>

      <CodeGroupItem language="Ruby">
        L'SDK Ruby non dispone di `EnvironmentWorker`. Per rispondere alle chiamate agli strumenti personalizzati dal tuo codice, invia ogni risultato sul flusso di eventi della sessione, come in [Restituire il risultato di uno strumento personalizzato](https://platform.claude.com/docs/it/managed-agents/events-and-streaming#return-a-custom-tool-result).
      </CodeGroupItem>
    </CodeGroup>
  </Step>
</Steps>

Il worker risponde solo agli strumenti registrati presso di esso. Se uno strumento è dichiarato sull'agente ma nessun worker o client lo serve, la sessione si mette in pausa con uno "stop reason" (motivo di arresto) `requires_action`. Rimane in pausa finché qualcosa non invia il risultato. Consulta [Rispondere alle chiamate agli strumenti che mettono in pausa la sessione](https://platform.claude.com/docs/it/managed-agents/events-and-streaming#answer-tool-calls-that-pause-the-session) per il flusso degli eventi.

## Incapsulare un server MCP come strumenti personalizzati

Il [connettore MCP](https://platform.claude.com/docs/it/managed-agents/mcp-connector) si connette ai server MCP dal lato di Anthropic. Un server deve quindi esporre un endpoint HTTP che Anthropic possa raggiungere, direttamente o tramite un [tunnel MCP](https://platform.claude.com/docs/it/agents-and-tools/mcp-tunnels/overview).

Per usare un server raggiungibile solo dalla tua rete, rendi invece il worker il client MCP e dichiara gli strumenti del server come strumenti personalizzati. Il server MCP non necessita di connettività in ingresso dall'esterno della tua rete. Anthropic riceve le definizioni degli strumenti che dichiari sull'agente, l'input di ogni chiamata e il risultato che il tuo worker invia.

In fase di esecuzione il modello chiama uno strumento incapsulato come qualsiasi altro strumento personalizzato:

1. L'agente emette un evento `agent.custom_tool_use`.
2. Il worker, all'interno della tua sandbox, inoltra la chiamata tramite la sua sessione MCP aperta al server sulla tua rete.
3. Il worker invia la risposta del server come `user.custom_tool_result`.

### Installare un SDK MCP

Gli [helper MCP lato client](https://platform.claude.com/docs/it/agents-and-tools/mcp-connector#client-side-mcp-helpers) dell'SDK convertono gli strumenti del server negli strumenti eseguibili accettati dal worker. Installa un SDK MCP insieme all'SDK di 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`).

Gli esempi si connettono senza autenticazione. Per inviare credenziali, configura il `http_client` (typescript: `requestInit`; go: `HTTPClient`; csharp: `AdditionalHeaders`; java: `customizeRequest`; php, ruby: `headers`) che passi al trasporto MCP.

### Dichiarare e servire gli strumenti

<Steps>
  <Step title="Dichiara gli strumenti del server sull'agente">
    Elenca gli strumenti del server MCP e dichiara ciascuno come strumento `custom`. I campi MCP `name`, `description` e `inputSchema` corrispondono uno a uno ai campi dello strumento personalizzato. Se il server pagina il suo elenco di strumenti, dichiara ogni pagina; il worker deve elencare le stesse pagine.

    <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

      # Richiede mcp >= 1.24, che ha rinominato streamablehttp_client in 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:
          # I campi MCP corrispondono uno a uno a una dichiarazione di strumento personalizzato. Il cast
          # passa il dizionario dello schema al parametro tipizzato dell'SDK senza modifiche.
          return {
              "type": "custom",
              "name": tool.name,
              "description": tool.description or tool.name,
              "input_schema": cast(Any, tool.inputSchema),
          }


      async def main() -> None:
          # Esegui questo codice dove crei gli agenti, non sull'host worker: si
          # autentica con la tua chiave API di 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";

      // Esegui questo codice dove crei gli agenti, non sull'host worker:
      // si autentica con la tua chiave API di 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" },
          // I campi MCP corrispondono uno a uno a una dichiarazione di strumento personalizzato.
          ...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";

      // Esegui questo codice dove crei gli agenti, non sull'host worker:
      // si autentica con la tua chiave API di 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;
          }

          // I campi MCP corrispondono uno a uno a una dichiarazione di strumento personalizzato. Le
          // parole chiave dello schema passano all'SDK invariate.
          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 mappa una definizione di strumento MCP su una dichiarazione di strumento personalizzato.
      // I campi corrispondono uno a uno: il parametro tipizzato contiene `properties` e
      // `required`, e ogni altra keyword JSON Schema emessa dal server viaggia in
      // ExtraFields così che lo schema dichiarato corrisponda a quello del server.
      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":
      			// Il tipo del parametro serializza sempre "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()

      	// Esegui questo dove crei gli agenti, non sull'host worker: si
      	// autentica con la tua chiave 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;

      // I campi MCP corrispondono uno a uno a una dichiarazione di strumento personalizzato. L'SDK Java
      // MCP mantiene solo queste parole chiave di primo livello dello schema di input di uno strumento: type,
      // properties, required, additionalProperties, $defs e 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() {
          // Esegui questo codice dove crei gli agenti, non sull'host worker: si
          // autentica con la tua chiave API di Claude (ANTHROPIC_API_KEY).
          var client = AnthropicOkHttpClient.fromEnv();

          // L'indirizzo del server MCP, poi il percorso del suo 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';

      // Esegui questo codice dove crei gli agenti, non sull'host worker:
      // si autentica con la tua chiave API di 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) {
          // I campi MCP corrispondono uno a uno a una dichiarazione di strumento personalizzato. Le
          // parole chiave dello schema passano all'SDK invariate.
          $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"

      # Esegui questo codice dove crei gli agenti, non sull'host worker:
      # si autentica con la tua chiave API di 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?

        # I campi MCP corrispondono uno a uno a una dichiarazione di strumento personalizzato. Le
        # parole chiave dello schema passano all'SDK invariate.
        {
          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="Servi gli strumenti dal worker">
    Connettiti allo stesso server MCP all'avvio, converti i suoi strumenti con `async_mcp_tool` (typescript: `mcpTools`; go: `mcp.NewBetaTools`) e registrali insieme a `beta_agent_toolset_20260401` (typescript: `betaAgentToolset20260401`; go: `agenttoolset.BetaAgentToolset20260401`). Mantieni una sessione MCP aperta per tutta la durata del 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

      # Richiede mcp >= 1.24, che ha rinominato streamablehttp_client in 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"]
          # Si connette al server MCP una sola volta all'avvio e mantiene la sessione aperta per
          # tutta la vita del worker. Il timeout trasforma una chiamata a uno strumento bloccata
          # in un risultato di errore anziché in una chiamata in stallo.
          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());

      // Connettiti al server MCP una sola volta all'avvio e mantieni la connessione aperta per
      // tutta la vita del 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();

      // Il tipo di ritorno di callTool dell'SDK MCP include ancora una forma di risultato legacy che
      // mcpTools non accetta; restringilo. Rimuovi questo quando MCPClientLike verrà ampliato.
      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#">
        L'SDK C# non dispone di `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))

      	// Connettiti al server MCP una sola volta all'avvio e mantieni la sessione aperta per
      	// tutta la vita del 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">
        L'SDK Java non dispone di `EnvironmentWorker`.
      </CodeGroupItem>

      <CodeGroupItem language="PHP">
        L'SDK PHP non dispone di `EnvironmentWorker`.
      </CodeGroupItem>

      <CodeGroupItem language="Ruby">
        L'SDK Ruby non dispone di `EnvironmentWorker`.
      </CodeGroupItem>
    </CodeGroup>
  </Step>
</Steps>

## Limiti e comportamento

### Gli strumenti vengono dichiarati, non scoperti in fase di esecuzione

Il worker elenca gli strumenti del server MCP una sola volta all'avvio e non può aggiungere strumenti a una sessione in esecuzione. Quando gli strumenti del server cambiano:

1. Dichiarali di nuovo, sull'agente o su una sessione inattiva, come descritto in [Aggiornare la configurazione dell'agente](https://platform.claude.com/docs/it/managed-agents/session-operations#updating-the-agent-configuration).
2. Riavvia il worker.

### Le dichiarazioni devono rispettare la Managed Agents API

Gli helper MCP mantengono i nomi e le descrizioni del server, e la maggior parte degli schemi passa invariata. Rinomina, accorcia o rendi inline dove una dichiarazione viola una di queste regole:

| Campo                       | Regola                                                                                                                                                                                                                                                                                                                                             |
| --------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `name`                      | Univoco per agente. Lettere, cifre, trattini bassi e trattini, da 1 a 128 caratteri. Non può corrispondere a uno strumento integrato dell'agente come `bash` o `read`, né usare il prefisso riservato `mcp__` o il prefisso `ant__`.                                                                                                               |
| `description`               | Obbligatorio e non vuoto.                                                                                                                                                                                                                                                                                                                          |
| `input_schema`              | Accetta le parole chiave JSON Schema che i server MCP emettono comunemente, come `additionalProperties` e `title`. Rifiuta parole chiave di riferimento come `$ref` in qualsiasi posizione, e `oneOf`, `anyOf` e `allOf` al livello superiore. I nomi delle proprietà usano lettere, cifre, trattini bassi, punti e trattini, da 1 a 64 caratteri. |
| L'array `tools` dell'agente | Al massimo 256 voci. Ogni strumento incapsulato è una voce, e il set di strumenti integrato è un'altra.                                                                                                                                                                                                                                            |

Due casi richiedono lavoro aggiuntivo:

* **Due server espongono lo stesso nome di strumento:** definisci tu stesso il wrapper con un nome prefissato e fagli chiamare il nome originale dello strumento del server.
* **Un generatore come pydantic scompone gli schemi in `$defs`:** rendi inline quegli schemi prima di dichiarare lo strumento.

### I fallimenti degli strumenti emergono come risultati di errore degli strumenti

Quando il server MCP segnala un errore dello strumento, il worker invia un risultato di errore dello strumento a cui il modello può reagire. Anche il contenuto MCP senza un equivalente come risultato di strumento, come blocchi audio e link a risorse, emerge come errore.

Imposta un timeout sul client MCP per un fallimento più rapido e chiaro, come fa l'esempio del worker Python con `read_timeout_seconds`. Consulta [Una chiamata a uno strumento MCP incapsulato si blocca](https://platform.claude.com/docs/it/managed-agents/self-hosted-sandboxes-operations#a-wrapped-mcp-tool-call-hangs) per sapere cosa succede senza di esso.

### Incapsula solo server che gestisci o di cui ti fidi

Il nome, la descrizione e i risultati di uno strumento incapsulato entrano nel contesto del modello come quelli di qualsiasi altro strumento. Sono input non attendibili che possono influenzare ciò che l'agente fa con i suoi altri strumenti, incluso `bash` sull'host del worker. Dichiara solo gli strumenti che intendi far usare all'agente.

### Le policy di autorizzazione non si applicano

Le [policy di autorizzazione](https://platform.claude.com/docs/it/managed-agents/permission-policies#custom-tools) regolano i set di strumenti integrati e MCP. Il worker esegue ogni chiamata a uno strumento incapsulato effettuata dal modello, quindi inserisci qualsiasi passaggio di approvazione nel codice del tuo strumento.
