Installazione

pip install claude-agent-sdk

Scegliere tra query() e ClaudeSDKClient

L’SDK Python fornisce due modi per interagire con Claude Code:

Confronto Rapido

Funzionalitàquery()ClaudeSDKClient
SessioneCrea una nuova sessione ogni voltaRiutilizza la stessa sessione
ConversazioneScambio singoloScambi multipli nello stesso contesto
ConnessioneGestita automaticamenteControllo manuale
Input Streaming✅ Supportato✅ Supportato
Interruzioni❌ Non supportato✅ Supportato
Hook❌ Non supportato✅ Supportato
Strumenti Personalizzati❌ Non supportato✅ Supportato
Continua Chat❌ Nuova sessione ogni volta✅ Mantiene la conversazione
Caso d’UsoAttività una tantumConversazioni continue

Quando Usare query() (Nuova Sessione Ogni Volta)

Migliore per:
  • Domande una tantum dove non hai bisogno della cronologia della conversazione
  • Attività indipendenti che non richiedono contesto da scambi precedenti
  • Script di automazione semplici
  • Quando vuoi un nuovo inizio ogni volta

Quando Usare ClaudeSDKClient (Conversazione Continua)

Migliore per:
  • Continuare conversazioni - Quando hai bisogno che Claude ricordi il contesto
  • Domande di follow-up - Costruire su risposte precedenti
  • Applicazioni interattive - Interfacce chat, REPL
  • Logica guidata dalle risposte - Quando l’azione successiva dipende dalla risposta di Claude
  • Controllo della sessione - Gestire esplicitamente il ciclo di vita della conversazione

Funzioni

query()

Crea una nuova sessione per ogni interazione con Claude Code. Restituisce un iteratore asincrono che produce messaggi man mano che arrivano. Ogni chiamata a query() inizia da zero senza memoria delle interazioni precedenti.
async def query(
    *,
    prompt: str | AsyncIterable[dict[str, Any]],
    options: ClaudeAgentOptions | None = None
) -> AsyncIterator[Message]

Parametri

ParametroTipoDescrizione
promptstr | AsyncIterable[dict]Il prompt di input come stringa o iterabile asincrono per la modalità streaming
optionsClaudeAgentOptions | NoneOggetto di configurazione opzionale (predefinito a ClaudeAgentOptions() se None)

Restituisce

Restituisce un AsyncIterator[Message] che produce messaggi dalla conversazione.

Esempio - Con opzioni


import asyncio
from claude_agent_sdk import query, ClaudeAgentOptions

async def main():
    options = ClaudeAgentOptions(
        system_prompt="Sei un esperto sviluppatore Python",
        permission_mode='acceptEdits',
        cwd="/home/user/project"
    )

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


asyncio.run(main())

tool()

Decoratore per definire strumenti MCP con sicurezza dei tipi.
def tool(
    name: str,
    description: str,
    input_schema: type | dict[str, Any]
) -> Callable[[Callable[[Any], Awaitable[dict[str, Any]]]], SdkMcpTool[Any]]

Parametri

ParametroTipoDescrizione
namestrIdentificatore univoco per lo strumento
descriptionstrDescrizione leggibile dall’uomo di cosa fa lo strumento
input_schematype | dict[str, Any]Schema che definisce i parametri di input dello strumento (vedi sotto)

Opzioni Schema di Input

  1. Mappatura di tipo semplice (raccomandato):
    {"text": str, "count": int, "enabled": bool}
    
  2. Formato JSON Schema (per validazione complessa):
    {
        "type": "object",
        "properties": {
            "text": {"type": "string"},
            "count": {"type": "integer", "minimum": 0}
        },
        "required": ["text"]
    }
    

Restituisce

Una funzione decoratore che avvolge l’implementazione dello strumento e restituisce un’istanza SdkMcpTool.

Esempio

from claude_agent_sdk import tool
from typing import Any

@tool("greet", "Saluta un utente", {"name": str})
async def greet(args: dict[str, Any]) -> dict[str, Any]:
    return {
        "content": [{
            "type": "text",
            "text": f"Ciao, {args['name']}!"
        }]
    }

create_sdk_mcp_server()

Crea un server MCP in-process che funziona all’interno della tua applicazione Python.
def create_sdk_mcp_server(
    name: str,
    version: str = "1.0.0",
    tools: list[SdkMcpTool[Any]] | None = None
) -> McpSdkServerConfig

Parametri

ParametroTipoPredefinitoDescrizione
namestr-Identificatore univoco per il server
versionstr"1.0.0"Stringa versione del server
toolslist[SdkMcpTool[Any]] | NoneNoneLista di funzioni strumento create con il decoratore @tool

Restituisce

Restituisce un oggetto McpSdkServerConfig che può essere passato a ClaudeAgentOptions.mcp_servers.

Esempio

