Installation

pip install claude-agent-sdk

Wahl zwischen query() und ClaudeSDKClient

Das Python SDK bietet zwei Möglichkeiten, mit Claude Code zu interagieren:

Schneller Vergleich

Featurequery()ClaudeSDKClient
SessionErstellt jedes Mal eine neue SessionVerwendet dieselbe Session wieder
UnterhaltungEinzelner AustauschMehrere Austausche im selben Kontext
VerbindungAutomatisch verwaltetManuelle Kontrolle
Streaming Input✅ Unterstützt✅ Unterstützt
Interrupts❌ Nicht unterstützt✅ Unterstützt
Hooks❌ Nicht unterstützt✅ Unterstützt
Custom Tools❌ Nicht unterstützt✅ Unterstützt
Continue Chat❌ Neue Session jedes Mal✅ Erhält Unterhaltung aufrecht
AnwendungsfallEinmalige AufgabenKontinuierliche Unterhaltungen

Wann query() verwenden (Neue Session jedes Mal)

Am besten für:
  • Einmalige Fragen, bei denen Sie keine Unterhaltungshistorie benötigen
  • Unabhängige Aufgaben, die keinen Kontext aus vorherigen Austauschen erfordern
  • Einfache Automatisierungsskripte
  • Wenn Sie jedes Mal einen frischen Start wollen

Wann ClaudeSDKClient verwenden (Kontinuierliche Unterhaltung)

Am besten für:
  • Fortführung von Unterhaltungen - Wenn Claude sich an den Kontext erinnern soll
  • Nachfragen - Aufbauend auf vorherigen Antworten
  • Interaktive Anwendungen - Chat-Interfaces, REPLs
  • Antwort-gesteuerte Logik - Wenn die nächste Aktion von Claudes Antwort abhängt
  • Session-Kontrolle - Explizite Verwaltung des Unterhaltungslebenszyklus

Funktionen

query()

Erstellt eine neue Session für jede Interaktion mit Claude Code. Gibt einen async Iterator zurück, der Nachrichten liefert, sobald sie ankommen. Jeder Aufruf von query() startet frisch ohne Erinnerung an vorherige Interaktionen.
async def query(
    *,
    prompt: str | AsyncIterable[dict[str, Any]],
    options: ClaudeAgentOptions | None = None
) -> AsyncIterator[Message]

Parameter

ParameterTypBeschreibung
promptstr | AsyncIterable[dict]Der Eingabe-Prompt als String oder async iterable für Streaming-Modus
optionsClaudeAgentOptions | NoneOptionales Konfigurationsobjekt (standardmäßig ClaudeAgentOptions() wenn None)

Rückgabe

Gibt einen AsyncIterator[Message] zurück, der Nachrichten aus der Unterhaltung liefert.

Beispiel - Mit Optionen


import asyncio
from claude_agent_sdk import query, ClaudeAgentOptions

async def main():
    options = ClaudeAgentOptions(
        system_prompt="Du bist ein Experte Python-Entwickler",
        permission_mode='acceptEdits',
        cwd="/home/user/project"
    )

    async for message in query(
        prompt="Erstelle einen Python-Webserver",
        options=options
    ):
        print(message)


asyncio.run(main())

tool()

Decorator zum Definieren von MCP-Tools mit Typsicherheit.
def tool(
    name: str,
    description: str,
    input_schema: type | dict[str, Any]
) -> Callable[[Callable[[Any], Awaitable[dict[str, Any]]]], SdkMcpTool[Any]]

Parameter

ParameterTypBeschreibung
namestrEindeutige Kennung für das Tool
descriptionstrMenschenlesbare Beschreibung dessen, was das Tool tut
input_schematype | dict[str, Any]Schema, das die Eingabeparameter des Tools definiert (siehe unten)

Input Schema Optionen

  1. Einfache Typ-Zuordnung (empfohlen):
    {"text": str, "count": int, "enabled": bool}
    
  2. JSON Schema Format (für komplexe Validierung):
    {
        "type": "object",
        "properties": {
            "text": {"type": "string"},
            "count": {"type": "integer", "minimum": 0}
        },
        "required": ["text"]
    }
    

Rückgabe

Eine Decorator-Funktion, die die Tool-Implementierung umhüllt und eine SdkMcpTool-Instanz zurückgibt.

Beispiel

from claude_agent_sdk import tool
from typing import Any

@tool("greet", "Einen Benutzer begrüßen", {"name": str})
async def greet(args: dict[str, Any]) -> dict[str, Any]:
    return {
        "content": [{
            "type": "text",
            "text": f"Hallo, {args['name']}!"
        }]
    }

create_sdk_mcp_server()

Erstellt einen In-Process-MCP-Server, der innerhalb Ihrer Python-Anwendung läuft.
def create_sdk_mcp_server(
    name: str,
    version: str = "1.0.0",
    tools: list[SdkMcpTool[Any]] | None = None
) -> McpSdkServerConfig

Parameter

ParameterTypStandardBeschreibung
namestr-Eindeutige Kennung für den Server
versionstr"1.0.0"Server-Versionsstring
toolslist[SdkMcpTool[Any]] | NoneNoneListe von Tool-Funktionen, die mit dem @tool-Decorator erstellt wurden

Rückgabe

Gibt ein McpSdkServerConfig-Objekt zurück, das an ClaudeAgentOptions.mcp_servers übergeben werden kann.

