SpyBara
Go Premium

agent-sdk/custom-tools.md 2026-11-09 23:02 UTC to 2026-11-10 21:57 UTC

No changes for this document in this history range.

2026
Mon 9 23:02

Dê a Claude ferramentas personalizadas

Defina ferramentas personalizadas com o servidor MCP em processo do Agent SDK do Claude para que Claude possa chamar suas funções, acessar suas APIs e executar operações específicas do domínio.

Ferramentas personalizadas estendem o Agent SDK permitindo que você defina suas próprias funções que Claude pode chamar durante uma conversa. Usando o servidor MCP em processo do SDK, você pode dar a Claude acesso a bancos de dados, APIs externas, lógica específica do domínio ou qualquer outra capacidade que sua aplicação necessite.

Referência rápida

Se você quer... Faça isto
Definir uma ferramenta Use @tool (Python) ou tool() (TypeScript) com um nome, descrição, esquema e manipulador. Veja Criar uma ferramenta personalizada.
Tornar um parâmetro opcional Declare-o como opcional no esquema e aplique o valor padrão no manipulador. Veja Tornar um parâmetro opcional.
Registrar uma ferramenta com Claude Envolva em create_sdk_mcp_server / createSdkMcpServer e passe para mcpServers em query(). Veja Chamar uma ferramenta personalizada.
Pré-aprovar uma ferramenta Adicione às suas ferramentas permitidas. Veja Configurar ferramentas permitidas.
Exigir aprovação para chamadas a uma ferramenta, ou bloquear a ferramenta No SDK TypeScript, adicione uma permission_policy para a ferramenta ao objeto de configuração que createSdkMcpServer() retorna. Veja Exigir aprovação para ferramentas específicas.
Remover uma ferramenta integrada do contexto de Claude Passe um array tools listando apenas os integrados que você quer. Veja Configurar ferramentas permitidas.
Deixar Claude chamar ferramentas em paralelo Defina readOnlyHint: true em ferramentas sem efeitos colaterais. Veja Adicionar anotações de ferramentas.
Controlar a mensagem de erro que Claude lê Retorne isError: true para compor a mensagem em vez de expor a exceção bruta. Veja Tratar erros.
Retornar imagens ou arquivos Use blocos image ou resource no array de conteúdo. Veja Retornar imagens e recursos.
Retornar um resultado JSON legível por máquina Defina structuredContent no resultado. Veja Retornar dados estruturados.
Escalar para muitas ferramentas Use tool search para carregar ferramentas sob demanda.

Criar uma ferramenta personalizada

Uma ferramenta é definida por quatro partes, passadas como argumentos para o auxiliar tool() em TypeScript ou o decorador @tool em Python:

  • Nome: um identificador único que Claude usa para chamar a ferramenta.
  • Descrição: o que a ferramenta faz. Claude lê isso para decidir quando chamá-la.
  • Esquema de entrada: os argumentos que a ferramenta aceita, declarados conforme a linguagem:
    • TypeScript: um esquema Zod. Os args do manipulador obtêm seus tipos a partir dele. Chame .describe() em um campo para dar a ele uma descrição que Claude vê.
    • Python: um dicionário mapeando nomes para tipos, como {"latitude": float}, que o SDK converte para JSON Schema para você. Envolva um tipo em Annotated, como {"latitude": Annotated[float, "Latitude coordinate"]}, para dar ao campo uma descrição que Claude vê. O decorador também aceita um dicionário completo de JSON Schema diretamente quando você precisa de enums, intervalos, campos opcionais ou objetos aninhados.
  • Manipulador: a função assíncrona que é executada quando Claude chama a ferramenta. Ela recebe os argumentos validados e deve retornar um objeto com:
    • content (obrigatório): um array de blocos de resultado, cada um com um type de "text", "image", "audio", "resource" ou "resource_link". Veja Retornar imagens e recursos para blocos não-texto.
    • structuredContent (opcional): um objeto JSON contendo o resultado como dados legíveis por máquina, retornado junto com content. Veja Retornar dados estruturados.
    • isError (opcional): defina como true para sinalizar uma falha da ferramenta para que Claude possa reagir a ela. Veja Tratar erros.

Após definir uma ferramenta, envolva-a em um servidor com createSdkMcpServer (TypeScript) ou create_sdk_mcp_server (Python). O servidor é executado no processo dentro de sua aplicação, não como um processo separado.

Os exemplos em Python nesta página que fazem requisições HTTP usam httpx. Adicione-o com o gerenciador de pacotes que seu projeto usa:

[tab] Python (uv)

uv add httpx

[tab] Python (pip)

pip install httpx