from claude_agent_sdk import tool, create_sdk_mcp_server

@tool("add", "Somma due numeri", {"a": float, "b": float})
async def add(args):
    return {
        "content": [{
            "type": "text",
            "text": f"Somma: {args['a'] + args['b']}"
        }]
    }

@tool("multiply", "Moltiplica due numeri", {"a": float, "b": float})
async def multiply(args):
    return {
        "content": [{
            "type": "text",
            "text": f"Prodotto: {args['a'] * args['b']}"
        }]
    }

calculator = create_sdk_mcp_server(
    name="calculator",
    version="2.0.0",
    tools=[add, multiply]  # Passa funzioni decorate
)

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

Classi

ClaudeSDKClient

Mantiene una sessione di conversazione attraverso scambi multipli. Questo è l’equivalente Python di come funziona internamente la funzione query() dell’SDK TypeScript - crea un oggetto client che può continuare le conversazioni.

Funzionalità Chiave

  • Continuità della Sessione: Mantiene il contesto della conversazione attraverso chiamate multiple a query()
  • Stessa Conversazione: Claude ricorda i messaggi precedenti nella sessione
  • Supporto Interruzioni: Può fermare Claude a metà esecuzione
  • Ciclo di Vita Esplicito: Tu controlli quando la sessione inizia e finisce
  • Flusso Guidato dalle Risposte: Può reagire alle risposte e inviare follow-up
  • Strumenti Personalizzati e Hook: Supporta strumenti personalizzati (creati con il decoratore @tool) e hook
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

Metodi

MetodoDescrizione
__init__(options)Inizializza il client con configurazione opzionale
connect(prompt)Connetti a Claude con un prompt iniziale opzionale o stream di messaggi
query(prompt, session_id)Invia una nuova richiesta in modalità streaming
receive_messages()Ricevi tutti i messaggi da Claude come iteratore asincrono
receive_response()Ricevi messaggi fino a includere un ResultMessage
interrupt()Invia segnale di interruzione (funziona solo in modalità streaming)
disconnect()Disconnetti da Claude

Supporto Context Manager

Il client può essere usato come context manager asincrono per la gestione automatica della connessione:
async with ClaudeSDKClient() as client:
    await client.query("Ciao Claude")
    async for message in client.receive_response():
        print(message)
Importante: Quando iteri sui messaggi, evita di usare break per uscire presto poiché questo può causare problemi di pulizia asyncio. Invece, lascia che l’iterazione si completi naturalmente o usa flag per tracciare quando hai trovato quello che cerchi.

Esempio - Continuare una conversazione

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

async def main():
    async with ClaudeSDKClient() as client:
        # Prima domanda
        await client.query("Qual è la capitale della Francia?")
        
        # Elabora risposta
        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}")
        
        # Domanda di follow-up - Claude ricorda il contesto precedente
        await client.query("Qual è la popolazione di quella città?")
        
        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}")
        
        # Un altro follow-up - ancora nella stessa conversazione
        await client.query("Quali sono alcuni punti di riferimento famosi lì?")
        
        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())

Esempio - Input streaming con ClaudeSDKClient

import asyncio
from claude_agent_sdk import ClaudeSDKClient

async def message_stream():
    """Genera messaggi dinamicamente."""
    yield {"type": "text", "text": "Analizza i seguenti dati:"}
    await asyncio.sleep(0.5)
    yield {"type": "text", "text": "Temperatura: 25°C"}
    await asyncio.sleep(0.5)
    yield {"type": "text", "text": "Umidità: 60%"}
    await asyncio.sleep(0.5)
    yield {"type": "text", "text": "Che pattern vedi?"}

async def main():
    async with ClaudeSDKClient() as client:
        # Stream input a Claude
        await client.query(message_stream())
        
        # Elabora risposta
        async for message in client.receive_response():
            print(message)
        
        # Follow-up nella stessa sessione
        await client.query("Dovremmo essere preoccupati per queste letture?")
        
        async for message in client.receive_response():
            print(message)

asyncio.run(main())

Esempio - Usare interruzioni

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:
        # Inizia un'attività a lungo termine
        await client.query("Conta da 1 a 100 lentamente")
        
        # Lascialo funzionare per un po'
        await asyncio.sleep(2)
        
        # Interrompi l'attività
        await client.interrupt()
        print("Attività interrotta!")
        
        # Invia un nuovo comando
        await client.query("Dì solo ciao invece")
        
        async for message in client.receive_response():
            # Elabora la nuova risposta
            pass

asyncio.run(interruptible_task())

Esempio - Controllo avanzato dei permessi

from claude_agent_sdk import (
    ClaudeSDKClient,
    ClaudeAgentOptions
)