Beispiel

from claude_agent_sdk import tool, create_sdk_mcp_server

@tool("add", "Zwei Zahlen addieren", {"a": float, "b": float})
async def add(args):
    return {
        "content": [{
            "type": "text",
            "text": f"Summe: {args['a'] + args['b']}"
        }]
    }

@tool("multiply", "Zwei Zahlen multiplizieren", {"a": float, "b": float})
async def multiply(args):
    return {
        "content": [{
            "type": "text",
            "text": f"Produkt: {args['a'] * args['b']}"
        }]
    }

calculator = create_sdk_mcp_server(
    name="calculator",
    version="2.0.0",
    tools=[add, multiply]  # Dekorierte Funktionen übergeben
)

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

Klassen

ClaudeSDKClient

Erhält eine Unterhaltungssession über mehrere Austausche aufrecht. Dies ist das Python-Äquivalent dazu, wie die query()-Funktion des TypeScript SDK intern funktioniert - es erstellt ein Client-Objekt, das Unterhaltungen fortsetzen kann.

Hauptmerkmale

  • Session-Kontinuität: Erhält Unterhaltungskontext über mehrere query()-Aufrufe aufrecht
  • Dieselbe Unterhaltung: Claude erinnert sich an vorherige Nachrichten in der Session
  • Interrupt-Unterstützung: Kann Claude mitten in der Ausführung stoppen
  • Expliziter Lebenszyklus: Sie kontrollieren, wann die Session startet und endet
  • Antwort-gesteuerter Ablauf: Kann auf Antworten reagieren und Nachfragen senden
  • Custom Tools & Hooks: Unterstützt benutzerdefinierte Tools (erstellt mit @tool-Decorator) und 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

Methoden

MethodeBeschreibung
__init__(options)Initialisiert den Client mit optionaler Konfiguration
connect(prompt)Verbindet sich mit Claude mit einem optionalen initialen Prompt oder Nachrichtenstrom
query(prompt, session_id)Sendet eine neue Anfrage im Streaming-Modus
receive_messages()Empfängt alle Nachrichten von Claude als async Iterator
receive_response()Empfängt Nachrichten bis einschließlich einer ResultMessage
interrupt()Sendet Interrupt-Signal (funktioniert nur im Streaming-Modus)
disconnect()Trennt die Verbindung zu Claude

Context Manager Unterstützung

Der Client kann als async Context Manager für automatische Verbindungsverwaltung verwendet werden:
async with ClaudeSDKClient() as client:
    await client.query("Hallo Claude")
    async for message in client.receive_response():
        print(message)
Wichtig: Beim Iterieren über Nachrichten vermeiden Sie die Verwendung von break zum vorzeitigen Beenden, da dies asyncio-Cleanup-Probleme verursachen kann. Lassen Sie stattdessen die Iteration natürlich abschließen oder verwenden Sie Flags, um zu verfolgen, wann Sie gefunden haben, was Sie brauchen.

Beispiel - Fortsetzung einer Unterhaltung

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

async def main():
    async with ClaudeSDKClient() as client:
        # Erste Frage
        await client.query("Was ist die Hauptstadt von Frankreich?")
        
        # Antwort verarbeiten
        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}")
        
        # Nachfrage - Claude erinnert sich an den vorherigen Kontext
        await client.query("Wie hoch ist die Einwohnerzahl dieser Stadt?")
        
        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}")
        
        # Weitere Nachfrage - immer noch in derselben Unterhaltung
        await client.query("Was sind einige berühmte Sehenswürdigkeiten dort?")
        
        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())

Beispiel - Streaming-Eingabe mit ClaudeSDKClient

import asyncio
from claude_agent_sdk import ClaudeSDKClient

async def message_stream():
    """Generiert Nachrichten dynamisch."""
    yield {"type": "text", "text": "Analysiere die folgenden Daten:"}
    await asyncio.sleep(0.5)
    yield {"type": "text", "text": "Temperatur: 25°C"}
    await asyncio.sleep(0.5)
    yield {"type": "text", "text": "Luftfeuchtigkeit: 60%"}
    await asyncio.sleep(0.5)
    yield {"type": "text", "text": "Welche Muster siehst du?"}

async def main():
    async with ClaudeSDKClient() as client:
        # Eingabe an Claude streamen
        await client.query(message_stream())
        
        # Antwort verarbeiten
        async for message in client.receive_response():
            print(message)
        
        # Nachfrage in derselben Session
        await client.query("Sollten wir uns wegen dieser Messwerte Sorgen machen?")
        
        async for message in client.receive_response():
            print(message)

asyncio.run(main())

Beispiel - Verwendung von Interrupts

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:
        # Startet eine lang laufende Aufgabe
        await client.query("Zähle langsam von 1 bis 100")
        
        # Lässt es eine Weile laufen
        await asyncio.sleep(2)
        
        # Unterbricht die Aufgabe
        await client.interrupt()
        print("Aufgabe unterbrochen!")
        
        # Sendet einen neuen Befehl
        await client.query("Sag stattdessen einfach hallo")
        
        async for message in client.receive_response():
            # Verarbeitet die neue Antwort
            pass

asyncio.run(interruptible_task())

Beispiel - Erweiterte Berechtigungskontrolle

from claude_agent_sdk import (
    ClaudeSDKClient,
    ClaudeAgentOptions
)