Exemplo de ferramenta de clima

Este exemplo define uma ferramenta get_temperature e a envolve em um servidor MCP, sem passar o servidor para query. Para executar a ferramenta, veja Chamar uma ferramenta personalizada.

from typing import Annotated, Any
import httpx
from claude_agent_sdk import tool, create_sdk_mcp_server


# Define a tool: name, description, input schema, handler
@tool(
    "get_temperature",
    "Get the current temperature at a location",
    {
        "latitude": Annotated[float, "Latitude coordinate"],
        "longitude": Annotated[float, "Longitude coordinate"],
    },
)
async def get_temperature(args: dict[str, Any]) -> dict[str, Any]:
    async with httpx.AsyncClient() as client:
        response = await client.get(
            "https://api.open-meteo.com/v1/forecast",
            params={
                "latitude": args["latitude"],
                "longitude": args["longitude"],
                "current": "temperature_2m",
                "temperature_unit": "fahrenheit",
            },
        )
        data = response.json()

    # Return a content array - Claude sees this as the tool result
    return {
        "content": [
            {
                "type": "text",
                "text": f"Temperature: {data['current']['temperature_2m']}°F",
            }
        ]
    }


# Wrap the tool in an in-process MCP server
weather_server = create_sdk_mcp_server(
    name="weather",
    version="1.0.0",
    tools=[get_temperature],
)
import { tool, createSdkMcpServer } from "@anthropic-ai/claude-agent-sdk";
import { z } from "zod";

// Define a tool: name, description, input schema, handler
const getTemperature = tool(
  "get_temperature",
  "Get the current temperature at a location",
  {
    latitude: z.number().describe("Latitude coordinate"), // .describe() adds a field description Claude sees
    longitude: z.number().describe("Longitude coordinate")
  },
  async (args) => {
    // args is typed from the schema: { latitude: number; longitude: number }
    const response = await fetch(
      `https://api.open-meteo.com/v1/forecast?latitude=${args.latitude}&longitude=${args.longitude}&current=temperature_2m&temperature_unit=fahrenheit`
    );
    const data: any = await response.json();

    // Return a content array - Claude sees this as the tool result
    return {
      content: [{ type: "text", text: `Temperature: ${data.current.temperature_2m}°F` }]
    };
  }
);

// Wrap the tool in an in-process MCP server
const weatherServer = createSdkMcpServer({
  name: "weather",
  version: "1.0.0",
  tools: [getTemperature]
});

Veja a referência TypeScript tool() ou a referência Python @tool para detalhes completos dos parâmetros, incluindo formatos de entrada JSON Schema e estrutura de valor de retorno.

Tornar um parâmetro opcional

Para tornar um parâmetro opcional, declare-o como opcional no esquema e aplique o padrão no manipulador:

  • TypeScript: adicione .optional() ao campo Zod.
  • Python: o esquema de dicionário exige todas as chaves. Use a forma JSON Schema, deixe o parâmetro fora de required e leia-o com args.get(). Para um esquema tipado com chaves opcionais, veja classe TypedDict.

A ferramenta get_precipitation_chance mostra ambos os padrões.

Chamar uma ferramenta personalizada

Para chamar uma ferramenta personalizada, siga estes passos:

  1. Passe o servidor MCP que você criou para query usando a opção mcpServers.
  2. Liste o nome totalmente qualificado da ferramenta em allowedTools para pré-aprovar suas chamadas. Esse nome segue a convenção mcp__{server_name}__{tool_name}, onde {server_name} é a chave que você atribui ao servidor em mcpServers. Se a chave contiver caracteres como . ou espaços, substitua-os por _ ao escrever {server_name}.

Estes trechos reutilizam o weatherServer do exemplo de ferramenta de clima para perguntar a Claude qual é o clima em um local específico.

import asyncio
from claude_agent_sdk import query, ClaudeAgentOptions, ResultMessage


async def main():
    options = ClaudeAgentOptions(
        mcp_servers={"weather": weather_server},
        allowed_tools=["mcp__weather__get_temperature"],
    )

    async for message in query(
        prompt="What's the temperature in San Francisco?",
        options=options,
    ):
        # ResultMessage is the final message after all tool calls complete
        if isinstance(message, ResultMessage) and message.subtype == "success":
            print(message.result)


asyncio.run(main())
import { query } from "@anthropic-ai/claude-agent-sdk";

for await (const message of query({
  prompt: "What's the temperature in San Francisco?",
  options: {
    mcpServers: { weather: weatherServer },
    allowedTools: ["mcp__weather__get_temperature"]
  }
})) {
  // "result" is the final message after all tool calls complete
  if (message.type === "result" && message.subtype === "success") {
    console.log(message.result);
  }
}

