Installation

pip install claude-agent-sdk

Wahl zwischen query() und ClaudeSDKClient

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

Schnellvergleich

Funktionquery()ClaudeSDKClient
SitzungErstellt jedes Mal eine neue SitzungVerwendet dieselbe Sitzung wieder
KonversationEinzelner AustauschMehrere Austausche im gleichen Kontext
VerbindungAutomatisch verwaltetManuelle Kontrolle
Streaming-Eingabe✅ Unterstützt✅ Unterstützt
Unterbrechungen❌ Nicht unterstützt✅ Unterstützt
Hooks❌ Nicht unterstützt✅ Unterstützt
Benutzerdefinierte Tools❌ Nicht unterstützt✅ Unterstützt
Chat fortsetzen❌ Neue Sitzung jedes Mal✅ Behält Konversation bei
AnwendungsfallEinmalige AufgabenKontinuierliche Konversationen

Wann query() verwenden (Neue Sitzung jedes Mal)

Am besten für:
  • Einmalige Fragen, bei denen Sie keinen Konversationsverlauf benötigen
  • Unabhängige Aufgaben, die keinen Kontext aus vorherigen Austauschen erfordern
  • Einfache Automatisierungsskripte
  • Wenn Sie jedes Mal einen Neuanfang möchten

Wann ClaudeSDKClient verwenden (Kontinuierliche Konversation)

Am besten für:
  • Konversationen fortsetzen - Wenn Claude den Kontext merken muss
  • Nachfolgefragen - Aufbau auf vorherigen Antworten
  • Interaktive Anwendungen - Chat-Schnittstellen, REPLs
  • Antwortgesteuerte Logik - Wenn die nächste Aktion von Claudes Antwort abhängt
  • Sitzungskontrolle - Explizite Verwaltung des Konversationslebenszyklus

Funktionen

query()

Erstellt für jede Interaktion mit Claude Code eine neue Sitzung. Gibt einen asynchronen Iterator zurück, der Nachrichten bei ihrer Ankunft liefert. Jeder Aufruf von query() beginnt neu 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]Die Eingabeaufforderung als Zeichenkette oder asynchroner Iterator für den Streaming-Modus
optionsClaudeAgentOptions | NoneOptionales Konfigurationsobjekt (standardmäßig ClaudeAgentOptions(), wenn None)

Rückgabe

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

Beispiel - Mit Optionen


import asyncio
from claude_agent_sdk import query, ClaudeAgentOptions

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

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


asyncio.run(main())

tool()

Dekorator 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
descriptionstrBenutzerfreundliche Beschreibung der Funktion des Tools
input_schematype | dict[str, Any]Schema, das die Eingabeparameter des Tools definiert (siehe unten)

Optionen für Eingabeschema

  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 Dekoratorfunktion, 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", "Greet a user", {"name": str})
async def greet(args: dict[str, Any]) -> dict[str, Any]:
    return {
        "content": [{
            "type": "text",
            "text": f"Hello, {args['name']}!"
        }]
    }

create_sdk_mcp_server()

Erstellen Sie einen In-Process-MCP-Server, der in Ihrer Python-Anwendung ausgeführt wird.
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"Versionsnummer des Servers
toolslist[SdkMcpTool[Any]] | NoneNoneListe von Tool-Funktionen, die mit dem @tool-Dekorator 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", "Add two numbers", {"a": float, "b": float})
async def add(args):
    return {
        "content": [{
            "type": "text",
            "text": f"Sum: {args['a'] + args['b']}"
        }]
    }

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

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

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

Klassen

ClaudeSDKClient

Behält eine Konversationssitzung über mehrere Austausche hinweg bei. Dies ist das Python-Äquivalent dazu, wie die query()-Funktion des TypeScript SDK intern funktioniert - sie erstellt ein Client-Objekt, das Konversationen fortsetzen kann.