async def custom_permission_handler(
    tool_name: str,
    input_data: dict,
    context: dict
):
    """Benutzerdefinierte Logik für Tool-Berechtigungen."""

    # Blockiert Schreibvorgänge in Systemverzeichnisse
    if tool_name == "Write" and input_data.get("file_path", "").startswith("/system/"):
        return {
            "behavior": "deny",
            "message": "Schreibvorgang in Systemverzeichnis nicht erlaubt",
            "interrupt": True
        }

    # Leitet sensible Dateioperationen um
    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}
        }

    # Erlaubt alles andere
    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("Aktualisiere die System-Konfigurationsdatei")
        
        async for message in client.receive_response():
            # Wird stattdessen Sandbox-Pfad verwenden
            print(message)

asyncio.run(main())

Typen

SdkMcpTool

Definition für ein SDK MCP-Tool, das mit dem @tool-Decorator erstellt wurde.
@dataclass
class SdkMcpTool(Generic[T]):
    name: str
    description: str
    input_schema: type[T] | dict[str, Any]
    handler: Callable[[T], Awaitable[dict[str, Any]]]
EigenschaftTypBeschreibung
namestrEindeutige Kennung für das Tool
descriptionstrMenschenlesbare Beschreibung
input_schematype[T] | dict[str, Any]Schema für Eingabevalidierung
handlerCallable[[T], Awaitable[dict[str, Any]]]Async-Funktion, die Tool-Ausführung behandelt

ClaudeAgentOptions

Konfigurations-Dataclass für Claude Code-Abfragen.
@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)
EigenschaftTypStandardBeschreibung
allowed_toolslist[str][]Liste erlaubter Tool-Namen
max_thinking_tokensint8000Maximale Token für Denkprozess
system_promptstr | NoneNoneSystem-Prompt-Konfiguration. Übergeben Sie einen String für benutzerdefinierten Prompt oder verwenden Sie Preset-Format für Claude Codes System-Prompt
mcp_serversdict[str, McpServerConfig] | str | Path{}MCP-Server-Konfigurationen oder Pfad zur Konfigurationsdatei
permission_modePermissionMode | NoneNoneBerechtigungsmodus für Tool-Verwendung
continue_conversationboolFalseSetzt die neueste Unterhaltung fort
resumestr | NoneNoneSession-ID zum Fortsetzen
fork_sessionboolFalseBeim Fortsetzen mit resume, fork zu einer neuen Session-ID anstatt die ursprüngliche Session fortzusetzen
max_turnsint | NoneNoneMaximale Unterhaltungsrunden
disallowed_toolslist[str][]Liste nicht erlaubter Tool-Namen
modelstr | NoneNoneZu verwendendes Claude-Modell
permission_prompt_tool_namestr | NoneNoneMCP-Tool-Name für Berechtigungs-Prompts
cwdstr | Path | NoneNoneAktuelles Arbeitsverzeichnis
settingsstr | NoneNonePfad zur Einstellungsdatei
add_dirslist[str | Path][]Zusätzliche Verzeichnisse, auf die Claude zugreifen kann
extra_argsdict[str, str | None]{}Zusätzliche CLI-Argumente, die direkt an die CLI übergeben werden
can_use_toolCanUseTool | NoneNoneTool-Berechtigungs-Callback-Funktion
hooksdict[HookEvent, list[HookMatcher]] | NoneNoneHook-Konfigurationen zum Abfangen von Ereignissen
agentsdict[str, AgentDefinition] | NoneNoneProgrammatisch definierte Subagenten
setting_sourceslist[SettingSource] | NoneNone (keine Einstellungen)Kontrolliert, welche Dateisystem-Einstellungen das SDK lädt. Wenn weggelassen, werden keine Einstellungen geladen

SettingSource

Kontrolliert, welche dateisystembasierten Konfigurationsquellen das SDK für Einstellungen lädt.
SettingSource = Literal["user", "project", "local"]
WertBeschreibungOrt
"user"Globale Benutzereinstellungen~/.claude/settings.json
"project"Geteilte Projekteinstellungen (versionskontrolliert).claude/settings.json
"local"Lokale Projekteinstellungen (gitignored).claude/settings.local.json

Standardverhalten

Wenn setting_sources weggelassen oder None ist, lädt das SDK keine Dateisystem-Einstellungen. Dies bietet Isolation für SDK-Anwendungen.

Warum setting_sources verwenden?

Alle Dateisystem-Einstellungen laden (Legacy-Verhalten):
# Alle Einstellungen laden wie SDK v0.0.x es tat
from claude_agent_sdk import query, ClaudeAgentOptions

async for message in query(
    prompt="Analysiere diesen Code",
    options=ClaudeAgentOptions(
        setting_sources=["user", "project", "local"]  # Alle Einstellungen laden
    )
):
    print(message)
Nur spezifische Einstellungsquellen laden:
# Nur Projekteinstellungen laden, Benutzer- und lokale ignorieren
async for message in query(
    prompt="CI-Checks ausführen",
    options=ClaudeAgentOptions(
        setting_sources=["project"]  # Nur .claude/settings.json
    )
):
    print(message)
Test- und CI-Umgebungen:
# Konsistentes Verhalten in CI durch Ausschluss lokaler Einstellungen sicherstellen
async for message in query(
    prompt="Tests ausführen",
    options=ClaudeAgentOptions(
        setting_sources=["project"],  # Nur team-geteilte Einstellungen
        permission_mode="bypassPermissions"
    )
):
    print(message)