Combine este trecho com as definições de ferramenta e servidor do exemplo de ferramenta de clima em um arquivo, weather.py ou weather.ts, depois execute-o a partir do seu terminal:

[tab] TypeScript

npx tsx weather.ts

[tab] Python (uv)

uv run weather.py

[tab] Python (pip)

Ative o ambiente virtual onde você instalou o SDK e, em seguida, execute:

python weather.py

Claude chama get_temperature e o script imprime uma resposta de uma linha com a temperatura atual em San Francisco.

Adicionar mais ferramentas

Um servidor contém quantas ferramentas você listar em seu array tools. Com mais de uma ferramenta em um servidor, você pode listar cada uma em allowedTools individualmente ou usar o curinga mcp__weather__* para cobrir todas as ferramentas que o servidor expõe.

O exemplo abaixo define uma segunda ferramenta, get_precipitation_chance, e substitui a definição de weatherServer do exemplo de ferramenta de clima por uma que lista ambas as ferramentas no array.

# Define a second tool for the same server
@tool(
    "get_precipitation_chance",
    "Get the hourly precipitation probability for a location",
    {
        "type": "object",
        "properties": {
            "latitude": {"type": "number"},
            "longitude": {"type": "number"},
            "hours": {
                "type": "integer",
                "minimum": 1,
                "maximum": 24,
                "description": "How many hours of forecast to return",
            },
        },
        # 'hours' is left out of required, so Claude can omit it
        "required": ["latitude", "longitude"],
    },
)
async def get_precipitation_chance(args: dict[str, Any]) -> dict[str, Any]:
    # 'hours' isn't in required - read it with .get() to fall back to a default
    hours = args.get("hours", 12)
    async with httpx.AsyncClient() as client:
        response = await client.get(
            "https://api.open-meteo.com/v1/forecast",
            params={
                "latitude": args["latitude"],
                "longitude": args["longitude"],
                "hourly": "precipitation_probability",
                "forecast_days": 1,
            },
        )
        data = response.json()
    chances = data["hourly"]["precipitation_probability"][:hours]

    return {
        "content": [
            {
                "type": "text",
                "text": f"Next {hours} hours: {'%, '.join(map(str, chances))}%",
            }
        ]
    }


# Rebuild the server with both tools in the array
weather_server = create_sdk_mcp_server(
    name="weather",
    version="1.0.0",
    tools=[get_temperature, get_precipitation_chance],
)
// Define a second tool for the same server
const getPrecipitationChance = tool(
  "get_precipitation_chance",
  "Get the hourly precipitation probability for a location",
  {
    latitude: z.number(),
    longitude: z.number(),
    hours: z
      .number()
      .int()
      .min(1)
      .max(24)
      .optional() // .optional() lets Claude omit the parameter
      .describe("How many hours of forecast to return")
  },
  async (args) => {
    const hours = args.hours ?? 12; // Apply the default in the handler
    const response = await fetch(
      `https://api.open-meteo.com/v1/forecast?latitude=${args.latitude}&longitude=${args.longitude}&hourly=precipitation_probability&forecast_days=1`
    );
    const data: any = await response.json();
    const chances = data.hourly.precipitation_probability.slice(0, hours);

    return {
      content: [{ type: "text", text: `Next ${hours} hours: ${chances.join("%, ")}%` }]
    };
  }
);

// Rebuild the server with both tools in the array
const weatherServer = createSdkMcpServer({
  name: "weather",
  version: "1.0.0",
  tools: [getTemperature, getPrecipitationChance]
});

Tool search está ativado por padrão e adia ferramentas SDK MCP: Claude vê o nome de cada ferramenta em uma lista compacta e carrega seu esquema completo sob demanda. Com a busca de ferramentas desativada, cada ferramenta neste array consome espaço da janela de contexto a cada turno. Em TypeScript, passe alwaysLoad: true no argumento extras de tool() ou nas opções de createSdkMcpServer() para manter o esquema completo de uma ferramenta no prompt inicial.

Adicionar anotações de ferramenta

Anotações de ferramenta são metadados opcionais descrevendo como uma ferramenta se comporta. Passe-as como o quinto argumento para o auxiliar tool() em TypeScript ou via o argumento de palavra-chave annotations para o decorador @tool em Python. Todos os campos de dica são Booleanos.

