Instalación

pip install claude-agent-sdk

Elegir entre query() y ClaudeSDKClient

El SDK de Python proporciona dos formas de interactuar con Claude Code:

Comparación rápida

Característicaquery()ClaudeSDKClient
SesiónCrea una nueva sesión cada vezReutiliza la misma sesión
ConversaciónIntercambio únicoMúltiples intercambios en el mismo contexto
ConexiónGestionada automáticamenteControl manual
Entrada de streaming✅ Compatible✅ Compatible
Interrupciones❌ No compatible✅ Compatible
Hooks❌ No compatible✅ Compatible
Herramientas personalizadas❌ No compatible✅ Compatible
Continuar chat❌ Nueva sesión cada vez✅ Mantiene la conversación
Caso de usoTareas puntualesConversaciones continuas

Cuándo usar query() (Nueva sesión cada vez)

Mejor para:
  • Preguntas puntuales donde no necesitas historial de conversación
  • Tareas independientes que no requieren contexto de intercambios anteriores
  • Scripts de automatización simple
  • Cuando quieres un comienzo fresco cada vez

Cuándo usar ClaudeSDKClient (Conversación continua)

Mejor para:
  • Continuar conversaciones - Cuando necesitas que Claude recuerde el contexto
  • Preguntas de seguimiento - Construir sobre respuestas anteriores
  • Aplicaciones interactivas - Interfaces de chat, REPLs
  • Lógica impulsada por respuestas - Cuando la siguiente acción depende de la respuesta de Claude
  • Control de sesión - Gestionar explícitamente el ciclo de vida de la conversación

Funciones

query()

Crea una nueva sesión para cada interacción con Claude Code. Devuelve un iterador asincrónico que produce mensajes a medida que llegan. Cada llamada a query() comienza de nuevo sin memoria de interacciones anteriores.
async def query(
    *,
    prompt: str | AsyncIterable[dict[str, Any]],
    options: ClaudeAgentOptions | None = None
) -> AsyncIterator[Message]

Parámetros

ParámetroTipoDescripción
promptstr | AsyncIterable[dict]El prompt de entrada como una cadena o iterable asincrónico para modo de streaming
optionsClaudeAgentOptions | NoneObjeto de configuración opcional (por defecto ClaudeAgentOptions() si es None)

Devuelve

Devuelve un AsyncIterator[Message] que produce mensajes de la conversación.

Ejemplo - Con opciones


import asyncio
from claude_agent_sdk import query, ClaudeAgentOptions

async def main():
    options = ClaudeAgentOptions(
        system_prompt="You are an expert Python developer",
        permission_mode='acceptEdits',
        cwd="/home/user/project"
    )

    async for message in query(
        prompt="Create a Python web server",
        options=options
    ):
        print(message)


asyncio.run(main())

tool()

Decorador para definir herramientas MCP con seguridad de tipos.
def tool(
    name: str,
    description: str,
    input_schema: type | dict[str, Any]
) -> Callable[[Callable[[Any], Awaitable[dict[str, Any]]]], SdkMcpTool[Any]]

Parámetros

ParámetroTipoDescripción
namestrIdentificador único para la herramienta
descriptionstrDescripción legible de lo que hace la herramienta
input_schematype | dict[str, Any]Esquema que define los parámetros de entrada de la herramienta (ver abajo)

Opciones de esquema de entrada

  1. Mapeo de tipo simple (recomendado):
    {"text": str, "count": int, "enabled": bool}
    
  2. Formato JSON Schema (para validación compleja):
    {
        "type": "object",
        "properties": {
            "text": {"type": "string"},
            "count": {"type": "integer", "minimum": 0}
        },
        "required": ["text"]
    }
    

Devuelve

Una función decoradora que envuelve la implementación de la herramienta y devuelve una instancia de SdkMcpTool.

Ejemplo

from claude_agent_sdk import tool
from typing import Any

@tool("greet", "Greet a user", {"name": str})
async def greet(args: dict[str, Any]) -> dict[str, Any]:
    return {
        "content": [{
            "type": "text",
            "text": f"Hello, {args['name']}!"
        }]
    }

create_sdk_mcp_server()

Crea un servidor MCP en proceso que se ejecuta dentro de tu aplicación Python.
def create_sdk_mcp_server(
    name: str,
    version: str = "1.0.0",
    tools: list[SdkMcpTool[Any]] | None = None
) -> McpSdkServerConfig

Parámetros

ParámetroTipoPor defectoDescripción
namestr-Identificador único para el servidor
versionstr"1.0.0"Cadena de versión del servidor
toolslist[SdkMcpTool[Any]] | NoneNoneLista de funciones de herramientas creadas con el decorador @tool

Devuelve

Devuelve un objeto McpSdkServerConfig que puede pasarse a ClaudeAgentOptions.mcp_servers.

Ejemplo

from claude_agent_sdk import tool, create_sdk_mcp_server

@tool("add", "Add two numbers", {"a": float, "b": float})
async def add(args):
    return {
        "content": [{
            "type": "text",
            "text": f"Sum: {args['a'] + args['b']}"
        }]
    }

@tool("multiply", "Multiply two numbers", {"a": float, "b": float})
async def multiply(args):
    return {
        "content": [{
            "type": "text",
            "text": f"Product: {args['a'] * args['b']}"
        }]
    }

calculator = create_sdk_mcp_server(
    name="calculator",
    version="2.0.0",
    tools=[add, multiply]  # Pass decorated functions
)

# Use with Claude
options = ClaudeAgentOptions(
    mcp_servers={"calc": calculator},
    allowed_tools=["mcp__calc__add", "mcp__calc__multiply"]
)

Clases

ClaudeSDKClient