Nur-SDK-Anwendungen:
# Alles programmatisch definieren (Standardverhalten)
# Keine Dateisystem-Abhängigkeiten - setting_sources ist standardmäßig None
async for message in query(
    prompt="Diese PR überprüfen",
    options=ClaudeAgentOptions(
        # setting_sources=None ist der Standard, muss nicht angegeben werden
        agents={ /* ... */ },
        mcp_servers={ /* ... */ },
        allowed_tools=["Read", "Grep", "Glob"]
    )
):
    print(message)

Einstellungs-Priorität

Wenn mehrere Quellen geladen werden, werden Einstellungen mit dieser Priorität zusammengeführt (höchste zu niedrigste):
  1. Lokale Einstellungen (.claude/settings.local.json)
  2. Projekteinstellungen (.claude/settings.json)
  3. Benutzereinstellungen (~/.claude/settings.json)
Programmatische Optionen (wie agents, allowed_tools) überschreiben immer Dateisystem-Einstellungen.

AgentDefinition

Konfiguration für einen programmatisch definierten Subagenten.
@dataclass
class AgentDefinition:
    description: str
    prompt: str
    tools: list[str] | None = None
    model: Literal["sonnet", "opus", "haiku", "inherit"] | None = None
FeldErforderlichBeschreibung
descriptionJaNatürlichsprachige Beschreibung, wann dieser Agent zu verwenden ist
toolsNeinArray erlaubter Tool-Namen. Wenn weggelassen, erbt alle Tools
promptJaDer System-Prompt des Agenten
modelNeinModell-Override für diesen Agenten. Wenn weggelassen, verwendet das Hauptmodell

PermissionMode

Berechtigungsmodi zur Kontrolle der Tool-Ausführung.
PermissionMode = Literal[
    "default",           # Standard-Berechtigungsverhalten
    "acceptEdits",       # Datei-Edits automatisch akzeptieren
    "plan",              # Planungsmodus - keine Ausführung
    "bypassPermissions"  # Alle Berechtigungsprüfungen umgehen (mit Vorsicht verwenden)
]

McpSdkServerConfig

Konfiguration für SDK MCP-Server, die mit create_sdk_mcp_server() erstellt wurden.
class McpSdkServerConfig(TypedDict):
    type: Literal["sdk"]
    name: str
    instance: Any  # MCP Server-Instanz

McpServerConfig

Union-Typ für MCP-Server-Konfigurationen.
McpServerConfig = McpStdioServerConfig | McpSSEServerConfig | McpHttpServerConfig | McpSdkServerConfig

McpStdioServerConfig

class McpStdioServerConfig(TypedDict):
    type: NotRequired[Literal["stdio"]]  # Optional für Rückwärtskompatibilität
    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]]

Nachrichtentypen

Message

Union-Typ aller möglichen Nachrichten.
Message = UserMessage | AssistantMessage | SystemMessage | ResultMessage

UserMessage

Benutzereingabe-Nachricht.
@dataclass
class UserMessage:
    content: str | list[ContentBlock]

AssistantMessage

Assistenten-Antwortnachricht mit Inhaltsblöcken.
@dataclass
class AssistantMessage:
    content: list[ContentBlock]
    model: str

SystemMessage

System-Nachricht mit Metadaten.
@dataclass
class SystemMessage:
    subtype: str
    data: dict[str, Any]

ResultMessage

Finale Ergebnisnachricht mit Kosten- und Nutzungsinformationen.
@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

Inhaltsblock-Typen

ContentBlock

Union-Typ aller Inhaltsblöcke.
ContentBlock = TextBlock | ThinkingBlock | ToolUseBlock | ToolResultBlock

TextBlock

Text-Inhaltsblock.
@dataclass
class TextBlock:
    text: str

ThinkingBlock

Denk-Inhaltsblock (für Modelle mit Denkfähigkeit).
@dataclass
class ThinkingBlock:
    thinking: str
    signature: str

ToolUseBlock

Tool-Verwendungsanfrage-Block.
@dataclass
class ToolUseBlock:
    id: str
    name: str
    input: dict[str, Any]

ToolResultBlock

Tool-Ausführungsergebnis-Block.
@dataclass
class ToolResultBlock:
    tool_use_id: str
    content: str | list[dict[str, Any]] | None = None
    is_error: bool | None = None

Fehlertypen

ClaudeSDKError

Basis-Exception-Klasse für alle SDK-Fehler.
class ClaudeSDKError(Exception):
    """Basisfehler für Claude SDK."""

CLINotFoundError

Wird ausgelöst, wenn Claude Code CLI nicht installiert oder nicht gefunden wird.
class CLINotFoundError(CLIConnectionError):
    def __init__(self, message: str = "Claude Code nicht gefunden", cli_path: str | None = None):
        """
        Args:
            message: Fehlermeldung (Standard: "Claude Code nicht gefunden")
            cli_path: Optionaler Pfad zur CLI, die nicht gefunden wurde
        """

CLIConnectionError

Wird ausgelöst, wenn die Verbindung zu Claude Code fehlschlägt.
class CLIConnectionError(ClaudeSDKError):
    """Verbindung zu Claude Code fehlgeschlagen."""