Wichtigste Funktionen

  • Sitzungskontinuität: Behält Konversationskontext über mehrere query()-Aufrufe hinweg bei
  • Gleiche Konversation: Claude erinnert sich an vorherige Nachrichten in der Sitzung
  • Unterbrechungsunterstützung: Kann Claude während der Ausführung stoppen
  • Expliziter Lebenszyklus: Sie kontrollieren, wann die Sitzung beginnt und endet
  • Antwortgesteuerte Abläufe: Kann auf Antworten reagieren und Nachverfolgungen senden
  • Benutzerdefinierte Tools & Hooks: Unterstützt benutzerdefinierte Tools (erstellt mit @tool-Dekorator) 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)Initialisieren Sie den Client mit optionaler Konfiguration
connect(prompt)Verbindung zu Claude mit optionaler Anfangsnachricht oder Nachrichtenstrom
query(prompt, session_id)Senden Sie eine neue Anfrage im Streaming-Modus
receive_messages()Empfangen Sie alle Nachrichten von Claude als asynchronen Iterator
receive_response()Empfangen Sie Nachrichten bis einschließlich einer ResultMessage
interrupt()Senden Sie ein Unterbrechungssignal (funktioniert nur im Streaming-Modus)
disconnect()Trennen Sie die Verbindung zu Claude

Context Manager-Unterstützung

Der Client kann als asynchroner Context Manager für automatische Verbindungsverwaltung verwendet werden:
async with ClaudeSDKClient() as client:
    await client.query("Hello Claude")
    async for message in client.receive_response():
        print(message)
Wichtig: Vermeiden Sie bei der Iteration über Nachrichten die Verwendung von break, um vorzeitig zu beenden, da dies zu asyncio-Bereinigungsproblemen führen kann. Lassen Sie die Iteration stattdessen natürlich abschließen oder verwenden Sie Flags, um zu verfolgen, wann Sie gefunden haben, was Sie suchen.

Beispiel - Konversation fortsetzen

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

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

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

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

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

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

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

asyncio.run(main())

Beispiel - Streaming-Eingabe mit ClaudeSDKClient

import asyncio
from claude_agent_sdk import ClaudeSDKClient

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

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

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

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

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

asyncio.run(main())

Beispiel - Unterbrechungen verwenden

import asyncio
from claude_agent_sdk import ClaudeSDKClient, ClaudeAgentOptions

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

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

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

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

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

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

asyncio.run(interruptible_task())

Beispiel - Erweiterte Berechtigungskontrolle

from claude_agent_sdk import (
    ClaudeSDKClient,
    ClaudeAgentOptions
)

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

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

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

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

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

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

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

asyncio.run(main())

Typen

SdkMcpTool

Definition für ein SDK MCP-Tool, das mit dem @tool-Dekorator 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
descriptionstrBenutzerfreundliche Beschreibung
input_schematype[T] | dict[str, Any]Schema für Eingabevalidierung
handlerCallable[[T], Awaitable[dict[str, Any]]]Asynchrone Funktion, die die Tool-Ausführung verarbeitet

ClaudeAgentOptions

Konfigurationsdatenklasse für Claude Code-Abfragen.
@dataclass
class ClaudeAgentOptions:
    allowed_tools: list[str] = field(default_factory=list)
    system_prompt: str | SystemPromptPreset | None = None
    mcp_servers: dict[str, McpServerConfig] | str | Path = field(default_factory=dict)
    permission_mode: PermissionMode | None = None
    continue_conversation: bool = False
    resume: str | None = None
    max_turns: int | None = None
    disallowed_tools: list[str] = field(default_factory=list)
    model: str | None = None
    permission_prompt_tool_name: str | None = None
    cwd: str | Path | None = None
    settings: str | None = None
    add_dirs: list[str | Path] = field(default_factory=list)
    env: dict[str, str] = field(default_factory=dict)
    extra_args: dict[str, str | None] = field(default_factory=dict)
    max_buffer_size: int | None = None
    debug_stderr: Any = sys.stderr  # Deprecated
    stderr: Callable[[str], None] | None = None
    can_use_tool: CanUseTool | None = None
    hooks: dict[HookEvent, list[HookMatcher]] | None = None
    user: str | None = None
    include_partial_messages: bool = False
    fork_session: bool = False
    agents: dict[str, AgentDefinition] | None = None
    setting_sources: list[SettingSource] | None = None