Mantiene una sesión de conversación en múltiples intercambios. Este es el equivalente en Python de cómo funciona internamente la función query() del SDK de TypeScript - crea un objeto cliente que puede continuar conversaciones.

Características clave

  • Continuidad de sesión: Mantiene el contexto de conversación en múltiples llamadas a query()
  • Misma conversación: Claude recuerda mensajes anteriores en la sesión
  • Soporte de interrupciones: Puede detener a Claude a mitad de la ejecución
  • Ciclo de vida explícito: Controlas cuándo comienza y termina la sesión
  • Flujo impulsado por respuestas: Puede reaccionar a respuestas y enviar seguimientos
  • Herramientas personalizadas y hooks: Admite herramientas personalizadas (creadas con el decorador @tool) y hooks
class ClaudeSDKClient:
    def __init__(self, options: ClaudeAgentOptions | None = None)
    async def connect(self, prompt: str | AsyncIterable[dict] | None = None) -> None
    async def query(self, prompt: str | AsyncIterable[dict], session_id: str = "default") -> None
    async def receive_messages(self) -> AsyncIterator[Message]
    async def receive_response(self) -> AsyncIterator[Message]
    async def interrupt(self) -> None
    async def disconnect(self) -> None

Métodos

MétodoDescripción
__init__(options)Inicializa el cliente con configuración opcional
connect(prompt)Conecta a Claude con un prompt inicial opcional o flujo de mensajes
query(prompt, session_id)Envía una nueva solicitud en modo de streaming
receive_messages()Recibe todos los mensajes de Claude como un iterador asincrónico
receive_response()Recibe mensajes hasta e incluyendo un ResultMessage
interrupt()Envía señal de interrupción (solo funciona en modo de streaming)
disconnect()Desconecta de Claude

Soporte de gestor de contexto

El cliente puede usarse como un gestor de contexto asincrónico para gestión automática de conexiones:
async with ClaudeSDKClient() as client:
    await client.query("Hello Claude")
    async for message in client.receive_response():
        print(message)
Importante: Cuando iteres sobre mensajes, evita usar break para salir temprano ya que esto puede causar problemas de limpieza de asyncio. En su lugar, deja que la iteración se complete naturalmente o usa banderas para rastrear cuándo has encontrado lo que necesitas.

Ejemplo - Continuar una conversación

import asyncio
from claude_agent_sdk import ClaudeSDKClient, AssistantMessage, TextBlock, ResultMessage

async def main():
    async with ClaudeSDKClient() as client:
        # First question
        await client.query("What's the capital of France?")

        # Process response
        async for message in client.receive_response():
            if isinstance(message, AssistantMessage):
                for block in message.content:
                    if isinstance(block, TextBlock):
                        print(f"Claude: {block.text}")

        # Follow-up question - Claude remembers the previous context
        await client.query("What's the population of that city?")

        async for message in client.receive_response():
            if isinstance(message, AssistantMessage):
                for block in message.content:
                    if isinstance(block, TextBlock):
                        print(f"Claude: {block.text}")

        # Another follow-up - still in the same conversation
        await client.query("What are some famous landmarks there?")

        async for message in client.receive_response():
            if isinstance(message, AssistantMessage):
                for block in message.content:
                    if isinstance(block, TextBlock):
                        print(f"Claude: {block.text}")

asyncio.run(main())

Ejemplo - Entrada de streaming con ClaudeSDKClient

import asyncio
from claude_agent_sdk import ClaudeSDKClient

async def message_stream():
    """Generate messages dynamically."""
    yield {"type": "text", "text": "Analyze the following data:"}
    await asyncio.sleep(0.5)
    yield {"type": "text", "text": "Temperature: 25°C"}
    await asyncio.sleep(0.5)
    yield {"type": "text", "text": "Humidity: 60%"}
    await asyncio.sleep(0.5)
    yield {"type": "text", "text": "What patterns do you see?"}

async def main():
    async with ClaudeSDKClient() as client:
        # Stream input to Claude
        await client.query(message_stream())

        # Process response
        async for message in client.receive_response():
            print(message)

        # Follow-up in same session
        await client.query("Should we be concerned about these readings?")

        async for message in client.receive_response():
            print(message)

asyncio.run(main())

Ejemplo - Usar interrupciones

import asyncio
from claude_agent_sdk import ClaudeSDKClient, ClaudeAgentOptions

async def interruptible_task():
    options = ClaudeAgentOptions(
        allowed_tools=["Bash"],
        permission_mode="acceptEdits"
    )

    async with ClaudeSDKClient(options=options) as client:
        # Start a long-running task
        await client.query("Count from 1 to 100 slowly")

        # Let it run for a bit
        await asyncio.sleep(2)

        # Interrupt the task
        await client.interrupt()
        print("Task interrupted!")

        # Send a new command
        await client.query("Just say hello instead")

        async for message in client.receive_response():
            # Process the new response
            pass

asyncio.run(interruptible_task())

Ejemplo - Control de permisos avanzado

from claude_agent_sdk import (
    ClaudeSDKClient,
    ClaudeAgentOptions
)

async def custom_permission_handler(
    tool_name: str,
    input_data: dict,
    context: dict
):
    """Custom logic for tool permissions."""

    # Block writes to system directories
    if tool_name == "Write" and input_data.get("file_path", "").startswith("/system/"):
        return {
            "behavior": "deny",
            "message": "System directory write not allowed",
            "interrupt": True
        }

    # Redirect sensitive file operations
    if tool_name in ["Write", "Edit"] and "config" in input_data.get("file_path", ""):
        safe_path = f"./sandbox/{input_data['file_path']}"
        return {
            "behavior": "allow",
            "updatedInput": {**input_data, "file_path": safe_path}
        }

    # Allow everything else
    return {
        "behavior": "allow",
        "updatedInput": input_data
    }