ProcessError

Wird ausgelöst, wenn der Claude Code-Prozess fehlschlägt.
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

Wird ausgelöst, wenn JSON-Parsing fehlschlägt.
class CLIJSONDecodeError(ClaudeSDKError):
    def __init__(self, line: str, original_error: Exception):
        """
        Args:
            line: Die Zeile, die nicht geparst werden konnte
            original_error: Der ursprüngliche JSON-Decode-Exception
        """
        self.line = line
        self.original_error = original_error

Hook-Typen

HookEvent

Unterstützte Hook-Ereignistypen. Beachten Sie, dass das Python SDK aufgrund von Setup-Beschränkungen SessionStart-, SessionEnd- und Notification-Hooks nicht unterstützt.
HookEvent = Literal[
    "PreToolUse",      # Wird vor Tool-Ausführung aufgerufen
    "PostToolUse",     # Wird nach Tool-Ausführung aufgerufen
    "UserPromptSubmit", # Wird aufgerufen, wenn Benutzer einen Prompt einreicht
    "Stop",            # Wird beim Stoppen der Ausführung aufgerufen
    "SubagentStop",    # Wird aufgerufen, wenn ein Subagent stoppt
    "PreCompact"       # Wird vor Nachrichten-Kompaktierung aufgerufen
]

HookCallback

Typdefinition für Hook-Callback-Funktionen.
HookCallback = Callable[
    [dict[str, Any], str | None, HookContext],
    Awaitable[dict[str, Any]]
]
Parameter:
  • input_data: Hook-spezifische Eingabedaten (siehe Hook-Dokumentation)
  • tool_use_id: Optionale Tool-Verwendungskennung (für tool-bezogene Hooks)
  • context: Hook-Kontext mit zusätzlichen Informationen
Gibt ein Dictionary zurück, das enthalten kann:
  • decision: "block" um die Aktion zu blockieren
  • systemMessage: System-Nachricht zum Hinzufügen zum Transkript
  • hookSpecificOutput: Hook-spezifische Ausgabedaten

HookContext

Kontextinformationen, die an Hook-Callbacks übergeben werden.
@dataclass
class HookContext:
    signal: Any | None = None  # Zukunft: Abort-Signal-Unterstützung

HookMatcher

Konfiguration zum Zuordnen von Hooks zu spezifischen Ereignissen oder Tools.
@dataclass
class HookMatcher:
    matcher: str | None = None        # Tool-Name oder Muster zum Abgleichen (z.B. "Bash", "Write|Edit")
    hooks: list[HookCallback] = field(default_factory=list)  # Liste der auszuführenden Callbacks

Hook-Verwendungsbeispiel

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]:
    """Validiert und blockiert potentiell gefährliche Bash-Befehle."""
    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': 'Gefährlicher Befehl blockiert'
                }
            }
    return {}

async def log_tool_use(
    input_data: dict[str, Any],
    tool_use_id: str | None,
    context: HookContext
) -> dict[str, Any]:
    """Protokolliert alle Tool-Verwendung für Auditing."""
    print(f"Tool verwendet: {input_data.get('tool_name')}")
    return {}

options = ClaudeAgentOptions(
    hooks={
        'PreToolUse': [
            HookMatcher(matcher='Bash', hooks=[validate_bash_command]),
            HookMatcher(hooks=[log_tool_use])  # Gilt für alle Tools
        ],
        'PostToolUse': [
            HookMatcher(hooks=[log_tool_use])
        ]
    }
)

async for message in query(
    prompt="Analysiere diese Codebasis",
    options=options
):
    print(message)

Tool-Input/Output-Typen

Dokumentation der Input/Output-Schemas für alle eingebauten Claude Code-Tools. Obwohl das Python SDK diese nicht als Typen exportiert, repräsentieren sie die Struktur von Tool-Inputs und -Outputs in Nachrichten.

Task

Tool-Name: Task Input:
{
    "description": str,      # Eine kurze (3-5 Wort) Beschreibung der Aufgabe
    "prompt": str,           # Die Aufgabe für den Agenten
    "subagent_type": str     # Der Typ des spezialisierten Agenten
}
Output:
{
    "result": str,                    # Endergebnis vom Subagenten
    "usage": dict | None,             # Token-Nutzungsstatistiken
    "total_cost_usd": float | None,  # Gesamtkosten in USD
    "duration_ms": int | None         # Ausführungsdauer in Millisekunden
}

Bash

Tool-Name: Bash Input:
{
    "command": str,                  # Der auszuführende Befehl
    "timeout": int | None,           # Optionales Timeout in Millisekunden (max 600000)
    "description": str | None,       # Klare, prägnante Beschreibung (5-10 Wörter)
    "run_in_background": bool | None # Auf true setzen, um im Hintergrund zu laufen
}
Output:
{
    "output": str,              # Kombinierte stdout- und stderr-Ausgabe
    "exitCode": int,            # Exit-Code des Befehls
    "killed": bool | None,      # Ob Befehl wegen Timeout getötet wurde
    "shellId": str | None       # Shell-ID für Hintergrundprozesse
}

Edit