Campo Padrão Significado
readOnlyHint false A ferramenta não modifica seu ambiente. Controla se a ferramenta pode ser chamada em paralelo com outras ferramentas somente leitura.
destructiveHint true A ferramenta pode realizar atualizações destrutivas. Defina-o explicitamente como true em qualquer ferramenta que você marcar como always_ask.
idempotentHint false Chamadas repetidas com os mesmos argumentos não têm efeito adicional.
openWorldHint true A ferramenta alcança sistemas fora de seu processo.

Anotações são metadados, não imposição. Uma ferramenta marcada com readOnlyHint: true ainda pode escrever em disco se isso for o que o manipulador faz. Mantenha a anotação precisa em relação ao manipulador.

Este exemplo adiciona readOnlyHint à ferramenta get_temperature do exemplo de ferramenta de clima.

from claude_agent_sdk import tool, ToolAnnotations


@tool(
    "get_temperature",
    "Get the current temperature at a location",
    {"latitude": float, "longitude": float},
    annotations=ToolAnnotations(
        readOnlyHint=True
    ),  # Lets Claude batch this with other read-only calls
)
async def get_temperature(args):
    return {"content": [{"type": "text", "text": "..."}]}
import { tool } from "@anthropic-ai/claude-agent-sdk";
import { z } from "zod";

tool(
  "get_temperature",
  "Get the current temperature at a location",
  { latitude: z.number(), longitude: z.number() },
  async (args) => ({ content: [{ type: "text", text: `...` }] }),
  { annotations: { readOnlyHint: true } } // Lets Claude batch this with other read-only calls
);

Veja ToolAnnotations na referência TypeScript ou Python.

Controlar o acesso à ferramenta

O exemplo de ferramenta de clima registrou um servidor e listou ferramentas em allowedTools. Esta seção aborda como definir o escopo de acesso quando você tem múltiplas ferramentas ou deseja restringir as ferramentas integradas. Para saber como os nomes das ferramentas são construídos, consulte Chamar uma ferramenta personalizada.

Configurar ferramentas permitidas

Você controla duas camadas com as opções tools, allowedTools e disallowedTools:

  • Disponibilidade: se uma ferramenta aparece no contexto do Claude. Para alterá-la, use a opção tools ou liste um nome simples de ferramenta integrada, como "Bash", em disallowedTools.
  • Permissão: se Claude Code aprova uma chamada quando Claude a faz. Para alterá-la, use allowedTools ou uma regra disallowedTools com escopo, como "Bash(rm *)".
Opção Camada Efeito
tools: ["Read", "Grep"] Disponibilidade Apenas as ferramentas integradas listadas estão no contexto do Claude. As ferramentas integradas não listadas são removidas. As ferramentas MCP não são afetadas.
tools: [] Disponibilidade Todas as ferramentas integradas são removidas. Claude pode usar apenas suas ferramentas MCP.
ferramentas permitidas Permissão As ferramentas listadas são pré-aprovadas, exceto pelas chamadas que nenhuma regra de permissão aprova. As chamadas a ferramentas que você deixa fora da lista passam pelo fluxo de permissão.
ferramentas não permitidas Ambas Um nome simples de ferramenta integrada como "Bash" remove a ferramenta do contexto do Claude. Uma regra com escopo como "Bash(rm *)" deixa a ferramenta no contexto e nega as chamadas correspondentes conforme escrito.

Para remover uma ferramenta integrada do contexto do Claude, omita-a de tools ou liste seu nome simples em disallowedTools (TypeScript) ou disallowed_tools (Python). Para a ordem de avaliação completa, consulte Configurar permissões.

Exigir aprovação para ferramentas específicas

Defina a permission_policy de uma ferramenta como always_ask, e allowedTools deixa de aprovar as chamadas dessa ferramenta, de modo que elas podem chegar ao seu callback canUseTool mesmo que a ferramenta esteja listada. Em vez disso, defina always_deny, e uma chamada à ferramenta é negada, mas um mod em um plugin que você carrega ainda pode aprová-la. O campo é exclusivo do TypeScript e requer o Agent SDK v0.3.301 ou posterior.

Coloque a entrada da política em um array tools no objeto de configuração que createSdkMcpServer() retorna, e não na opção tools que você passa para essa função. Este exemplo espalha weatherServer do exemplo de ferramenta de clima e adiciona um array tools que atribui a get_temperature a política always_ask:

options: {
  mcpServers: {
    weather: {
      ...weatherServer,
      tools: [{ name: "get_temperature", permission_policy: "always_ask" as const }]
    }
  },
  allowedTools: ["mcp__weather__get_temperature"],
  permissionMode: "default",
  canUseTool: async (toolName, input) => {
    console.log(`Approval requested for ${toolName}`);
    return { behavior: "allow", updatedInput: input };
  }
}