async def main():
    options = ClaudeAgentOptions(
        can_use_tool=custom_permission_handler,
        allowed_tools=["Read", "Write", "Edit"]
    )

    async with ClaudeSDKClient(options=options) as client:
        await client.query("Update the system config file")

        async for message in client.receive_response():
            # Will use sandbox path instead
            print(message)

asyncio.run(main())

Tipos

SdkMcpTool

Definición para una herramienta MCP del SDK creada con el decorador @tool.
@dataclass
class SdkMcpTool(Generic[T]):
    name: str
    description: str
    input_schema: type[T] | dict[str, Any]
    handler: Callable[[T], Awaitable[dict[str, Any]]]
PropiedadTipoDescripción
namestrIdentificador único para la herramienta
descriptionstrDescripción legible
input_schematype[T] | dict[str, Any]Esquema para validación de entrada
handlerCallable[[T], Awaitable[dict[str, Any]]]Función asincrónica que maneja la ejecución de la herramienta

ClaudeAgentOptions

Clase de datos de configuración para consultas de Claude Code.
@dataclass
class ClaudeAgentOptions:
    allowed_tools: list[str] = field(default_factory=list)
    system_prompt: str | SystemPromptPreset | None = None
    mcp_servers: dict[str, McpServerConfig] | str | Path = field(default_factory=dict)
    permission_mode: PermissionMode | None = None
    continue_conversation: bool = False
    resume: str | None = None
    max_turns: int | None = None
    disallowed_tools: list[str] = field(default_factory=list)
    model: str | None = None
    permission_prompt_tool_name: str | None = None
    cwd: str | Path | None = None
    settings: str | None = None
    add_dirs: list[str | Path] = field(default_factory=list)
    env: dict[str, str] = field(default_factory=dict)
    extra_args: dict[str, str | None] = field(default_factory=dict)
    max_buffer_size: int | None = None
    debug_stderr: Any = sys.stderr  # Deprecated
    stderr: Callable[[str], None] | None = None
    can_use_tool: CanUseTool | None = None
    hooks: dict[HookEvent, list[HookMatcher]] | None = None
    user: str | None = None
    include_partial_messages: bool = False
    fork_session: bool = False
    agents: dict[str, AgentDefinition] | None = None
    setting_sources: list[SettingSource] | None = None
PropiedadTipoPor defectoDescripción
allowed_toolslist[str][]Lista de nombres de herramientas permitidas
system_promptstr | SystemPromptPreset | NoneNoneConfiguración del prompt del sistema. Pasa una cadena para un prompt personalizado, o usa {"type": "preset", "preset": "claude_code"} para el prompt del sistema de Claude Code. Añade "append" para extender el preset
mcp_serversdict[str, McpServerConfig] | str | Path{}Configuraciones del servidor MCP o ruta al archivo de configuración
permission_modePermissionMode | NoneNoneModo de permiso para el uso de herramientas
continue_conversationboolFalseContinuar la conversación más reciente
resumestr | NoneNoneID de sesión para reanudar
max_turnsint | NoneNoneMáximo de turnos de conversación
disallowed_toolslist[str][]Lista de nombres de herramientas no permitidas
modelstr | NoneNoneModelo de Claude a usar
permission_prompt_tool_namestr | NoneNoneNombre de herramienta MCP para prompts de permiso
cwdstr | Path | NoneNoneDirectorio de trabajo actual
settingsstr | NoneNoneRuta al archivo de configuración
add_dirslist[str | Path][]Directorios adicionales a los que Claude puede acceder
envdict[str, str]{}Variables de entorno
extra_argsdict[str, str | None]{}Argumentos CLI adicionales para pasar directamente a la CLI
max_buffer_sizeint | NoneNoneMáximo de bytes al almacenar en búfer stdout de CLI
debug_stderrAnysys.stderrDeprecated - Objeto similar a archivo para salida de depuración. Usa el callback stderr en su lugar
stderrCallable[[str], None] | NoneNoneFunción de callback para salida stderr de CLI
can_use_toolCanUseTool | NoneNoneFunción de callback de permiso de herramienta
hooksdict[HookEvent, list[HookMatcher]] | NoneNoneConfiguraciones de hook para interceptar eventos
userstr | NoneNoneIdentificador de usuario
include_partial_messagesboolFalseIncluir eventos de streaming de mensajes parciales
fork_sessionboolFalseAl reanudar con resume, bifurcar a un nuevo ID de sesión en lugar de continuar la sesión original
agentsdict[str, AgentDefinition] | NoneNoneSubagentes definidos programáticamente
pluginslist[SdkPluginConfig][]Cargar plugins personalizados desde rutas locales. Ver Plugins para detalles
setting_sourceslist[SettingSource] | NoneNone (sin configuración)Controlar qué configuraciones del sistema de archivos cargar. Cuando se omite, no se carga ninguna configuración. Nota: Debe incluir "project" para cargar archivos CLAUDE.md

SystemPromptPreset

Configuración para usar el prompt del sistema preset de Claude Code con adiciones opcionales.
class SystemPromptPreset(TypedDict):
    type: Literal["preset"]
    preset: Literal["claude_code"]
    append: NotRequired[str]
CampoRequeridoDescripción
typeDebe ser "preset" para usar un prompt del sistema preset
presetDebe ser "claude_code" para usar el prompt del sistema de Claude Code
appendNoInstrucciones adicionales para añadir al prompt del sistema preset

SettingSource