Tool-Name: Edit Input:
{
    "file_path": str,           # Der absolute Pfad zur zu modifizierenden Datei
    "old_string": str,          # Der zu ersetzende Text
    "new_string": str,          # Der Text, durch den ersetzt werden soll
    "replace_all": bool | None  # Alle Vorkommen ersetzen (Standard False)
}
Output:
{
    "message": str,      # Bestätigungsnachricht
    "replacements": int, # Anzahl der vorgenommenen Ersetzungen
    "file_path": str     # Dateipfad, der bearbeitet wurde
}

MultiEdit

Tool-Name: MultiEdit Input:
{
    "file_path": str,     # Der absolute Pfad zur zu modifizierenden Datei
    "edits": [            # Array von Bearbeitungsoperationen
        {
            "old_string": str,          # Der zu ersetzende Text
            "new_string": str,          # Der Text, durch den ersetzt werden soll
            "replace_all": bool | None  # Alle Vorkommen ersetzen
        }
    ]
}
Output:
{
    "message": str,       # Erfolgsnachricht
    "edits_applied": int, # Gesamtanzahl angewendeter Bearbeitungen
    "file_path": str      # Dateipfad, der bearbeitet wurde
}

Read

Tool-Name: Read Input:
{
    "file_path": str,       # Der absolute Pfad zur zu lesenden Datei
    "offset": int | None,   # Die Zeilennummer, ab der gelesen werden soll
    "limit": int | None     # Die Anzahl der zu lesenden Zeilen
}
Output (Textdateien):
{
    "content": str,         # Dateiinhalt mit Zeilennummern
    "total_lines": int,     # Gesamtanzahl der Zeilen in der Datei
    "lines_returned": int   # Tatsächlich zurückgegebene Zeilen
}
Output (Bilder):
{
    "image": str,       # Base64-kodierte Bilddaten
    "mime_type": str,   # Bild-MIME-Typ
    "file_size": int    # Dateigröße in Bytes
}

Write

Tool-Name: Write Input:
{
    "file_path": str,  # Der absolute Pfad zur zu schreibenden Datei
    "content": str     # Der in die Datei zu schreibende Inhalt
}
Output:
{
    "message": str,        # Erfolgsnachricht
    "bytes_written": int,  # Anzahl geschriebener Bytes
    "file_path": str       # Dateipfad, der geschrieben wurde
}

Glob

Tool-Name: Glob Input:
{
    "pattern": str,       # Das Glob-Muster zum Abgleichen von Dateien
    "path": str | None    # Das zu durchsuchende Verzeichnis (standardmäßig cwd)
}
Output:
{
    "matches": list[str],  # Array übereinstimmender Dateipfade
    "count": int,          # Anzahl gefundener Übereinstimmungen
    "search_path": str     # Verwendetes Suchverzeichnis
}

Grep

Tool-Name: Grep Input:
{
    "pattern": str,                    # Das reguläre Ausdrucksmuster
    "path": str | None,                # Datei oder Verzeichnis zum Durchsuchen
    "glob": str | None,                # Glob-Muster zum Filtern von Dateien
    "type": str | None,                # Zu durchsuchender Dateityp
    "output_mode": str | None,         # "content", "files_with_matches", oder "count"
    "-i": bool | None,                 # Groß-/Kleinschreibung ignorieren
    "-n": bool | None,                 # Zeilennummern anzeigen
    "-B": int | None,                  # Zeilen vor jeder Übereinstimmung anzeigen
    "-A": int | None,                  # Zeilen nach jeder Übereinstimmung anzeigen
    "-C": int | None,                  # Zeilen vor und nach anzeigen
    "head_limit": int | None,          # Ausgabe auf erste N Zeilen/Einträge begrenzen
    "multiline": bool | None           # Mehrzeilenmodus aktivieren
}
Output (content-Modus):
{
    "matches": [
        {
            "file": str,
            "line_number": int | None,
            "line": str,
            "before_context": list[str] | None,
            "after_context": list[str] | None
        }
    ],
    "total_matches": int
}
Output (files_with_matches-Modus):
{
    "files": list[str],  # Dateien mit Übereinstimmungen
    "count": int         # Anzahl der Dateien mit Übereinstimmungen
}

NotebookEdit

Tool-Name: NotebookEdit Input:
{
    "notebook_path": str,                     # Absoluter Pfad zum Jupyter-Notebook
    "cell_id": str | None,                    # Die ID der zu bearbeitenden Zelle
    "new_source": str,                        # Die neue Quelle für die Zelle
    "cell_type": "code" | "markdown" | None,  # Der Typ der Zelle
    "edit_mode": "replace" | "insert" | "delete" | None  # Bearbeitungsoperationstyp
}
Output:
{
    "message": str, # Erfolgsnachricht
    "edit_type": "replaced" | "inserted" | "deleted",  # Typ der durchgeführten Bearbeitung
    "cell_id": str | None,                       # Zellen-ID, die betroffen war
    "total_cells": int                           # Gesamtzellen im Notebook nach Bearbeitung
}

WebFetch

Tool-Name: WebFetch Input:
{
    "url": str,     # Die URL, von der Inhalt abgerufen werden soll
    "prompt": str   # Der Prompt, der auf den abgerufenen Inhalt angewendet werden soll
}
Output:
{
    "response": str,           # Antwort des KI-Modells auf den Prompt
    "url": str,                # URL, die abgerufen wurde
    "final_url": str | None,   # Finale URL nach Weiterleitungen
    "status_code": int | None  #HTTP-Statuscode
}

WebSearch