EigenschaftTypStandardBeschreibung
allowed_toolslist[str][]Liste der zulässigen Tool-Namen
system_promptstr | SystemPromptPreset | NoneNoneKonfiguration der Systemaufforderung. Übergeben Sie eine Zeichenkette für eine benutzerdefinierte Aufforderung oder verwenden Sie {"type": "preset", "preset": "claude_code"} für die Systemaufforderung von Claude Code. Fügen Sie "append" hinzu, um die Voreinstellung zu erweitern
mcp_serversdict[str, McpServerConfig] | str | Path{}MCP-Serverkonfigurationen oder Pfad zur Konfigurationsdatei
permission_modePermissionMode | NoneNoneBerechtigungsmodus für die Tool-Nutzung
continue_conversationboolFalseSetzen Sie die neueste Konversation fort
resumestr | NoneNoneSitzungs-ID zum Fortsetzen
max_turnsint | NoneNoneMaximale Konversationsrunden
disallowed_toolslist[str][]Liste der nicht zulässigen Tool-Namen
modelstr | NoneNoneZu verwendendes Claude-Modell
permission_prompt_tool_namestr | NoneNoneMCP-Tool-Name für Berechtigungsaufforderungen
cwdstr | Path | NoneNoneAktuelles Arbeitsverzeichnis
settingsstr | NoneNonePfad zur Einstellungsdatei
add_dirslist[str | Path][]Zusätzliche Verzeichnisse, auf die Claude zugreifen kann
envdict[str, str]{}Umgebungsvariablen
extra_argsdict[str, str | None]{}Zusätzliche CLI-Argumente, die direkt an die CLI übergeben werden
max_buffer_sizeint | NoneNoneMaximale Bytes beim Puffern der CLI-Standardausgabe
debug_stderrAnysys.stderrVeraltet - Dateiähnliches Objekt für Debug-Ausgabe. Verwenden Sie stattdessen den stderr-Callback
stderrCallable[[str], None] | NoneNoneCallback-Funktion für stderr-Ausgabe von CLI
can_use_toolCanUseTool | NoneNoneTool-Berechtigungscallback-Funktion
hooksdict[HookEvent, list[HookMatcher]] | NoneNoneHook-Konfigurationen zum Abfangen von Ereignissen
userstr | NoneNoneBenutzerkennung
include_partial_messagesboolFalseTeilweise Nachricht-Streaming-Ereignisse einschließen
fork_sessionboolFalseBeim Fortsetzen mit resume zu einer neuen Sitzungs-ID verzweigen, anstatt die ursprüngliche Sitzung fortzusetzen
agentsdict[str, AgentDefinition] | NoneNoneProgrammgesteuert definierte Subagenten
pluginslist[SdkPluginConfig][]Laden Sie benutzerdefinierte Plugins aus lokalen Pfaden. Siehe Plugins für Details
setting_sourceslist[SettingSource] | NoneNone (keine Einstellungen)Kontrollieren Sie, welche Dateisystem-Einstellungen geladen werden. Wenn weggelassen, werden keine Einstellungen geladen. Hinweis: Muss "project" enthalten, um CLAUDE.md-Dateien zu laden

SystemPromptPreset

Konfiguration für die Verwendung der voreingestellten Systemaufforderung von Claude Code mit optionalen Ergänzungen.
class SystemPromptPreset(TypedDict):
    type: Literal["preset"]
    preset: Literal["claude_code"]
    append: NotRequired[str]
FeldErforderlichBeschreibung
typeJaMuss "preset" sein, um eine voreingestellte Systemaufforderung zu verwenden
presetJaMuss "claude_code" sein, um die Systemaufforderung von Claude Code zu verwenden
appendNeinZusätzliche Anweisungen, die an die voreingestellte Systemaufforderung angehängt werden

SettingSource