Controla qué fuentes de configuración basadas en el sistema de archivos carga el SDK.
SettingSource = Literal["user", "project", "local"]
ValorDescripciónUbicación
"user"Configuración global del usuario~/.claude/settings.json
"project"Configuración compartida del proyecto (controlada por versión).claude/settings.json
"local"Configuración local del proyecto (ignorada por git).claude/settings.local.json

Comportamiento por defecto

Cuando setting_sources está omitido o es None, el SDK no carga ninguna configuración del sistema de archivos. Esto proporciona aislamiento para aplicaciones SDK.

¿Por qué usar setting_sources?

Cargar todas las configuraciones del sistema de archivos (comportamiento heredado):
# Load all settings like SDK v0.0.x did
from claude_agent_sdk import query, ClaudeAgentOptions

async for message in query(
    prompt="Analyze this code",
    options=ClaudeAgentOptions(
        setting_sources=["user", "project", "local"]  # Load all settings
    )
):
    print(message)
Cargar solo fuentes de configuración específicas:
# Load only project settings, ignore user and local
async for message in query(
    prompt="Run CI checks",
    options=ClaudeAgentOptions(
        setting_sources=["project"]  # Only .claude/settings.json
    )
):
    print(message)
Entornos de prueba y CI:
# Ensure consistent behavior in CI by excluding local settings
async for message in query(
    prompt="Run tests",
    options=ClaudeAgentOptions(
        setting_sources=["project"],  # Only team-shared settings
        permission_mode="bypassPermissions"
    )
):
    print(message)
Aplicaciones solo SDK:
# Define everything programmatically (default behavior)
# No filesystem dependencies - setting_sources defaults to None
async for message in query(
    prompt="Review this PR",
    options=ClaudeAgentOptions(
        # setting_sources=None is the default, no need to specify
        agents={ /* ... */ },
        mcp_servers={ /* ... */ },
        allowed_tools=["Read", "Grep", "Glob"]
    )
):
    print(message)
Cargar instrucciones del proyecto CLAUDE.md:
# Load project settings to include CLAUDE.md files
async for message in query(
    prompt="Add a new feature following project conventions",
    options=ClaudeAgentOptions(
        system_prompt={
            "type": "preset",
            "preset": "claude_code"  # Use Claude Code's system prompt
        },
        setting_sources=["project"],  # Required to load CLAUDE.md from project
        allowed_tools=["Read", "Write", "Edit"]
    )
):
    print(message)

Precedencia de configuración

Cuando se cargan múltiples fuentes, las configuraciones se fusionan con esta precedencia (mayor a menor):
  1. Configuración local (.claude/settings.local.json)
  2. Configuración del proyecto (.claude/settings.json)
  3. Configuración del usuario (~/.claude/settings.json)
Las opciones programáticas (como agents, allowed_tools) siempre anulan las configuraciones del sistema de archivos.

AgentDefinition

Configuración para un subagente definido programáticamente.
@dataclass
class AgentDefinition:
    description: str
    prompt: str
    tools: list[str] | None = None
    model: Literal["sonnet", "opus", "haiku", "inherit"] | None = None
CampoRequeridoDescripción
descriptionDescripción en lenguaje natural de cuándo usar este agente
toolsNoArray de nombres de herramientas permitidas. Si se omite, hereda todas las herramientas
promptEl prompt del sistema del agente
modelNoAnulación de modelo para este agente. Si se omite, usa el modelo principal

PermissionMode

Modos de permiso para controlar la ejecución de herramientas.
PermissionMode = Literal[
    "default",           # Standard permission behavior
    "acceptEdits",       # Auto-accept file edits
    "plan",              # Planning mode - no execution
    "bypassPermissions"  # Bypass all permission checks (use with caution)
]

McpSdkServerConfig

Configuración para servidores MCP del SDK creados con create_sdk_mcp_server().
class McpSdkServerConfig(TypedDict):
    type: Literal["sdk"]
    name: str
    instance: Any  # MCP Server instance

McpServerConfig

Tipo de unión para configuraciones de servidor MCP.
McpServerConfig = McpStdioServerConfig | McpSSEServerConfig | McpHttpServerConfig | McpSdkServerConfig

McpStdioServerConfig

class McpStdioServerConfig(TypedDict):
    type: NotRequired[Literal["stdio"]]  # Optional for backwards compatibility
    command: str
    args: NotRequired[list[str]]
    env: NotRequired[dict[str, str]]

McpSSEServerConfig

class McpSSEServerConfig(TypedDict):
    type: Literal["sse"]
    url: str
    headers: NotRequired[dict[str, str]]

McpHttpServerConfig

class McpHttpServerConfig(TypedDict):
    type: Literal["http"]
    url: str
    headers: NotRequired[dict[str, str]]

SdkPluginConfig

Configuración para cargar plugins en el SDK.
class SdkPluginConfig(TypedDict):
    type: Literal["local"]
    path: str
CampoTipoDescripción
typeLiteral["local"]Debe ser "local" (actualmente solo se admiten plugins locales)
pathstrRuta absoluta o relativa al directorio del plugin
Ejemplo:
plugins=[
    {"type": "local", "path": "./my-plugin"},
    {"type": "local", "path": "/absolute/path/to/plugin"}
]
Para información completa sobre cómo crear y usar plugins, ver Plugins.

Tipos de mensaje

Message

Tipo de unión de todos los mensajes posibles.
Message = UserMessage | AssistantMessage | SystemMessage | ResultMessage

UserMessage

Mensaje de entrada del usuario.
@dataclass
class UserMessage:
    content: str | list[ContentBlock]

AssistantMessage

Mensaje de respuesta del asistente con bloques de contenido.
@dataclass
class AssistantMessage:
    content: list[ContentBlock]
    model: str