async def custom_permission_handler(
    tool_name: str,
    input_data: dict,
    context: dict
):
    """Logica personalizzata per i permessi degli strumenti."""

    # Blocca scritture nelle directory di sistema
    if tool_name == "Write" and input_data.get("file_path", "").startswith("/system/"):
        return {
            "behavior": "deny",
            "message": "Scrittura directory di sistema non consentita",
            "interrupt": True
        }

    # Reindirizza operazioni su file sensibili
    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}
        }

    # Consenti tutto il resto
    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("Aggiorna il file di configurazione del sistema")
        
        async for message in client.receive_response():
            # Userà il percorso sandbox invece
            print(message)

asyncio.run(main())

Tipi

SdkMcpTool

Definizione per uno strumento SDK MCP creato con il decoratore @tool.
@dataclass
class SdkMcpTool(Generic[T]):
    name: str
    description: str
    input_schema: type[T] | dict[str, Any]
    handler: Callable[[T], Awaitable[dict[str, Any]]]
ProprietàTipoDescrizione
namestrIdentificatore univoco per lo strumento
descriptionstrDescrizione leggibile dall’uomo
input_schematype[T] | dict[str, Any]Schema per la validazione dell’input
handlerCallable[[T], Awaitable[dict[str, Any]]]Funzione asincrona che gestisce l’esecuzione dello strumento

ClaudeAgentOptions

Dataclass di configurazione per le query Claude Code.
@dataclass
class ClaudeAgentOptions:
    allowed_tools: list[str] = field(default_factory=list)
    max_thinking_tokens: int = 8000
    system_prompt: str | 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
    fork_session: bool = False
    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)
ProprietàTipoPredefinitoDescrizione
allowed_toolslist[str][]Lista di nomi strumenti consentiti
max_thinking_tokensint8000Token massimi per il processo di pensiero
system_promptstr | NoneNoneConfigurazione prompt di sistema. Passa una stringa per prompt personalizzato, o usa formato preset per il prompt di sistema di Claude Code
mcp_serversdict[str, McpServerConfig] | str | Path{}Configurazioni server MCP o percorso al file di configurazione
permission_modePermissionMode | NoneNoneModalità permessi per l’uso degli strumenti
continue_conversationboolFalseContinua la conversazione più recente
resumestr | NoneNoneID sessione da riprendere
fork_sessionboolFalseQuando riprendi con resume, fork a un nuovo ID sessione invece di continuare la sessione originale
max_turnsint | NoneNoneTurni massimi di conversazione
disallowed_toolslist[str][]Lista di nomi strumenti non consentiti
modelstr | NoneNoneModello Claude da usare
permission_prompt_tool_namestr | NoneNoneNome strumento MCP per prompt di permessi
cwdstr | Path | NoneNoneDirectory di lavoro corrente
settingsstr | NoneNonePercorso al file delle impostazioni
add_dirslist[str | Path][]Directory aggiuntive a cui Claude può accedere
extra_argsdict[str, str | None]{}Argomenti CLI aggiuntivi da passare direttamente al CLI
can_use_toolCanUseTool | NoneNoneFunzione callback per permessi strumenti
hooksdict[HookEvent, list[HookMatcher]] | NoneNoneConfigurazioni hook per intercettare eventi
agentsdict[str, AgentDefinition] | NoneNoneSubagenti definiti programmaticamente
setting_sourceslist[SettingSource] | NoneNone (nessuna impostazione)Controlla quali impostazioni filesystem caricare. Quando omesso, nessuna impostazione viene caricata

SettingSource

Controlla quali fonti di configurazione basate su filesystem l’SDK carica le impostazioni da.
SettingSource = Literal["user", "project", "local"]
ValoreDescrizionePosizione
"user"Impostazioni utente globali~/.claude/settings.json
"project"Impostazioni progetto condivise (controllate da versione).claude/settings.json
"local"Impostazioni progetto locali (gitignored).claude/settings.local.json

Comportamento predefinito

Quando setting_sources è omesso o None, l’SDK non carica alcuna impostazione filesystem. Questo fornisce isolamento per le applicazioni SDK.

Perché usare setting_sources?

Carica tutte le impostazioni filesystem (comportamento legacy):
# Carica tutte le impostazioni come faceva SDK v0.0.x
from claude_agent_sdk import query, ClaudeAgentOptions

async for message in query(
    prompt="Analizza questo codice",
    options=ClaudeAgentOptions(
        setting_sources=["user", "project", "local"]  # Carica tutte le impostazioni
    )
):
    print(message)
Carica solo fonti di impostazioni specifiche:
# Carica solo impostazioni progetto, ignora utente e locali
async for message in query(
    prompt="Esegui controlli CI",
    options=ClaudeAgentOptions(
        setting_sources=["project"]  # Solo .claude/settings.json
    )
):
    print(message)
Ambienti di test e CI:
# Assicura comportamento coerente in CI escludendo impostazioni locali
async for message in query(
    prompt="Esegui test",
    options=ClaudeAgentOptions(
        setting_sources=["project"],  # Solo impost. condivise del team
        permission_mode="bypassPermissions"
    )
):
    print(message)