Kontrolliert, welche dateisystembasierte Konfigurationsquellen das SDK lädt.
SettingSource = Literal["user", "project", "local"]
WertBeschreibungOrt
"user"Globale Benutzereinstellungen~/.claude/settings.json
"project"Gemeinsame Projekteinstellungen (versionskontrolliert).claude/settings.json
"local"Lokale Projekteinstellungen (gitignoriert).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?

Laden Sie alle Dateisystem-Einstellungen (Legacy-Verhalten):
# Load all settings like SDK v0.0.x did
from claude_agent_sdk import query, ClaudeAgentOptions

async for message in query(
    prompt="Analyze this code",
    options=ClaudeAgentOptions(
        setting_sources=["user", "project", "local"]  # Load all settings
    )
):
    print(message)
Laden Sie nur bestimmte Einstellungsquellen:
# Load only project settings, ignore user and local
async for message in query(
    prompt="Run CI checks",
    options=ClaudeAgentOptions(
        setting_sources=["project"]  # Only .claude/settings.json
    )
):
    print(message)
Test- und CI-Umgebungen:
# Ensure consistent behavior in CI by excluding local settings
async for message in query(
    prompt="Run tests",
    options=ClaudeAgentOptions(
        setting_sources=["project"],  # Only team-shared settings
        permission_mode="bypassPermissions"
    )
):
    print(message)
SDK-only-Anwendungen:
# Define everything programmatically (default behavior)
# No filesystem dependencies - setting_sources defaults to None
async for message in query(
    prompt="Review this PR",
    options=ClaudeAgentOptions(
        # setting_sources=None is the default, no need to specify
        agents={ /* ... */ },
        mcp_servers={ /* ... */ },
        allowed_tools=["Read", "Grep", "Glob"]
    )
):
    print(message)
Laden von CLAUDE.md-Projektanweisungen:
# Load project settings to include CLAUDE.md files
async for message in query(
    prompt="Add a new feature following project conventions",
    options=ClaudeAgentOptions(
        system_prompt={
            "type": "preset",
            "preset": "claude_code"  # Use Claude Code's system prompt
        },
        setting_sources=["project"],  # Required to load CLAUDE.md from project
        allowed_tools=["Read", "Write", "Edit"]
    )
):
    print(message)

Einstellungspriorität

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

AgentDefinition

Konfiguration für einen programmgesteuert 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 verwendet werden soll
toolsNeinArray von zulässigen Tool-Namen. Wenn weggelassen, erbt alle Tools
promptJaDie Systemaufforderung des Agenten
modelNeinModellüberschreibung für diesen Agenten. Wenn weggelassen, verwendet das Hauptmodell

PermissionMode

Berechtigungsmodi zur Kontrolle der Tool-Ausführung.
PermissionMode = Literal[
    "default",           # Standard permission behavior
    "acceptEdits",       # Auto-accept file edits
    "plan",              # Planning mode - no execution
    "bypassPermissions"  # Bypass all permission checks (use with caution)
]

McpSdkServerConfig

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 instance

McpServerConfig

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

McpStdioServerConfig

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

McpSSEServerConfig

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

McpHttpServerConfig

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

SdkPluginConfig

Konfiguration zum Laden von Plugins im SDK.
class SdkPluginConfig(TypedDict):
    type: Literal["local"]
    path: str
FeldTypBeschreibung
typeLiteral["local"]Muss "local" sein (derzeit werden nur lokale Plugins unterstützt)
pathstrAbsoluter oder relativer Pfad zum Plugin-Verzeichnis
Beispiel:
plugins=[
    {"type": "local", "path": "./my-plugin"},
    {"type": "local", "path": "/absolute/path/to/plugin"}
]
Vollständige Informationen zum Erstellen und Verwenden von Plugins finden Sie unter Plugins.

Nachrichtentypen

Message

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

UserMessage

Benutzereingabenachricht.
@dataclass
class UserMessage:
    content: str | list[ContentBlock]

AssistantMessage

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

SystemMessage

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

ResultMessage

Endgültige 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

Inhaltsblocktypen

ContentBlock

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

TextBlock

Textinhaltsblock.
@dataclass
class TextBlock:
    text: str

ThinkingBlock

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