SystemMessage

Mensaje del sistema con metadatos.
@dataclass
class SystemMessage:
    subtype: str
    data: dict[str, Any]

ResultMessage

Mensaje de resultado final con información de costo y uso.
@dataclass
class ResultMessage:
    subtype: str
    duration_ms: int
    duration_api_ms: int
    is_error: bool
    num_turns: int
    session_id: str
    total_cost_usd: float | None = None
    usage: dict[str, Any] | None = None
    result: str | None = None

Tipos de bloque de contenido

ContentBlock

Tipo de unión de todos los bloques de contenido.
ContentBlock = TextBlock | ThinkingBlock | ToolUseBlock | ToolResultBlock

TextBlock

Bloque de contenido de texto.
@dataclass
class TextBlock:
    text: str

ThinkingBlock

Bloque de contenido de pensamiento (para modelos con capacidad de pensamiento).
@dataclass
class ThinkingBlock:
    thinking: str
    signature: str

ToolUseBlock

Bloque de solicitud de uso de herramienta.
@dataclass
class ToolUseBlock:
    id: str
    name: str
    input: dict[str, Any]

ToolResultBlock

Bloque de resultado de ejecución de herramienta.
@dataclass
class ToolResultBlock:
    tool_use_id: str
    content: str | list[dict[str, Any]] | None = None
    is_error: bool | None = None

Tipos de error

ClaudeSDKError

Clase de excepción base para todos los errores del SDK.
class ClaudeSDKError(Exception):
    """Base error for Claude SDK."""

CLINotFoundError

Se genera cuando Claude Code CLI no está instalado o no se encuentra.
class CLINotFoundError(CLIConnectionError):
    def __init__(self, message: str = "Claude Code not found", cli_path: str | None = None):
        """
        Args:
            message: Error message (default: "Claude Code not found")
            cli_path: Optional path to the CLI that was not found
        """

CLIConnectionError

Se genera cuando la conexión a Claude Code falla.
class CLIConnectionError(ClaudeSDKError):
    """Failed to connect to Claude Code."""

ProcessError

Se genera cuando el proceso de Claude Code falla.
class ProcessError(ClaudeSDKError):
    def __init__(self, message: str, exit_code: int | None = None, stderr: str | None = None):
        self.exit_code = exit_code
        self.stderr = stderr

CLIJSONDecodeError

Se genera cuando el análisis JSON falla.
class CLIJSONDecodeError(ClaudeSDKError):
    def __init__(self, line: str, original_error: Exception):
        """
        Args:
            line: The line that failed to parse
            original_error: The original JSON decode exception
        """
        self.line = line
        self.original_error = original_error

Tipos de hook

HookEvent

Tipos de eventos de hook admitidos. Tenga en cuenta que debido a limitaciones de configuración, el SDK de Python no admite hooks de SessionStart, SessionEnd y Notification.
HookEvent = Literal[
    "PreToolUse",      # Called before tool execution
    "PostToolUse",     # Called after tool execution
    "UserPromptSubmit", # Called when user submits a prompt
    "Stop",            # Called when stopping execution
    "SubagentStop",    # Called when a subagent stops
    "PreCompact"       # Called before message compaction
]

HookCallback

Definición de tipo para funciones de callback de hook.
HookCallback = Callable[
    [dict[str, Any], str | None, HookContext],
    Awaitable[dict[str, Any]]
]
Parámetros:
  • input_data: Datos de entrada específicos del hook (ver documentación de hooks)
  • tool_use_id: Identificador de uso de herramienta opcional (para hooks relacionados con herramientas)
  • context: Contexto de hook con información adicional
Devuelve un diccionario que puede contener:
  • decision: "block" para bloquear la acción
  • systemMessage: Mensaje del sistema para añadir a la transcripción
  • hookSpecificOutput: Datos de salida específicos del hook

HookContext

Información de contexto pasada a callbacks de hook.
@dataclass
class HookContext:
    signal: Any | None = None  # Future: abort signal support

HookMatcher

Configuración para hacer coincidir hooks con eventos o herramientas específicas.
@dataclass
class HookMatcher:
    matcher: str | None = None        # Tool name or pattern to match (e.g., "Bash", "Write|Edit")
    hooks: list[HookCallback] = field(default_factory=list)  # List of callbacks to execute

Ejemplo de uso de hook

from claude_agent_sdk import query, ClaudeAgentOptions, HookMatcher, HookContext
from typing import Any

async def validate_bash_command(
    input_data: dict[str, Any],
    tool_use_id: str | None,
    context: HookContext
) -> dict[str, Any]:
    """Validate and potentially block dangerous bash commands."""
    if input_data['tool_name'] == 'Bash':
        command = input_data['tool_input'].get('command', '')
        if 'rm -rf /' in command:
            return {
                'hookSpecificOutput': {
                    'hookEventName': 'PreToolUse',
                    'permissionDecision': 'deny',
                    'permissionDecisionReason': 'Dangerous command blocked'
                }
            }
    return {}

async def log_tool_use(
    input_data: dict[str, Any],
    tool_use_id: str | None,
    context: HookContext
) -> dict[str, Any]:
    """Log all tool usage for auditing."""
    print(f"Tool used: {input_data.get('tool_name')}")
    return {}

options = ClaudeAgentOptions(
    hooks={
        'PreToolUse': [
            HookMatcher(matcher='Bash', hooks=[validate_bash_command]),
            HookMatcher(hooks=[log_tool_use])  # Applies to all tools
        ],
        'PostToolUse': [
            HookMatcher(hooks=[log_tool_use])
        ]
    }
)

async for message in query(
    prompt="Analyze this codebase",
    options=options
):
    print(message)