Applicazioni solo SDK:
# Definisci tutto programmaticamente (comportamento predefinito)
# Nessuna dipendenza filesystem - setting_sources predefinito a None
async for message in query(
    prompt="Rivedi questa PR",
    options=ClaudeAgentOptions(
        # setting_sources=None è il predefinito, non serve specificare
        agents={ /* ... */ },
        mcp_servers={ /* ... */ },
        allowed_tools=["Read", "Grep", "Glob"]
    )
):
    print(message)

Precedenza delle impostazioni

Quando vengono caricate fonti multiple, le impostazioni vengono unite con questa precedenza (dalla più alta alla più bassa):
  1. Impostazioni locali (.claude/settings.local.json)
  2. Impostazioni progetto (.claude/settings.json)
  3. Impostazioni utente (~/.claude/settings.json)
Le opzioni programmatiche (come agents, allowed_tools) sovrascrivono sempre le impostazioni filesystem.

AgentDefinition

Configurazione per un subagente definito programmaticamente.
@dataclass
class AgentDefinition:
    description: str
    prompt: str
    tools: list[str] | None = None
    model: Literal["sonnet", "opus", "haiku", "inherit"] | None = None
CampoRichiestoDescrizione
descriptionDescrizione in linguaggio naturale di quando usare questo agente
toolsNoArray di nomi strumenti consentiti. Se omesso, eredita tutti gli strumenti
promptIl prompt di sistema dell’agente
modelNoOverride modello per questo agente. Se omesso, usa il modello principale

PermissionMode

Modalità permessi per controllare l’esecuzione degli strumenti.
PermissionMode = Literal[
    "default",           # Comportamento permessi standard
    "acceptEdits",       # Auto-accetta modifiche file
    "plan",              # Modalità pianificazione - nessuna esecuzione
    "bypassPermissions"  # Bypassa tutti i controlli permessi (usa con cautela)
]

McpSdkServerConfig

Configurazione per server SDK MCP creati con create_sdk_mcp_server().
class McpSdkServerConfig(TypedDict):
    type: Literal["sdk"]
    name: str
    instance: Any  # Istanza MCP Server

McpServerConfig

Tipo union per configurazioni server MCP.
McpServerConfig = McpStdioServerConfig | McpSSEServerConfig | McpHttpServerConfig | McpSdkServerConfig

McpStdioServerConfig

class McpStdioServerConfig(TypedDict):
    type: NotRequired[Literal["stdio"]]  # Opzionale per compatibilità all'indietro
    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]]

Tipi di Messaggio

Message

Tipo union di tutti i possibili messaggi.
Message = UserMessage | AssistantMessage | SystemMessage | ResultMessage

UserMessage

Messaggio di input utente.
@dataclass
class UserMessage:
    content: str | list[ContentBlock]

AssistantMessage

Messaggio di risposta assistente con blocchi di contenuto.
@dataclass
class AssistantMessage:
    content: list[ContentBlock]
    model: str

SystemMessage

Messaggio di sistema con metadati.
@dataclass
class SystemMessage:
    subtype: str
    data: dict[str, Any]

ResultMessage

Messaggio risultato finale con informazioni su costo e utilizzo.
@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

Tipi di Blocco Contenuto

ContentBlock

Tipo union di tutti i blocchi di contenuto.
ContentBlock = TextBlock | ThinkingBlock | ToolUseBlock | ToolResultBlock

TextBlock

Blocco di contenuto testuale.
@dataclass
class TextBlock:
    text: str

ThinkingBlock

Blocco di contenuto pensiero (per modelli con capacità di pensiero).
@dataclass
class ThinkingBlock:
    thinking: str
    signature: str

ToolUseBlock

Blocco richiesta uso strumento.
@dataclass
class ToolUseBlock:
    id: str
    name: str
    input: dict[str, Any]

ToolResultBlock

Blocco risultato esecuzione strumento.
@dataclass
class ToolResultBlock:
    tool_use_id: str
    content: str | list[dict[str, Any]] | None = None
    is_error: bool | None = None

Tipi di Errore

ClaudeSDKError

Classe eccezione base per tutti gli errori SDK.
class ClaudeSDKError(Exception):
    """Errore base per Claude SDK."""

CLINotFoundError

Sollevato quando Claude Code CLI non è installato o non trovato.
class CLINotFoundError(CLIConnectionError):
    def __init__(self, message: str = "Claude Code non trovato", cli_path: str | None = None):
        """
        Args:
            message: Messaggio di errore (predefinito: "Claude Code non trovato")
            cli_path: Percorso opzionale al CLI che non è stato trovato
        """

CLIConnectionError