Para testar a política, substitua as options no script de Chamar uma ferramenta personalizada pelas options deste exemplo e, em seguida, execute esse script no seu terminal. O script imprime Approval requested for mcp__weather__get_temperature, seguido da resposta do Claude sobre a temperatura. Para um script completo, consulte a referência do TypeScript.

Se você usar o modo de permissão auto ou plan, defina destructiveHint: true nas anotações da ferramenta para que as chamadas à ferramenta ainda cheguem ao seu callback. Caso contrário, o classificador do modo auto pode aprovar algumas chamadas always_ask, sob condições listadas na referência do TypeScript, e uma chamada que ele aprova não chega ao seu callback. Defina a dica explicitamente, porque um destructiveHint não definido conta como false para chamadas always_ask, em vez de assumir true como padrão.

Tratar erros

Um erro de handler não interrompe o loop do agente. O servidor MCP em processo do SDK captura exceções não capturadas e as retorna como resultados de erro, portanto, a forma como você relata um erro determina o que Claude lê, não se a consulta falha:

O que acontece Resultado
Handler lança uma exceção não capturada O servidor MCP a converte em um resultado de erro contendo a mensagem de exceção bruta. Claude vê essa mensagem e o loop do agente continua.
Handler captura o erro e retorna isError: true (TS) / "is_error": True (Python) Claude vê a mensagem que você compõe. Você pode adicionar contexto que a exceção bruta não possui, como qual solicitação falhou ou o que tentar em vez disso.

Em ambos os casos, Claude pode tentar novamente, tentar uma ferramenta diferente ou explicar a falha. Capture erros você mesmo quando a mensagem de exceção bruta não for suficiente para Claude agir.

O exemplo abaixo captura dois tipos de falhas dentro do handler e compõe a mensagem de erro que Claude lê. Um status HTTP diferente de 200 é capturado da resposta e retornado como um resultado de erro. Um erro de rede ou JSON inválido é capturado pelo try/except (Python) ou try/catch (TypeScript) circundante e também retornado como um resultado de erro. Em ambos os casos, Claude recebe uma mensagem que descreve a falha em vez de uma string de exceção bruta.

import json
import httpx
from typing import Any
from claude_agent_sdk import tool


@tool(
    "fetch_data",
    "Fetch data from an API",
    {"endpoint": str},  # Simple schema
)
async def fetch_data(args: dict[str, Any]) -> dict[str, Any]:
    try:
        async with httpx.AsyncClient() as client:
            response = await client.get(args["endpoint"])
            if response.status_code != 200:
                # Return the failure as a tool result so Claude can react to it.
                # is_error marks this as a failed call rather than odd-looking data.
                return {
                    "content": [
                        {
                            "type": "text",
                            "text": f"API error: {response.status_code} {response.reason_phrase}",
                        }
                    ],
                    "is_error": True,
                }

            data = response.json()
            return {"content": [{"type": "text", "text": json.dumps(data, indent=2)}]}
    except Exception as e:
        # Composes the message Claude reads. An uncaught exception would
        # reach Claude as the raw str(e) with no context.
        return {
            "content": [{"type": "text", "text": f"Failed to fetch data: {str(e)}"}],
            "is_error": True,
        }
import { tool } from "@anthropic-ai/claude-agent-sdk";
import { z } from "zod";

tool(
  "fetch_data",
  "Fetch data from an API",
  {
    endpoint: z.string().url().describe("API endpoint URL")
  },
  async (args) => {
    try {
      const response = await fetch(args.endpoint);

      if (!response.ok) {
        // Return the failure as a tool result so Claude can react to it.
        // isError marks this as a failed call rather than odd-looking data.
        return {
          content: [
            {
              type: "text",
              text: `API error: ${response.status} ${response.statusText}`
            }
          ],
          isError: true
        };
      }

      const data = await response.json();
      return {
        content: [
          {
            type: "text",
            text: JSON.stringify(data, null, 2)
          }
        ]
      };
    } catch (error) {
      // Composes the message Claude reads. An uncaught throw would
      // reach Claude as the raw error message with no context.
      return {
        content: [
          {
            type: "text",
            text: `Failed to fetch data: ${error instanceof Error ? error.message : String(error)}`
          }
        ],
        isError: true
      };
    }
  }
);

Retornar imagens e recursos

O array content em um resultado de ferramenta aceita blocos text, image, audio, resource e resource_link. Você pode misturá-los na mesma resposta. Em TypeScript, o SDK salva blocos de áudio em disco e Claude recebe um bloco de texto com o caminho do arquivo salvo; em Python, o SDK remove blocos de áudio do resultado da ferramenta e registra um aviso.