Tipos de entrada/salida de herramienta

Documentación de esquemas de entrada/salida para todas las herramientas integradas de Claude Code. Aunque el SDK de Python no exporta estos como tipos, representan la estructura de entradas y salidas de herramientas en mensajes.

Task

Nombre de herramienta: Task Entrada:
{
    "description": str,      # A short (3-5 word) description of the task
    "prompt": str,           # The task for the agent to perform
    "subagent_type": str     # The type of specialized agent to use
}
Salida:
{
    "result": str,                    # Final result from the subagent
    "usage": dict | None,             # Token usage statistics
    "total_cost_usd": float | None,  # Total cost in USD
    "duration_ms": int | None         # Execution duration in milliseconds
}

Bash

Nombre de herramienta: Bash Entrada:
{
    "command": str,                  # The command to execute
    "timeout": int | None,           # Optional timeout in milliseconds (max 600000)
    "description": str | None,       # Clear, concise description (5-10 words)
    "run_in_background": bool | None # Set to true to run in background
}
Salida:
{
    "output": str,              # Combined stdout and stderr output
    "exitCode": int,            # Exit code of the command
    "killed": bool | None,      # Whether command was killed due to timeout
    "shellId": str | None       # Shell ID for background processes
}

Edit

Nombre de herramienta: Edit Entrada:
{
    "file_path": str,           # The absolute path to the file to modify
    "old_string": str,          # The text to replace
    "new_string": str,          # The text to replace it with
    "replace_all": bool | None  # Replace all occurrences (default False)
}
Salida:
{
    "message": str,      # Confirmation message
    "replacements": int, # Number of replacements made
    "file_path": str     # File path that was edited
}

Read

Nombre de herramienta: Read Entrada:
{
    "file_path": str,       # The absolute path to the file to read
    "offset": int | None,   # The line number to start reading from
    "limit": int | None     # The number of lines to read
}
Salida (archivos de texto):
{
    "content": str,         # File contents with line numbers
    "total_lines": int,     # Total number of lines in file
    "lines_returned": int   # Lines actually returned
}
Salida (imágenes):
{
    "image": str,       # Base64 encoded image data
    "mime_type": str,   # Image MIME type
    "file_size": int    # File size in bytes
}

Write

Nombre de herramienta: Write Entrada:
{
    "file_path": str,  # The absolute path to the file to write
    "content": str     # The content to write to the file
}
Salida:
{
    "message": str,        # Success message
    "bytes_written": int,  # Number of bytes written
    "file_path": str       # File path that was written
}

Glob

Nombre de herramienta: Glob Entrada:
{
    "pattern": str,       # The glob pattern to match files against
    "path": str | None    # The directory to search in (defaults to cwd)
}
Salida:
{
    "matches": list[str],  # Array of matching file paths
    "count": int,          # Number of matches found
    "search_path": str     # Search directory used
}

Grep

Nombre de herramienta: Grep Entrada:
{
    "pattern": str,                    # The regular expression pattern
    "path": str | None,                # File or directory to search in
    "glob": str | None,                # Glob pattern to filter files
    "type": str | None,                # File type to search
    "output_mode": str | None,         # "content", "files_with_matches", or "count"
    "-i": bool | None,                 # Case insensitive search
    "-n": bool | None,                 # Show line numbers
    "-B": int | None,                  # Lines to show before each match
    "-A": int | None,                  # Lines to show after each match
    "-C": int | None,                  # Lines to show before and after
    "head_limit": int | None,          # Limit output to first N lines/entries
    "multiline": bool | None           # Enable multiline mode
}
Salida (modo content):
{
    "matches": [
        {
            "file": str,
            "line_number": int | None,
            "line": str,
            "before_context": list[str] | None,
            "after_context": list[str] | None
        }
    ],
    "total_matches": int
}
Salida (modo files_with_matches):
{
    "files": list[str],  # Files containing matches
    "count": int         # Number of files with matches
}

NotebookEdit

Nombre de herramienta: NotebookEdit Entrada:
{
    "notebook_path": str,                     # Absolute path to the Jupyter notebook
    "cell_id": str | None,                    # The ID of the cell to edit
    "new_source": str,                        # The new source for the cell
    "cell_type": "code" | "markdown" | None,  # The type of the cell
    "edit_mode": "replace" | "insert" | "delete" | None  # Edit operation type
}
Salida:
{
    "message": str, # Success message
    "edit_type": "replaced" | "inserted" | "deleted",  # Type of edit performed
    "cell_id": str | None,                       # Cell ID that was affected
    "total_cells": int                           # Total cells in notebook after edit
}

WebFetch

Nombre de herramienta: WebFetch Entrada:
{
    "url": str,     # The URL to fetch content from
    "prompt": str   # The prompt to run on the fetched content
}
Salida:
{
    "response": str,           # AI model's response to the prompt
    "url": str,                # URL that was fetched
    "final_url": str | None,   # Final URL after redirects
    "status_code": int | None  # HTTP status code
}

WebSearch

Nombre de herramienta: WebSearch Entrada:
{
    "query": str,                        # The search query to use
    "allowed_domains": list[str] | None, # Only include results from these domains
    "blocked_domains": list[str] | None  # Never include results from these domains
}
Salida:
{
    "results": [
        {
            "title": str,
            "url": str,
            "snippet": str,
            "metadata": dict | None
        }
    ],
    "total_results": int,
    "query": str
}

TodoWrite

Nombre de herramienta: TodoWrite Entrada:
{
    "todos": [
        {
            "content": str, # The task description
            "status": "pending" | "in_progress" | "completed",  # Task status
            "activeForm": str                            # Active form of the description
        }
    ]
}
Salida:
{
    "message": str,  # Success message
    "stats": {
        "total": int,
        "pending": int,
        "in_progress": int,
        "completed": int
    }
}