Sollevato quando la connessione a Claude Code fallisce.
class CLIConnectionError(ClaudeSDKError):
    """Fallimento connessione a Claude Code."""

ProcessError

Sollevato quando il processo Claude Code fallisce.
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

Sollevato quando il parsing JSON fallisce.
class CLIJSONDecodeError(ClaudeSDKError):
    def __init__(self, line: str, original_error: Exception):
        """
        Args:
            line: La riga che ha fallito il parsing
            original_error: L'eccezione originale di decodifica JSON
        """
        self.line = line
        self.original_error = original_error

Tipi Hook

HookEvent

Tipi di eventi hook supportati. Nota che a causa di limitazioni di setup, l’SDK Python non supporta hook SessionStart, SessionEnd e Notification.
HookEvent = Literal[
    "PreToolUse",      # Chiamato prima dell'esecuzione strumento
    "PostToolUse",     # Chiamato dopo l'esecuzione strumento
    "UserPromptSubmit", # Chiamato quando l'utente invia un prompt
    "Stop",            # Chiamato quando si ferma l'esecuzione
    "SubagentStop",    # Chiamato quando un subagente si ferma
    "PreCompact"       # Chiamato prima della compattazione messaggi
]

HookCallback

Definizione tipo per funzioni callback hook.
HookCallback = Callable[
    [dict[str, Any], str | None, HookContext],
    Awaitable[dict[str, Any]]
]
Parametri:
  • input_data: Dati di input specifici dell’hook (vedi documentazione hook)
  • tool_use_id: Identificatore uso strumento opzionale (per hook relativi agli strumenti)
  • context: Contesto hook con informazioni aggiuntive
Restituisce un dizionario che può contenere:
  • decision: "block" per bloccare l’azione
  • systemMessage: Messaggio di sistema da aggiungere alla trascrizione
  • hookSpecificOutput: Dati di output specifici dell’hook

HookContext

Informazioni di contesto passate ai callback hook.
@dataclass
class HookContext:
    signal: Any | None = None  # Futuro: supporto segnale abort

HookMatcher

Configurazione per abbinare hook a eventi o strumenti specifici.
@dataclass
class HookMatcher:
    matcher: str | None = None        # Nome strumento o pattern da abbinare (es. "Bash", "Write|Edit")
    hooks: list[HookCallback] = field(default_factory=list)  # Lista di callback da eseguire

Esempio Uso 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]:
    """Valida e potenzialmente blocca comandi bash pericolosi."""
    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': 'Comando pericoloso bloccato'
                }
            }
    return {}

async def log_tool_use(
    input_data: dict[str, Any],
    tool_use_id: str | None,
    context: HookContext
) -> dict[str, Any]:
    """Registra tutto l'uso degli strumenti per audit."""
    print(f"Strumento usato: {input_data.get('tool_name')}")
    return {}

options = ClaudeAgentOptions(
    hooks={
        'PreToolUse': [
            HookMatcher(matcher='Bash', hooks=[validate_bash_command]),
            HookMatcher(hooks=[log_tool_use])  # Si applica a tutti gli strumenti
        ],
        'PostToolUse': [
            HookMatcher(hooks=[log_tool_use])
        ]
    }
)

async for message in query(
    prompt="Analizza questa codebase",
    options=options
):
    print(message)

Tipi Input/Output Strumenti

Documentazione degli schemi input/output per tutti gli strumenti integrati Claude Code. Mentre l’SDK Python non esporta questi come tipi, rappresentano la struttura degli input e output degli strumenti nei messaggi.

Task

Nome strumento: Task Input:
{
    "description": str,      # Una breve descrizione (3-5 parole) dell'attività
    "prompt": str,           # L'attività per l'agente da eseguire
    "subagent_type": str     # Il tipo di agente specializzato da usare
}
Output:
{
    "result": str,                    # Risultato finale dal subagente
    "usage": dict | None,             # Statistiche uso token
    "total_cost_usd": float | None,  # Costo totale in USD
    "duration_ms": int | None         # Durata esecuzione in millisecondi
}

Bash

Nome strumento: Bash Input:
{
    "command": str,                  # Il comando da eseguire
    "timeout": int | None,           # Timeout opzionale in millisecondi (max 600000)
    "description": str | None,       # Descrizione chiara, concisa (5-10 parole)
    "run_in_background": bool | None # Imposta a true per eseguire in background
}
Output:
{
    "output": str,              # Output combinato stdout e stderr
    "exitCode": int,            # Codice di uscita del comando
    "killed": bool | None,      # Se il comando è stato terminato per timeout
    "shellId": str | None       # ID shell per processi in background
}

Edit

Nome strumento: Edit Input:
{
    "file_path": str,           # Il percorso assoluto al file da modificare
    "old_string": str,          # Il testo da sostituire
    "new_string": str,          # Il testo con cui sostituirlo
    "replace_all": bool | None  # Sostituisci tutte le occorrenze (predefinito False)
}
Output:
{
    "message": str,      # Messaggio di conferma
    "replacements": int, # Numero di sostituzioni effettuate
    "file_path": str     # Percorso file che è stato modificato
}