Tool-Name: WebSearch Input:
{
    "query": str,                        # Die zu verwendende Suchanfrage
    "allowed_domains": list[str] | None, # Nur Ergebnisse von diesen Domains einschließen
    "blocked_domains": list[str] | None  # Niemals Ergebnisse von diesen Domains einschließen
}
Output:
{
    "results": [
        {
            "title": str,
            "url": str,
            "snippet": str,
            "metadata": dict | None
        }
    ],
    "total_results": int,
    "query": str
}

TodoWrite

Tool-Name: TodoWrite Input:
{
    "todos": [
        {
            "content": str, # Die Aufgabenbeschreibung
            "status": "pending" | "in_progress" | "completed",  # Aufgabenstatus
            "activeForm": str                            # Aktive Form der Beschreibung
        }
    ]
}
Output:
{
    "message": str,  # Erfolgsnachricht
    "stats": {
        "total": int,
        "pending": int,
        "in_progress": int,
        "completed": int
    }
}

BashOutput

Tool-Name: BashOutput Input:
{
    "bash_id": str,       # Die ID der Hintergrund-Shell
    "filter": str | None  # Optionaler Regex zum Filtern von Ausgabezeilen
}
Output:
{
    "output": str, # Neue Ausgabe seit letzter Überprüfung
    "status": "running" | "completed" | "failed",       # Aktueller Shell-Status
    "exitCode": int | None # Exit-Code bei Abschluss
}

KillBash

Tool-Name: KillBash Input:
{
    "shell_id": str  # Die ID der zu tötenden Hintergrund-Shell
}
Output:
{
    "message": str,  # Erfolgsnachricht
    "shell_id": str  # ID der getöteten Shell
}

ExitPlanMode

Tool-Name: ExitPlanMode Input:
{
    "plan": str  # Der Plan, der vom Benutzer zur Genehmigung ausgeführt werden soll
}
Output:
{
    "message": str,          # Bestätigungsnachricht
    "approved": bool | None  # Ob Benutzer den Plan genehmigt hat
}

ListMcpResources

Tool-Name: ListMcpResources Input:
{
    "server": str | None  # Optionaler Servername zum Filtern von Ressourcen
}
Output:
{
    "resources": [
        {
            "uri": str,
            "name": str,
            "description": str | None,
            "mimeType": str | None,
            "server": str
        }
    ],
    "total": int
}

ReadMcpResource

Tool-Name: ReadMcpResource Input:
{
    "server": str,  # Der MCP-Servername
    "uri": str      # Die zu lesende Ressourcen-URI
}
Output:
{
    "contents": [
        {
            "uri": str,
            "mimeType": str | None,
            "text": str | None,
            "blob": str | None
        }
    ],
    "server": str
}

Erweiterte Features mit ClaudeSDKClient

Aufbau einer kontinuierlichen Unterhaltungsschnittstelle

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

class ConversationSession:
    """Erhält eine einzelne Unterhaltungssession mit Claude aufrecht."""
    
    def __init__(self, options: ClaudeCodeOptions = None):
        self.client = ClaudeSDKClient(options)
        self.turn_count = 0
    
    async def start(self):
        await self.client.connect()
        print("Starte Unterhaltungssession. Claude wird sich an den Kontext erinnern.")
        print("Befehle: 'exit' zum Beenden, 'interrupt' zum Stoppen der aktuellen Aufgabe, 'new' für neue Session")
        
        while True:
            user_input = input(f"\n[Runde {self.turn_count + 1}] Sie: ")
            
            if user_input.lower() == 'exit':
                break
            elif user_input.lower() == 'interrupt':
                await self.client.interrupt()
                print("Aufgabe unterbrochen!")
                continue
            elif user_input.lower() == 'new':
                # Verbindung trennen und neu verbinden für frische Session
                await self.client.disconnect()
                await self.client.connect()
                self.turn_count = 0
                print("Neue Unterhaltungssession gestartet (vorheriger Kontext gelöscht)")
                continue
            
            # Nachricht senden - Claude erinnert sich an alle vorherigen Nachrichten in dieser Session
            await self.client.query(user_input)
            self.turn_count += 1
            
            # Antwort verarbeiten
            print(f"[Runde {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()  # Neue Zeile nach Antwort
        
        await self.client.disconnect()
        print(f"Unterhaltung nach {self.turn_count} Runden beendet.")

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

# Beispielunterhaltung:
# Runde 1 - Sie: "Erstelle eine Datei namens hello.py"
# Runde 1 - Claude: "Ich erstelle eine hello.py-Datei für Sie..."
# Runde 2 - Sie: "Was ist in dieser Datei?"  
# Runde 2 - Claude: "Die hello.py-Datei, die ich gerade erstellt habe, enthält..." (erinnert sich!)
# Runde 3 - Sie: "Füge eine main-Funktion hinzu"
# Runde 3 - Claude: "Ich füge eine main-Funktion zu hello.py hinzu..." (weiß, welche Datei!)

asyncio.run(main())

Verwendung von Hooks zur Verhaltensmodifikation

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]:
    """Protokolliert alle Tool-Verwendung vor der Ausführung."""
    tool_name = input_data.get('tool_name', 'unbekannt')
    print(f"[PRE-TOOL] Verwende gleich: {tool_name}")

    # Sie können hier die Tool-Ausführung modifizieren oder blockieren
    if tool_name == "Bash" and "rm -rf" in str(input_data.get('tool_input', {})):
        return {
            'hookSpecificOutput': {
                'hookEventName': 'PreToolUse',
                'permissionDecision': 'deny',
                'permissionDecisionReason': 'Gefährlicher Befehl blockiert'
            }
        }
    return {}