BashOutput

Nombre de herramienta: BashOutput Entrada:
{
    "bash_id": str,       # The ID of the background shell
    "filter": str | None  # Optional regex to filter output lines
}
Salida:
{
    "output": str, # New output since last check
    "status": "running" | "completed" | "failed",       # Current shell status
    "exitCode": int | None # Exit code when completed
}

KillBash

Nombre de herramienta: KillBash Entrada:
{
    "shell_id": str  # The ID of the background shell to kill
}
Salida:
{
    "message": str,  # Success message
    "shell_id": str  # ID of the killed shell
}

ExitPlanMode

Nombre de herramienta: ExitPlanMode Entrada:
{
    "plan": str  # The plan to run by the user for approval
}
Salida:
{
    "message": str,          # Confirmation message
    "approved": bool | None  # Whether user approved the plan
}

ListMcpResources

Nombre de herramienta: ListMcpResources Entrada:
{
    "server": str | None  # Optional server name to filter resources by
}
Salida:
{
    "resources": [
        {
            "uri": str,
            "name": str,
            "description": str | None,
            "mimeType": str | None,
            "server": str
        }
    ],
    "total": int
}

ReadMcpResource

Nombre de herramienta: ReadMcpResource Entrada:
{
    "server": str,  # The MCP server name
    "uri": str      # The resource URI to read
}
Salida:
{
    "contents": [
        {
            "uri": str,
            "mimeType": str | None,
            "text": str | None,
            "blob": str | None
        }
    ],
    "server": str
}

Características avanzadas con ClaudeSDKClient

Construir una interfaz de conversación continua

from claude_agent_sdk import ClaudeSDKClient, ClaudeAgentOptions, AssistantMessage, TextBlock
import asyncio

class ConversationSession:
    """Maintains a single conversation session with Claude."""

    def __init__(self, options: ClaudeAgentOptions = None):
        self.client = ClaudeSDKClient(options)
        self.turn_count = 0

    async def start(self):
        await self.client.connect()
        print("Starting conversation session. Claude will remember context.")
        print("Commands: 'exit' to quit, 'interrupt' to stop current task, 'new' for new session")

        while True:
            user_input = input(f"\n[Turn {self.turn_count + 1}] You: ")

            if user_input.lower() == 'exit':
                break
            elif user_input.lower() == 'interrupt':
                await self.client.interrupt()
                print("Task interrupted!")
                continue
            elif user_input.lower() == 'new':
                # Disconnect and reconnect for a fresh session
                await self.client.disconnect()
                await self.client.connect()
                self.turn_count = 0
                print("Started new conversation session (previous context cleared)")
                continue

            # Send message - Claude remembers all previous messages in this session
            await self.client.query(user_input)
            self.turn_count += 1

            # Process response
            print(f"[Turn {self.turn_count}] Claude: ", end="")
            async for message in self.client.receive_response():
                if isinstance(message, AssistantMessage):
                    for block in message.content:
                        if isinstance(block, TextBlock):
                            print(block.text, end="")
            print()  # New line after response

        await self.client.disconnect()
        print(f"Conversation ended after {self.turn_count} turns.")

async def main():
    options = ClaudeAgentOptions(
        allowed_tools=["Read", "Write", "Bash"],
        permission_mode="acceptEdits"
    )
    session = ConversationSession(options)
    await session.start()

# Example conversation:
# Turn 1 - You: "Create a file called hello.py"
# Turn 1 - Claude: "I'll create a hello.py file for you..."
# Turn 2 - You: "What's in that file?"
# Turn 2 - Claude: "The hello.py file I just created contains..." (remembers!)
# Turn 3 - You: "Add a main function to it"
# Turn 3 - Claude: "I'll add a main function to hello.py..." (knows which file!)

asyncio.run(main())

Usar hooks para modificación de comportamiento

from claude_agent_sdk import (
    ClaudeSDKClient,
    ClaudeAgentOptions,
    HookMatcher,
    HookContext
)
import asyncio
from typing import Any

async def pre_tool_logger(
    input_data: dict[str, Any],
    tool_use_id: str | None,
    context: HookContext
) -> dict[str, Any]:
    """Log all tool usage before execution."""
    tool_name = input_data.get('tool_name', 'unknown')
    print(f"[PRE-TOOL] About to use: {tool_name}")

    # You can modify or block the tool execution here
    if tool_name == "Bash" and "rm -rf" in str(input_data.get('tool_input', {})):
        return {
            'hookSpecificOutput': {
                'hookEventName': 'PreToolUse',
                'permissionDecision': 'deny',
                'permissionDecisionReason': 'Dangerous command blocked'
            }
        }
    return {}

async def post_tool_logger(
    input_data: dict[str, Any],
    tool_use_id: str | None,
    context: HookContext
) -> dict[str, Any]:
    """Log results after tool execution."""
    tool_name = input_data.get('tool_name', 'unknown')
    print(f"[POST-TOOL] Completed: {tool_name}")
    return {}

async def user_prompt_modifier(
    input_data: dict[str, Any],
    tool_use_id: str | None,
    context: HookContext
) -> dict[str, Any]:
    """Add context to user prompts."""
    original_prompt = input_data.get('prompt', '')

    # Add timestamp to all prompts
    from datetime import datetime
    timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")

    return {
        'hookSpecificOutput': {
            'hookEventName': 'UserPromptSubmit',
            'updatedPrompt': f"[{timestamp}] {original_prompt}"
        }
    }