MultiEdit

Nome strumento: MultiEdit Input:
{
    "file_path": str,     # Il percorso assoluto al file da modificare
    "edits": [            # Array di operazioni di modifica
        {
            "old_string": str,          # Il testo da sostituire
            "new_string": str,          # Il testo con cui sostituirlo
            "replace_all": bool | None  # Sostituisci tutte le occorrenze
        }
    ]
}
Output:
{
    "message": str,       # Messaggio di successo
    "edits_applied": int, # Numero totale di modifiche applicate
    "file_path": str      # Percorso file che è stato modificato
}

Read

Nome strumento: Read Input:
{
    "file_path": str,       # Il percorso assoluto al file da leggere
    "offset": int | None,   # Il numero di riga da cui iniziare a leggere
    "limit": int | None     # Il numero di righe da leggere
}
Output (File di testo):
{
    "content": str,         # Contenuti file con numeri di riga
    "total_lines": int,     # Numero totale di righe nel file
    "lines_returned": int   # Righe effettivamente restituite
}
Output (Immagini):
{
    "image": str,       # Dati immagine codificati Base64
    "mime_type": str,   # Tipo MIME immagine
    "file_size": int    # Dimensione file in byte
}

Write

Nome strumento: Write Input:
{
    "file_path": str,  # Il percorso assoluto al file da scrivere
    "content": str     # Il contenuto da scrivere nel file
}
Output:
{
    "message": str,        # Messaggio di successo
    "bytes_written": int,  # Numero di byte scritti
    "file_path": str       # Percorso file che è stato scritto
}

Glob

Nome strumento: Glob Input:
{
    "pattern": str,       # Il pattern glob per abbinare i file
    "path": str | None    # La directory in cui cercare (predefinito a cwd)
}
Output:
{
    "matches": list[str],  # Array di percorsi file corrispondenti
    "count": int,          # Numero di corrispondenze trovate
    "search_path": str     # Directory di ricerca usata
}

Grep

Nome strumento: Grep Input:
{
    "pattern": str,                    # Il pattern espressione regolare
    "path": str | None,                # File o directory in cui cercare
    "glob": str | None,                # Pattern glob per filtrare file
    "type": str | None,                # Tipo di file da cercare
    "output_mode": str | None,         # "content", "files_with_matches", o "count"
    "-i": bool | None,                 # Ricerca case insensitive
    "-n": bool | None,                 # Mostra numeri di riga
    "-B": int | None,                  # Righe da mostrare prima di ogni corrispondenza
    "-A": int | None,                  # Righe da mostrare dopo ogni corrispondenza
    "-C": int | None,                  # Righe da mostrare prima e dopo
    "head_limit": int | None,          # Limita output alle prime N righe/voci
    "multiline": bool | None           # Abilita modalità multiriga
}
Output (modalità content):
{
    "matches": [
        {
            "file": str,
            "line_number": int | None,
            "line": str,
            "before_context": list[str] | None,
            "after_context": list[str] | None
        }
    ],
    "total_matches": int
}
Output (modalità files_with_matches):
{
    "files": list[str],  # File contenenti corrispondenze
    "count": int         # Numero di file con corrispondenze
}

NotebookEdit

Nome strumento: NotebookEdit Input:
{
    "notebook_path": str,                     # Percorso assoluto al notebook Jupyter
    "cell_id": str | None,                    # L'ID della cella da modificare
    "new_source": str,                        # Il nuovo sorgente per la cella
    "cell_type": "code" | "markdown" | None,  # Il tipo della cella
    "edit_mode": "replace" | "insert" | "delete" | None  # Tipo operazione di modifica
}
Output:
{
    "message": str, # Messaggio di successo
    "edit_type": "replaced" | "inserted" | "deleted",  # Tipo di modifica eseguita
    "cell_id": str | None,                       # ID cella che è stata interessata
    "total_cells": int                           # Celle totali nel notebook dopo la modifica
}

WebFetch

Nome strumento: WebFetch Input:
{
    "url": str,     # L'URL da cui recuperare contenuto
    "prompt": str   # Il prompt da eseguire sul contenuto recuperato
}
Output:
{
    "response": str,           # Risposta del modello AI al prompt
    "url": str,                # URL che è stato recuperato
    "final_url": str | None,   # URL finale dopo reindirizzamenti
    "status_code": int | None  # Codice di stato HTTP
}

WebSearch