Claude recebe cada bloco de link de recurso como um bloco de texto contendo o nome, URI e descrição do link. Em TypeScript, sua aplicação também recebe os links como resourceLinks no tool_use_result da mensagem do usuário; em Python, o SDK os achata para texto antes que a CLI veja o resultado, portanto a chave resourceLinks do Python nunca é produzida para ferramentas em processo.

Imagens

Um bloco de imagem carrega os bytes da imagem inline, codificados em base64. Não há campo de URL. Para retornar uma imagem que existe em uma URL, busque-a no manipulador, leia os bytes da resposta e codifique-os em base64 antes de retornar. Uma imagem PNG, JPEG, GIF ou WebP chega a Claude como entrada visual; uma imagem de qualquer outro tipo é salva em disco e Claude recebe seu caminho de arquivo como texto.

Campo Tipo Notas
type "image"
data string Bytes codificados em base64. Apenas base64 bruto, sem prefixo data:image/...;base64,
mimeType string Obrigatório. Por exemplo image/png, image/jpeg, image/webp, image/gif
import base64
import httpx
from claude_agent_sdk import tool


# Define a tool that fetches an image from a URL and returns it to Claude
@tool("fetch_image", "Fetch an image from a URL and return it to Claude", {"url": str})
async def fetch_image(args):
    async with httpx.AsyncClient() as client:  # Fetch the image bytes
        response = await client.get(args["url"])

    return {
        "content": [
            {
                "type": "image",
                "data": base64.b64encode(response.content).decode(
                    "ascii"
                ),  # Base64-encode the raw bytes
                "mimeType": response.headers.get(
                    "content-type", "image/png"
                ),  # Read MIME type from the response
            }
        ]
    }
import { tool } from "@anthropic-ai/claude-agent-sdk";
import { z } from "zod";

tool(
  "fetch_image",
  "Fetch an image from a URL and return it to Claude",
  {
    url: z.string().url()
  },
  async (args) => {
    const response = await fetch(args.url); // Fetch the image bytes
    const buffer = Buffer.from(await response.arrayBuffer()); // Read into a Buffer for base64 encoding
    const mimeType = response.headers.get("content-type") ?? "image/png";

    return {
      content: [
        {
          type: "image",
          data: buffer.toString("base64"), // Base64-encode the raw bytes
          mimeType
        }
      ]
    };
  }
);

Recursos

Um bloco de recurso incorpora um pedaço de conteúdo identificado por uma URI. O conteúdo real fica no campo text ou blob do bloco. Use isso quando sua ferramenta produz um arquivo gerado ou um registro de um sistema externo.

Campo Tipo Notas
type "resource"
resource.uri string Identificador para o conteúdo. Qualquer esquema de URI
resource.text string O conteúdo, se for texto. Forneça este ou blob, não ambos
resource.blob string O conteúdo codificado em base64, se for binário. Apenas TypeScript: o SDK do Python remove recursos binários do resultado da ferramenta e registra um aviso
resource.mimeType string Opcional

Este exemplo mostra um bloco de recurso retornado de dentro de um manipulador de ferramenta. O SDK não lê do URI do exemplo, file:///tmp/report.md.

return {
  content: [
    {
      type: "resource",
      resource: {
        uri: "file:///tmp/report.md", // Label for Claude to reference, not a path the SDK reads
        mimeType: "text/markdown",
        text: "# Report\n..." // The actual content, inline
      }
    }
  ]
};
return {
    "content": [
        {
            "type": "resource",
            "resource": {
                "uri": "file:///tmp/report.md",  # Not a path the SDK reads
                "mimeType": "text/markdown",
                "text": "# Report\n...",  # The actual content, inline
            },
        }
    ]
}

Essas formas de bloco vêm do tipo MCP CallToolResult. Consulte a especificação MCP para a definição completa.

Retornar dados estruturados

structuredContent é um objeto JSON opcional no resultado, separado do array content. Use-o para retornar valores brutos que Claude possa ler como campos exatos em vez de analisá-los de uma string de texto ou imagem. Para manipuladores Python, consulte Conteúdo estruturado em Python.

Quando um manipulador tool() em TypeScript define structuredContent, Claude recebe o JSON mais os blocos de imagem e recurso em content, e os blocos de texto em content não chegam a Claude. O exemplo abaixo renderiza um gráfico como um bloco de imagem e retorna os pontos de dados por trás dele em structuredContent do mesmo manipulador. No trecho, chartPngBuffer é um Buffer contendo os bytes PNG renderizados.