ToolUseBlock

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

ToolResultBlock

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

Fehlertypen

ClaudeSDKError

Basis-Ausnahmeklasse für alle SDK-Fehler.
class ClaudeSDKError(Exception):
    """Base error for Claude SDK."""

CLINotFoundError

Wird ausgelöst, wenn Claude Code CLI nicht installiert oder nicht gefunden ist.
class CLINotFoundError(CLIConnectionError):
    def __init__(self, message: str = "Claude Code not found", cli_path: str | None = None):
        """
        Args:
            message: Error message (default: "Claude Code not found")
            cli_path: Optional path to the CLI that was not found
        """

CLIConnectionError

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

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 das JSON-Parsing fehlschlägt.
class CLIJSONDecodeError(ClaudeSDKError):
    def __init__(self, line: str, original_error: Exception):
        """
        Args:
            line: The line that failed to parse
            original_error: The original JSON decode exception
        """
        self.line = line
        self.original_error = original_error

Hook-Typen

HookEvent

Unterstützte Hook-Ereignistypen. Beachten Sie, dass das Python SDK aufgrund von Setup-Einschränkungen SessionStart-, SessionEnd- und Notification-Hooks nicht unterstützt.
HookEvent = Literal[
    "PreToolUse",      # Called before tool execution
    "PostToolUse",     # Called after tool execution
    "UserPromptSubmit", # Called when user submits a prompt
    "Stop",            # Called when stopping execution
    "SubagentStop",    # Called when a subagent stops
    "PreCompact"       # Called before message compaction
]

HookCallback

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-Nutzungskennung (für Tool-bezogene Hooks)
  • context: Hook-Kontext mit zusätzlichen Informationen
Gibt ein Wörterbuch zurück, das möglicherweise Folgendes enthält:
  • decision: "block", um die Aktion zu blockieren
  • systemMessage: Systemnachricht, die zum Transkript hinzugefügt werden soll
  • hookSpecificOutput: Hook-spezifische Ausgabedaten

HookContext

Kontextinformationen, die an Hook-Callbacks übergeben werden.
@dataclass
class HookContext:
    signal: Any | None = None  # Future: abort signal support

HookMatcher

Konfiguration zum Abgleichen von Hooks mit bestimmten Ereignissen oder Tools.
@dataclass
class HookMatcher:
    matcher: str | None = None        # Tool name or pattern to match (e.g., "Bash", "Write|Edit")
    hooks: list[HookCallback] = field(default_factory=list)  # List of callbacks to execute

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]:
    """Validate and potentially block dangerous bash commands."""
    if input_data['tool_name'] == 'Bash':
        command = input_data['tool_input'].get('command', '')
        if 'rm -rf /' in command:
            return {
                'hookSpecificOutput': {
                    'hookEventName': 'PreToolUse',
                    'permissionDecision': 'deny',
                    'permissionDecisionReason': 'Dangerous command blocked'
                }
            }
    return {}

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

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

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

Tool-Ein-/Ausgabetypen

Dokumentation von Ein-/Ausgabeschemas für alle integrierten Claude Code-Tools. Obwohl das Python SDK diese nicht als Typen exportiert, stellen sie die Struktur von Tool-Ein- und -Ausgaben in Nachrichten dar.

Task

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

Bash

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

Edit

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

Read

Tool-Name: Read Eingabe:
{
    "file_path": str,       # The absolute path to the file to read
    "offset": int | None,   # The line number to start reading from
    "limit": int | None     # The number of lines to read
}
Ausgabe (Textdateien):
{
    "content": str,         # File contents with line numbers
    "total_lines": int,     # Total number of lines in file
    "lines_returned": int   # Lines actually returned
}
Ausgabe (Bilder):
{
    "image": str,       # Base64 encoded image data
    "mime_type": str,   # Image MIME type
    "file_size": int    # File size in bytes
}

Write

Tool-Name: Write Eingabe:
{
    "file_path": str,  # The absolute path to the file to write
    "content": str     # The content to write to the file
}
Ausgabe:
{
    "message": str,        # Success message
    "bytes_written": int,  # Number of bytes written
    "file_path": str       # File path that was written
}