Nome strumento: WebSearch Input:
{
    "query": str,                        # La query di ricerca da usare
    "allowed_domains": list[str] | None, # Includi solo risultati da questi domini
    "blocked_domains": list[str] | None  # Non includere mai risultati da questi domini
}
Output:
{
    "results": [
        {
            "title": str,
            "url": str,
            "snippet": str,
            "metadata": dict | None
        }
    ],
    "total_results": int,
    "query": str
}

TodoWrite

Nome strumento: TodoWrite Input:
{
    "todos": [
        {
            "content": str, # La descrizione dell'attività
            "status": "pending" | "in_progress" | "completed",  # Stato attività
            "activeForm": str                            # Forma attiva della descrizione
        }
    ]
}
Output:
{
    "message": str,  # Messaggio di successo
    "stats": {
        "total": int,
        "pending": int,
        "in_progress": int,
        "completed": int
    }
}

BashOutput

Nome strumento: BashOutput Input:
{
    "bash_id": str,       # L'ID della shell in background
    "filter": str | None  # Regex opzionale per filtrare righe di output
}
Output:
{
    "output": str, # Nuovo output dall'ultimo controllo
    "status": "running" | "completed" | "failed",       # Stato shell corrente
    "exitCode": int | None # Codice di uscita quando completato
}

KillBash

Nome strumento: KillBash Input:
{
    "shell_id": str  # L'ID della shell in background da terminare
}
Output:
{
    "message": str,  # Messaggio di successo
    "shell_id": str  # ID della shell terminata
}

ExitPlanMode

Nome strumento: ExitPlanMode Input:
{
    "plan": str  # Il piano da eseguire dall'utente per approvazione
}
Output:
{
    "message": str,          # Messaggio di conferma
    "approved": bool | None  # Se l'utente ha approvato il piano
}

ListMcpResources

Nome strumento: ListMcpResources Input:
{
    "server": str | None  # Nome server opzionale per filtrare risorse per
}
Output:
{
    "resources": [
        {
            "uri": str,
            "name": str,
            "description": str | None,
            "mimeType": str | None,
            "server": str
        }
    ],
    
    "total": int
}

ReadMcpResource

Nome strumento: ReadMcpResource Input:
{
    "server": str,  # Il nome del server MCP
    "uri": str      # L'URI risorsa da leggere
}
Output:
{
    "contents": [
        {
            "uri": str,
            "mimeType": str | None,
            "text": str | None,
            "blob": str | None
        }
    ],
    "server": str
}

Funzionalità Avanzate con ClaudeSDKClient

Costruire un’Interfaccia di Conversazione Continua

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

class ConversationSession:
    """Mantiene una singola sessione di conversazione con Claude."""
    
    def __init__(self, options: ClaudeCodeOptions = None):
        self.client = ClaudeSDKClient(options)
        self.turn_count = 0
    
    async def start(self):
        await self.client.connect()
        print("Avvio sessione di conversazione. Claude ricorderà il contesto.")
        print("Comandi: 'exit' per uscire, 'interrupt' per fermare attività corrente, 'new' per nuova sessione")
        
        while True:
            user_input = input(f"\n[Turno {self.turn_count + 1}] Tu: ")
            
            if user_input.lower() == 'exit':
                break
            elif user_input.lower() == 'interrupt':
                await self.client.interrupt()
                print("Attività interrotta!")
                continue
            elif user_input.lower() == 'new':
                # Disconnetti e riconnetti per una sessione fresca
                await self.client.disconnect()
                await self.client.connect()
                self.turn_count = 0
                print("Avviata nuova sessione di conversazione (contesto precedente cancellato)")
                continue
            
            # Invia messaggio - Claude ricorda tutti i messaggi precedenti in questa sessione
            await self.client.query(user_input)
            self.turn_count += 1
            
            # Elabora risposta
            print(f"[Turno {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()  # Nuova riga dopo la risposta
        
        await self.client.disconnect()
        print(f"Conversazione terminata dopo {self.turn_count} turni.")

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

# Esempio conversazione:
# Turno 1 - Tu: "Crea un file chiamato hello.py"
# Turno 1 - Claude: "Creerò un file hello.py per te..."
# Turno 2 - Tu: "Cosa c'è in quel file?"  
# Turno 2 - Claude: "Il file hello.py che ho appena creato contiene..." (ricorda!)
# Turno 3 - Tu: "Aggiungi una funzione main ad esso"
# Turno 3 - Claude: "Aggiungerò una funzione main a hello.py..." (sa quale file!)

asyncio.run(main())

Usare Hook per Modifica del Comportamento

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]:
    """Registra tutto l'uso degli strumenti prima dell'esecuzione."""
    tool_name = input_data.get('tool_name', 'sconosciuto')
    print(f"[PRE-STRUMENTO] Sto per usare: {tool_name}")

    # Puoi modificare o bloccare l'esecuzione dello strumento qui
    if tool_name == "Bash" and "rm -rf" in str(input_data.get('tool_input', {})):
        return {
            'hookSpecificOutput': {
                'hookEventName': 'PreToolUse',
                'permissionDecision': 'deny',
                'permissionDecisionReason': 'Comando pericoloso bloccato'
            }
        }
    return {}