async def main():
    options = ClaudeAgentOptions(
        hooks={
            'PreToolUse': [
                HookMatcher(hooks=[pre_tool_logger]),
                HookMatcher(matcher='Bash', hooks=[pre_tool_logger])
            ],
            'PostToolUse': [
                HookMatcher(hooks=[post_tool_logger])
            ],
            'UserPromptSubmit': [
                HookMatcher(hooks=[user_prompt_modifier])
            ]
        },
        allowed_tools=["Read", "Write", "Bash"]
    )

    async with ClaudeSDKClient(options=options) as client:
        await client.query("List files in current directory")

        async for message in client.receive_response():
            # Hooks will automatically log tool usage
            pass

asyncio.run(main())

Monitoreo de progreso en tiempo real

from claude_agent_sdk import (
    ClaudeSDKClient,
    ClaudeAgentOptions,
    AssistantMessage,
    ToolUseBlock,
    ToolResultBlock,
    TextBlock
)
import asyncio

async def monitor_progress():
    options = ClaudeAgentOptions(
        allowed_tools=["Write", "Bash"],
        permission_mode="acceptEdits"
    )

    async with ClaudeSDKClient(options=options) as client:
        await client.query(
            "Create 5 Python files with different sorting algorithms"
        )

        # Monitor progress in real-time
        files_created = []
        async for message in client.receive_messages():
            if isinstance(message, AssistantMessage):
                for block in message.content:
                    if isinstance(block, ToolUseBlock):
                        if block.name == "Write":
                            file_path = block.input.get("file_path", "")
                            print(f"🔨 Creating: {file_path}")
                    elif isinstance(block, ToolResultBlock):
                        print(f"✅ Completed tool execution")
                    elif isinstance(block, TextBlock):
                        print(f"💭 Claude says: {block.text[:100]}...")

            # Check if we've received the final result
            if hasattr(message, 'subtype') and message.subtype in ['success', 'error']:
                print(f"\n🎯 Task completed!")
                break

asyncio.run(monitor_progress())

Uso de ejemplo

Operaciones básicas de archivo (usando query)

from claude_agent_sdk import query, ClaudeAgentOptions, AssistantMessage, ToolUseBlock
import asyncio

async def create_project():
    options = ClaudeAgentOptions(
        allowed_tools=["Read", "Write", "Bash"],
        permission_mode='acceptEdits',
        cwd="/home/user/project"
    )

    async for message in query(
        prompt="Create a Python project structure with setup.py",
        options=options
    ):
        if isinstance(message, AssistantMessage):
            for block in message.content:
                if isinstance(block, ToolUseBlock):
                    print(f"Using tool: {block.name}")

asyncio.run(create_project())

Manejo de errores

from claude_agent_sdk import (
    query,
    CLINotFoundError,
    ProcessError,
    CLIJSONDecodeError
)

try:
    async for message in query(prompt="Hello"):
        print(message)
except CLINotFoundError:
    print("Please install Claude Code: npm install -g @anthropic-ai/claude-code")
except ProcessError as e:
    print(f"Process failed with exit code: {e.exit_code}")
except CLIJSONDecodeError as e:
    print(f"Failed to parse response: {e}")

Modo de streaming con cliente

from claude_agent_sdk import ClaudeSDKClient
import asyncio

async def interactive_session():
    async with ClaudeSDKClient() as client:
        # Send initial message
        await client.query("What's the weather like?")

        # Process responses
        async for msg in client.receive_response():
            print(msg)

        # Send follow-up
        await client.query("Tell me more about that")

        # Process follow-up response
        async for msg in client.receive_response():
            print(msg)

asyncio.run(interactive_session())

Usar herramientas personalizadas con ClaudeSDKClient

from claude_agent_sdk import (
    ClaudeSDKClient,
    ClaudeAgentOptions,
    tool,
    create_sdk_mcp_server,
    AssistantMessage,
    TextBlock
)
import asyncio
from typing import Any

# Define custom tools with @tool decorator
@tool("calculate", "Perform mathematical calculations", {"expression": str})
async def calculate(args: dict[str, Any]) -> dict[str, Any]:
    try:
        result = eval(args["expression"], {"__builtins__": {}})
        return {
            "content": [{
                "type": "text",
                "text": f"Result: {result}"
            }]
        }
    except Exception as e:
        return {
            "content": [{
                "type": "text",
                "text": f"Error: {str(e)}"
            }],
            "is_error": True
        }

@tool("get_time", "Get current time", {})
async def get_time(args: dict[str, Any]) -> dict[str, Any]:
    from datetime import datetime
    current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    return {
        "content": [{
            "type": "text",
            "text": f"Current time: {current_time}"
        }]
    }

async def main():
    # Create SDK MCP server with custom tools
    my_server = create_sdk_mcp_server(
        name="utilities",
        version="1.0.0",
        tools=[calculate, get_time]
    )

    # Configure options with the server
    options = ClaudeAgentOptions(
        mcp_servers={"utils": my_server},
        allowed_tools=[
            "mcp__utils__calculate",
            "mcp__utils__get_time"
        ]
    )

    # Use ClaudeSDKClient for interactive tool usage
    async with ClaudeSDKClient(options=options) as client:
        await client.query("What's 123 * 456?")

        # Process calculation response
        async for message in client.receive_response():
            if isinstance(message, AssistantMessage):
                for block in message.content:
                    if isinstance(block, TextBlock):
                        print(f"Calculation: {block.text}")

        # Follow up with time query
        await client.query("What time is it now?")

        async for message in client.receive_response():
            if isinstance(message, AssistantMessage):
                for block in message.content:
                    if isinstance(block, TextBlock):
                        print(f"Time: {block.text}")

asyncio.run(main())

Ver también