async def post_tool_logger(
    input_data: dict[str, Any],
    tool_use_id: str | None,
    context: HookContext
) -> dict[str, Any]:
    """Protokolliert Ergebnisse nach Tool-Ausführung."""
    tool_name = input_data.get('tool_name', 'unbekannt')
    print(f"[POST-TOOL] Abgeschlossen: {tool_name}")
    return {}

async def user_prompt_modifier(
    input_data: dict[str, Any],
    tool_use_id: str | None,
    context: HookContext
) -> dict[str, Any]:
    """Fügt Kontext zu Benutzer-Prompts hinzu."""
    original_prompt = input_data.get('prompt', '')

    # Zeitstempel zu allen Prompts hinzufügen
    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("Liste Dateien im aktuellen Verzeichnis auf")
        
        async for message in client.receive_response():
            # Hooks werden automatisch Tool-Verwendung protokollieren
            pass

asyncio.run(main())

Echtzeit-Fortschrittsüberwachung

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(
            "Erstelle 5 Python-Dateien mit verschiedenen Sortieralgorithmen"
        )
        
        # Fortschritt in Echtzeit überwachen
        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"🔨 Erstelle: {file_path}")
                    elif isinstance(block, ToolResultBlock):
                        print(f"✅ Tool-Ausführung abgeschlossen")
                    elif isinstance(block, TextBlock):
                        print(f"💭 Claude sagt: {block.text[:100]}...")
            
            # Prüfen, ob wir das finale Ergebnis erhalten haben
            if hasattr(message, 'subtype') and message.subtype in ['success', 'error']:
                print(f"\n🎯 Aufgabe abgeschlossen!")
                break

asyncio.run(monitor_progress())

Beispielverwendung

Grundlegende Dateioperationen (mit 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="Erstelle eine Python-Projektstruktur mit setup.py",
        options=options
    ):
        if isinstance(message, AssistantMessage):
            for block in message.content:
                if isinstance(block, ToolUseBlock):
                    print(f"Verwende Tool: {block.name}")

asyncio.run(create_project())

Fehlerbehandlung

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

try:
    async for message in query(prompt="Hallo"):
        print(message)
except CLINotFoundError:
    print("Bitte installieren Sie Claude Code: npm install -g @anthropic-ai/claude-code")
except ProcessError as e:
    print(f"Prozess fehlgeschlagen mit Exit-Code: {e.exit_code}")
except CLIJSONDecodeError as e:
    print(f"Antwort konnte nicht geparst werden: {e}")

Streaming-Modus mit Client

from claude_agent_sdk import ClaudeSDKClient
import asyncio

async def interactive_session():
    async with ClaudeSDKClient() as client:
        # Initiale Nachricht senden
        await client.query("Wie ist das Wetter?")
        
        # Antworten verarbeiten
        async for msg in client.receive_response():
            print(msg)
        
        # Nachfrage senden
        await client.query("Erzähl mir mehr darüber")
        
        # Nachfrage-Antwort verarbeiten
        async for msg in client.receive_response():
            print(msg)

asyncio.run(interactive_session())

Verwendung benutzerdefinierter Tools mit ClaudeSDKClient

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

# Benutzerdefinierte Tools mit @tool-Decorator definieren
@tool("calculate", "Mathematische Berechnungen durchführen", {"expression": str})
async def calculate(args: dict[str, Any]) -> dict[str, Any]:
    try:
        result = eval(args["expression"], {"__builtins__": {}})
        return {
            "content": [{
                "type": "text",
                "text": f"Ergebnis: {result}"
            }]
        }
    except Exception as e:
        return {
            "content": [{
                "type": "text",
                "text": f"Fehler: {str(e)}"
            }],
            "is_error": True
        }

@tool("get_time", "Aktuelle Zeit abrufen", {})
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"Aktuelle Zeit: {current_time}"
        }]
    }

async def main():
    # SDK MCP-Server mit benutzerdefinierten Tools erstellen
    my_server = create_sdk_mcp_server(
        name="utilities",
        version="1.0.0",
        tools=[calculate, get_time]
    )

    # Optionen mit dem Server konfigurieren
    options = ClaudeAgentOptions(
        mcp_servers={"utils": my_server},
        allowed_tools=[
            "mcp__utils__calculate",
            "mcp__utils__get_time"
        ]
    )
    
    # ClaudeSDKClient für interaktive Tool-Verwendung nutzen
    async with ClaudeSDKClient(options=options) as client:
        await client.query("Was ist 123 * 456?")
        
        # Berechnungsantwort verarbeiten
        async for message in client.receive_response():
            if isinstance(message, AssistantMessage):
                for block in message.content:
                    if isinstance(block, TextBlock):
                        print(f"Berechnung: {block.text}")
        
        # Mit Zeitabfrage nachfassen
        await client.query("Wie spät ist es jetzt?")
        
        async for message in client.receive_response():
            if isinstance(message, AssistantMessage):
                for block in message.content:
                    if isinstance(block, TextBlock):
                        print(f"Zeit: {block.text}")

asyncio.run(main())

Siehe auch