return {
  content: [
    {
      type: "image",
      data: chartPngBuffer.toString("base64"),
      mimeType: "image/png"
    }
  ],
  structuredContent: {
    series: "temperature_2m",
    unit: "fahrenheit",
    points: [62.1, 63.4, 65.0, 64.2]
  }
};

[!NOTE]

O decorador Python @tool encaminha apenas content e is_error do dict de retorno do manipulador. Para retornar structuredContent do Python, execute um servidor MCP autônomo em vez de um servidor SDK em processo.

Exemplo: conversor de unidades

Esta ferramenta converte valores entre unidades de comprimento, temperatura e peso. Um usuário pode perguntar "converter 100 quilômetros para milhas" ou "quanto é 72°F em Celsius", e Claude escolhe o tipo de unidade correto e as unidades da solicitação.

Demonstra dois padrões:

  • Esquemas Enum: unit_type é restrito a um conjunto fixo de valores. Em TypeScript, use z.enum(). Em Python, o esquema dict não suporta enums, portanto o dict de JSON Schema completo é necessário.
  • Tratamento de entrada não suportada: quando um par de conversão não é encontrado, o manipulador retorna isError: true para que Claude possa informar ao usuário o que deu errado em vez de tratar uma falha como um resultado normal.
from typing import Any
from claude_agent_sdk import tool, create_sdk_mcp_server


# z.enum() em TypeScript se torna uma restrição "enum" em JSON Schema.
# O esquema dict não tem equivalente, portanto JSON Schema completo é necessário.
@tool(
    "convert_units",
    "Convert a value from one unit to another",
    {
        "type": "object",
        "properties": {
            "unit_type": {
                "type": "string",
                "enum": ["length", "temperature", "weight"],
                "description": "Category of unit",
            },
            "from_unit": {
                "type": "string",
                "description": "Unit to convert from, e.g. kilometers, fahrenheit, pounds",
            },
            "to_unit": {"type": "string", "description": "Unit to convert to"},
            "value": {"type": "number", "description": "Value to convert"},
        },
        "required": ["unit_type", "from_unit", "to_unit", "value"],
    },
)
async def convert_units(args: dict[str, Any]) -> dict[str, Any]:
    conversions = {
        "length": {
            "kilometers_to_miles": lambda v: v * 0.621371,
            "miles_to_kilometers": lambda v: v * 1.60934,
            "meters_to_feet": lambda v: v * 3.28084,
            "feet_to_meters": lambda v: v * 0.3048,
        },
        "temperature": {
            "celsius_to_fahrenheit": lambda v: (v * 9) / 5 + 32,
            "fahrenheit_to_celsius": lambda v: (v - 32) * 5 / 9,
            "celsius_to_kelvin": lambda v: v + 273.15,
            "kelvin_to_celsius": lambda v: v - 273.15,
        },
        "weight": {
            "kilograms_to_pounds": lambda v: v * 2.20462,
            "pounds_to_kilograms": lambda v: v * 0.453592,
            "grams_to_ounces": lambda v: v * 0.035274,
            "ounces_to_grams": lambda v: v * 28.3495,
        },
    }

    key = f"{args['from_unit']}_to_{args['to_unit']}"
    fn = conversions.get(args["unit_type"], {}).get(key)

    if not fn:
        return {
            "content": [
                {
                    "type": "text",
                    "text": f"Unsupported conversion: {args['from_unit']} to {args['to_unit']}",
                }
            ],
            "is_error": True,
        }

    result = fn(args["value"])
    return {
        "content": [
            {
                "type": "text",
                "text": f"{args['value']} {args['from_unit']} = {result:.4f} {args['to_unit']}",
            }
        ]
    }


converter_server = create_sdk_mcp_server(
    name="converter",
    version="1.0.0",
    tools=[convert_units],
)
import { tool, createSdkMcpServer } from "@anthropic-ai/claude-agent-sdk";
import { z } from "zod";