Glob

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

Grep

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

NotebookEdit

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

WebFetch

Tool-Name: WebFetch Eingabe:
{
    "url": str,     # The URL to fetch content from
    "prompt": str   # The prompt to run on the fetched content
}
Ausgabe:
{
    "response": str,           # AI model's response to the prompt
    "url": str,                # URL that was fetched
    "final_url": str | None,   # Final URL after redirects
    "status_code": int | None  # HTTP status code
}

WebSearch

Tool-Name: WebSearch Eingabe:
{
    "query": str,                        # The search query to use
    "allowed_domains": list[str] | None, # Only include results from these domains
    "blocked_domains": list[str] | None  # Never include results from these domains
}
Ausgabe:
{
    "results": [
        {
            "title": str,
            "url": str,
            "snippet": str,
            "metadata": dict | None
        }
    ],
    "total_results": int,
    "query": str
}

TodoWrite

Tool-Name: TodoWrite Eingabe:
{
    "todos": [
        {
            "content": str, # The task description
            "status": "pending" | "in_progress" | "completed",  # Task status
            "activeForm": str                            # Active form of the description
        }
    ]
}
Ausgabe:
{
    "message": str,  # Success message
    "stats": {
        "total": int,
        "pending": int,
        "in_progress": int,
        "completed": int
    }
}

BashOutput

Tool-Name: BashOutput Eingabe:
{
    "bash_id": str,       # The ID of the background shell
    "filter": str | None  # Optional regex to filter output lines
}
Ausgabe:
{
    "output": str, # New output since last check
    "status": "running" | "completed" | "failed",       # Current shell status
    "exitCode": int | None # Exit code when completed
}

KillBash

Tool-Name: KillBash Eingabe:
{
    "shell_id": str  # The ID of the background shell to kill
}
Ausgabe:
{
    "message": str,  # Success message
    "shell_id": str  # ID of the killed shell
}

ExitPlanMode

Tool-Name: ExitPlanMode Eingabe:
{
    "plan": str  # The plan to run by the user for approval
}
Ausgabe:
{
    "message": str,          # Confirmation message
    "approved": bool | None  # Whether user approved the plan
}

ListMcpResources

Tool-Name: ListMcpResources Eingabe:
{
    "server": str | None  # Optional server name to filter resources by
}
Ausgabe:
{
    "resources": [
        {
            "uri": str,
            "name": str,
            "description": str | None,
            "mimeType": str | None,
            "server": str
        }
    ],
    "total": int
}

ReadMcpResource

Tool-Name: ReadMcpResource Eingabe:
{
    "server": str,  # The MCP server name
    "uri": str      # The resource URI to read
}
Ausgabe:
{
    "contents": [
        {
            "uri": str,
            "mimeType": str | None,
            "text": str | None,
            "blob": str | None
        }
    ],
    "server": str
}

Erweiterte Funktionen mit ClaudeSDKClient

Aufbau einer kontinuierlichen Konversationsschnittstelle

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

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

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

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

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

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

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

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

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

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

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

asyncio.run(main())

Verwendung von Hooks zur Verhaltensänderung

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

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

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

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

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

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

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

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

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

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

asyncio.run(main())

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(
            "Create 5 Python files with different sorting algorithms"
        )

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

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

asyncio.run(monitor_progress())

Beispielverwendung

Grundlegende Dateivorgänge (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="Create a Python project structure with setup.py",
        options=options
    ):
        if isinstance(message, AssistantMessage):
            for block in message.content:
                if isinstance(block, ToolUseBlock):
                    print(f"Using tool: {block.name}")

asyncio.run(create_project())

Fehlerbehandlung

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

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

Streaming-Modus mit Client

from claude_agent_sdk import ClaudeSDKClient
import asyncio

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

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

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

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

asyncio.run(interactive_session())

Verwendung von benutzerdefinierten Tools mit ClaudeSDKClient

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

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

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

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

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

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

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

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

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

asyncio.run(main())

Siehe auch