async def post_tool_logger(
    input_data: dict[str, Any],
    tool_use_id: str | None,
    context: HookContext
) -> dict[str, Any]:
    """Registra risultati dopo l'esecuzione dello strumento."""
    tool_name = input_data.get('tool_name', 'sconosciuto')
    print(f"[POST-STRUMENTO] Completato: {tool_name}")
    return {}

async def user_prompt_modifier(
    input_data: dict[str, Any],
    tool_use_id: str | None,
    context: HookContext
) -> dict[str, Any]:
    """Aggiungi contesto ai prompt utente."""
    original_prompt = input_data.get('prompt', '')

    # Aggiungi timestamp a tutti i prompt
    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("Elenca file nella directory corrente")
        
        async for message in client.receive_response():
            # Gli hook registreranno automaticamente l'uso degli strumenti
            pass

asyncio.run(main())

Monitoraggio Progresso in Tempo Reale

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(
            "Crea 5 file Python con diversi algoritmi di ordinamento"
        )
        
        # Monitora progresso in tempo reale
        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"🔨 Creando: {file_path}")
                    elif isinstance(block, ToolResultBlock):
                        print(f"✅ Esecuzione strumento completata")
                    elif isinstance(block, TextBlock):
                        print(f"💭 Claude dice: {block.text[:100]}...")
            
            # Controlla se abbiamo ricevuto il risultato finale
            if hasattr(message, 'subtype') and message.subtype in ['success', 'error']:
                print(f"\n🎯 Attività completata!")
                break

asyncio.run(monitor_progress())

Esempio di Utilizzo

Operazioni file di base (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="Crea una struttura progetto Python con setup.py",
        options=options
    ):
        if isinstance(message, AssistantMessage):
            for block in message.content:
                if isinstance(block, ToolUseBlock):
                    print(f"Usando strumento: {block.name}")

asyncio.run(create_project())

Gestione errori

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

try:
    async for message in query(prompt="Ciao"):
        print(message)
except CLINotFoundError:
    print("Per favore installa Claude Code: npm install -g @anthropic-ai/claude-code")
except ProcessError as e:
    print(f"Processo fallito con codice di uscita: {e.exit_code}")
except CLIJSONDecodeError as e:
    print(f"Fallimento parsing risposta: {e}")

Modalità streaming con client

from claude_agent_sdk import ClaudeSDKClient
import asyncio

async def interactive_session():
    async with ClaudeSDKClient() as client:
        # Invia messaggio iniziale
        await client.query("Com'è il tempo?")
        
        # Elabora risposte
        async for msg in client.receive_response():
            print(msg)
        
        # Invia follow-up
        await client.query("Dimmi di più su quello")
        
        # Elabora risposta follow-up
        async for msg in client.receive_response():
            print(msg)

asyncio.run(interactive_session())

Usare strumenti personalizzati con ClaudeSDKClient

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

# Definisci strumenti personalizzati con decoratore @tool
@tool("calculate", "Esegui calcoli matematici", {"expression": str})
async def calculate(args: dict[str, Any]) -> dict[str, Any]:
    try:
        result = eval(args["expression"], {"__builtins__": {}})
        return {
            "content": [{
                "type": "text",
                "text": f"Risultato: {result}"
            }]
        }
    except Exception as e:
        return {
            "content": [{
                "type": "text",
                "text": f"Errore: {str(e)}"
            }],
            "is_error": True
        }

@tool("get_time", "Ottieni ora corrente", {})
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"Ora corrente: {current_time}"
        }]
    }

async def main():
    # Crea server SDK MCP con strumenti personalizzati
    my_server = create_sdk_mcp_server(
        name="utilities",
        version="1.0.0",
        tools=[calculate, get_time]
    )

    # Configura opzioni con il server
    options = ClaudeAgentOptions(
        mcp_servers={"utils": my_server},
        allowed_tools=[
            "mcp__utils__calculate",
            "mcp__utils__get_time"
        ]
    )
    
    # Usa ClaudeSDKClient per uso strumenti interattivo
    async with ClaudeSDKClient(options=options) as client:
        await client.query("Quanto fa 123 * 456?")
        
        # Elabora risposta calcolo
        async for message in client.receive_response():
            if isinstance(message, AssistantMessage):
                for block in message.content:
                    if isinstance(block, TextBlock):
                        print(f"Calcolo: {block.text}")
        
        # Segui con query ora
        await client.query("Che ore sono ora?")
        
        async for message in client.receive_response():
            if isinstance(message, AssistantMessage):
                for block in message.content:
                    if isinstance(block, TextBlock):
                        print(f"Ora: {block.text}")

asyncio.run(main())

Vedi anche