const convert = tool(
  "convert_units",
  "Convert a value from one unit to another",
  {
    unit_type: z.enum(["length", "temperature", "weight"]).describe("Category of unit"),
    from_unit: z
      .string()
      .describe("Unit to convert from, e.g. kilometers, fahrenheit, pounds"),
    to_unit: z.string().describe("Unit to convert to"),
    value: z.number().describe("Value to convert")
  },
  async (args) => {
    type Conversions = Record<string, Record<string, (v: number) => number>>;

    const conversions: Conversions = {
      length: {
        kilometers_to_miles: (v) => v * 0.621371,
        miles_to_kilometers: (v) => v * 1.60934,
        meters_to_feet: (v) => v * 3.28084,
        feet_to_meters: (v) => v * 0.3048
      },
      temperature: {
        celsius_to_fahrenheit: (v) => (v * 9) / 5 + 32,
        fahrenheit_to_celsius: (v) => ((v - 32) * 5) / 9,
        celsius_to_kelvin: (v) => v + 273.15,
        kelvin_to_celsius: (v) => v - 273.15
      },
      weight: {
        kilograms_to_pounds: (v) => v * 2.20462,
        pounds_to_kilograms: (v) => v * 0.453592,
        grams_to_ounces: (v) => v * 0.035274,
        ounces_to_grams: (v) => v * 28.3495
      }
    };

    const key = `${args.from_unit}_to_${args.to_unit}`;
    const fn = conversions[args.unit_type]?.[key];

    if (!fn) {
      return {
        content: [
          {
            type: "text",
            text: `Unsupported conversion: ${args.from_unit} to ${args.to_unit}`
          }
        ],
        isError: true
      };
    }

    const result = fn(args.value);
    return {
      content: [
        {
          type: "text",
          text: `${args.value} ${args.from_unit} = ${result.toFixed(4)} ${args.to_unit}`
        }
      ]
    };
  }
);

const converterServer = createSdkMcpServer({
  name: "converter",
  version: "1.0.0",
  tools: [convert]
});

Depois que o servidor é definido, passe-o para query da mesma forma que o exemplo de clima. Este exemplo envia três prompts diferentes em um loop para mostrar a mesma ferramenta tratando diferentes tipos de unidades. Para cada resposta, ele inspeciona objetos AssistantMessage (que contêm as chamadas de ferramenta que Claude fez durante esse turno) e imprime cada ToolUseBlock antes de imprimir o texto final ResultMessage. Isso permite que você veja quando Claude está usando a ferramenta versus respondendo a partir de seu próprio conhecimento.

Como tool search está ativado por padrão, a saída também pode incluir uma chamada ToolSearch conforme Claude carrega o esquema de ferramenta adiado.

import asyncio
from claude_agent_sdk import (
    query,
    ClaudeAgentOptions,
    ResultMessage,
    AssistantMessage,
    ToolUseBlock,
)


async def main():
    options = ClaudeAgentOptions(
        mcp_servers={"converter": converter_server},
        allowed_tools=["mcp__converter__convert_units"],
    )

    prompts = [
        "Convert 100 kilometers to miles.",
        "What is 72°F in Celsius?",
        "How many pounds is 5 kilograms?",
    ]

    for prompt in prompts:
        try:
            async for message in query(prompt=prompt, options=options):
                if isinstance(message, AssistantMessage):
                    for block in message.content:
                        if isinstance(block, ToolUseBlock):
                            print(f"[tool call] {block.name}({block.input})")
                elif isinstance(message, ResultMessage) and message.subtype == "success":
                    print(f"Q: {prompt}\nA: {message.result}\n")
        except Exception as error:
            # A single-shot query() raises after yielding an error result. Only success
            # results are printed above, so handle the failure here and continue with
            # the next prompt.
            print(f"Call failed: {error}")


asyncio.run(main())
import { query } from "@anthropic-ai/claude-agent-sdk";

const prompts = [
  "Convert 100 kilometers to miles.",
  "What is 72°F in Celsius?",
  "How many pounds is 5 kilograms?"
];

for (const prompt of prompts) {
  try {
    for await (const message of query({
      prompt,
      options: {
        mcpServers: { converter: converterServer },
        allowedTools: ["mcp__converter__convert_units"]
      }
    })) {
      if (message.type === "assistant") {
        for (const block of message.message.content) {
          if (block.type === "tool_use") {
            console.log(`[tool call] ${block.name}`, block.input);
          }
        }
      } else if (message.type === "result" && message.subtype === "success") {
        console.log(`Q: ${prompt}\nA: ${message.result}\n`);
      }
    }
  } catch (error) {
    // A single-shot query() throws after yielding an error result. Only success
    // results are logged above, so handle the failure here and continue with
    // the next prompt.
    console.error(`Call failed: ${error}`);
  }
}

Próximos passos

Você pode misturar os padrões nesta página no mesmo servidor: um único servidor pode conter uma ferramenta de banco de dados, uma ferramenta de gateway de API e um renderizador de imagem lado a lado.

A partir daqui:

  • Se seu servidor crescer para dezenas de ferramentas, consulte pesquisa de ferramentas para adiar o carregamento delas até que Claude precise delas.
  • Para conectar a servidores MCP externos (sistema de arquivos, GitHub, Slack) em vez de construir os seus próprios, consulte Conectar servidores MCP.
  • Para controlar quais ferramentas são executadas automaticamente versus exigindo aprovação, consulte Configurar permissões.