설치

pip install claude-agent-sdk

query()ClaudeSDKClient 중 선택하기

Python SDK는 Claude Code와 상호작용하는 두 가지 방법을 제공합니다:

빠른 비교

기능query()ClaudeSDKClient
세션매번 새 세션 생성동일한 세션 재사용
대화단일 교환동일한 컨텍스트에서 여러 교환
연결자동으로 관리됨수동 제어
스트리밍 입력✅ 지원됨✅ 지원됨
중단❌ 지원되지 않음✅ 지원됨
❌ 지원되지 않음✅ 지원됨
사용자 정의 도구❌ 지원되지 않음✅ 지원됨
대화 계속하기❌ 매번 새 세션✅ 대화 유지
사용 사례일회성 작업지속적인 대화

query() 사용 시기 (매번 새 세션)

최적의 경우:
  • 대화 기록이 필요 없는 일회성 질문
  • 이전 교환의 컨텍스트가 필요 없는 독립적인 작업
  • 간단한 자동화 스크립트
  • 매번 새로 시작하고 싶을 때

ClaudeSDKClient 사용 시기 (지속적인 대화)

최적의 경우:
  • 대화 계속하기 - Claude가 컨텍스트를 기억해야 할 때
  • 후속 질문 - 이전 응답을 기반으로 구축
  • 대화형 애플리케이션 - 채팅 인터페이스, REPL
  • 응답 기반 로직 - 다음 작업이 Claude의 응답에 따라 달라질 때
  • 세션 제어 - 대화 생명주기를 명시적으로 관리

함수

query()

Claude Code와의 각 상호작용을 위해 새 세션을 생성합니다. 메시지가 도착할 때 메시지를 생성하는 비동기 반복자를 반환합니다. query()에 대한 각 호출은 이전 상호작용의 메모리 없이 새로 시작됩니다.
async def query(
    *,
    prompt: str | AsyncIterable[dict[str, Any]],
    options: ClaudeAgentOptions | None = None
) -> AsyncIterator[Message]

매개변수

매개변수타입설명
promptstr | AsyncIterable[dict]문자열 또는 스트리밍 모드용 비동기 반복 가능 객체로서의 입력 프롬프트
optionsClaudeAgentOptions | None선택적 구성 객체 (None인 경우 ClaudeAgentOptions()로 기본값 설정)

반환

대화에서 메시지를 생성하는 AsyncIterator[Message]를 반환합니다.

예제 - 옵션 포함


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()

타입 안전성을 갖춘 MCP 도구를 정의하기 위한 데코레이터입니다.
def tool(
    name: str,
    description: str,
    input_schema: type | dict[str, Any]
) -> Callable[[Callable[[Any], Awaitable[dict[str, Any]]]], SdkMcpTool[Any]]

매개변수

매개변수타입설명
namestr도구의 고유 식별자
descriptionstr도구가 수행하는 작업에 대한 사람이 읽을 수 있는 설명
input_schematype | dict[str, Any]도구의 입력 매개변수를 정의하는 스키마 (아래 참조)

입력 스키마 옵션

  1. 간단한 타입 매핑 (권장):
    {"text": str, "count": int, "enabled": bool}
    
  2. JSON 스키마 형식 (복잡한 검증용):
    {
        "type": "object",
        "properties": {
            "text": {"type": "string"},
            "count": {"type": "integer", "minimum": 0}
        },
        "required": ["text"]
    }
    

반환

도구 구현을 래핑하고 SdkMcpTool 인스턴스를 반환하는 데코레이터 함수입니다.

예제

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()

Python 애플리케이션 내에서 실행되는 프로세스 내 MCP 서버를 생성합니다.
def create_sdk_mcp_server(
    name: str,
    version: str = "1.0.0",
    tools: list[SdkMcpTool[Any]] | None = None
) -> McpSdkServerConfig

매개변수

매개변수타입기본값설명
namestr-서버의 고유 식별자
versionstr"1.0.0"서버 버전 문자열
toolslist[SdkMcpTool[Any]] | NoneNone@tool 데코레이터로 생성된 도구 함수 목록

반환

ClaudeAgentOptions.mcp_servers에 전달할 수 있는 McpSdkServerConfig 객체를 반환합니다.

예제

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"]
)

클래스

ClaudeSDKClient

여러 교환에 걸쳐 대화 세션을 유지합니다. 이것은 TypeScript SDK의 query() 함수가 내부적으로 작동하는 방식의 Python 동등물입니다 - 대화를 계속할 수 있는 클라이언트 객체를 생성합니다.

주요 기능

  • 세션 연속성: 여러 query() 호출에 걸쳐 대화 컨텍스트 유지
  • 동일한 대화: Claude가 세션의 이전 메시지를 기억함
  • 중단 지원: Claude 실행 중에 중지 가능
  • 명시적 생명주기: 세션 시작 및 종료 시기를 제어
  • 응답 기반 흐름: 응답에 반응하고 후속 조치 전송 가능
  • 사용자 정의 도구 및 훅: @tool 데코레이터로 생성된 사용자 정의 도구 및 훅 지원
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

메서드

메서드설명
__init__(options)선택적 구성으로 클라이언트 초기화
connect(prompt)선택적 초기 프롬프트 또는 메시지 스트림으로 Claude에 연결
query(prompt, session_id)스트리밍 모드에서 새 요청 전송
receive_messages()Claude의 모든 메시지를 비동기 반복자로 수신
receive_response()ResultMessage를 포함하여 메시지 수신
interrupt()중단 신호 전송 (스트리밍 모드에서만 작동)
disconnect()Claude에서 연결 해제

컨텍스트 관리자 지원

클라이언트는 자동 연결 관리를 위해 비동기 컨텍스트 관리자로 사용할 수 있습니다:
async with ClaudeSDKClient() as client:
    await client.query("Hello Claude")
    async for message in client.receive_response():
        print(message)
중요: 메시지를 반복할 때 조기에 종료하기 위해 break를 사용하지 마세요. 이는 asyncio 정리 문제를 야기할 수 있습니다. 대신 반복이 자연스럽게 완료되도록 하거나 필요한 것을 찾았을 때를 추적하기 위해 플래그를 사용하세요.

예제 - 대화 계속하기

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())

예제 - 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())

예제 - 중단 사용

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())

예제 - 고급 권한 제어

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())

타입

SdkMcpTool

@tool 데코레이터로 생성된 SDK MCP 도구의 정의입니다.
@dataclass
class SdkMcpTool(Generic[T]):
    name: str
    description: str
    input_schema: type[T] | dict[str, Any]
    handler: Callable[[T], Awaitable[dict[str, Any]]]
속성타입설명
namestr도구의 고유 식별자
descriptionstr사람이 읽을 수 있는 설명
input_schematype[T] | dict[str, Any]입력 검증용 스키마
handlerCallable[[T], Awaitable[dict[str, Any]]]도구 실행을 처리하는 비동기 함수

ClaudeAgentOptions

Claude Code 쿼리를 위한 구성 데이터클래스입니다.
@dataclass
class ClaudeAgentOptions:
    allowed_tools: list[str] = field(default_factory=list)
    system_prompt: str | SystemPromptPreset | None = None
    mcp_servers: dict[str, McpServerConfig] | str | Path = field(default_factory=dict)
    permission_mode: PermissionMode | None = None
    continue_conversation: bool = False
    resume: str | None = None
    max_turns: int | None = None
    disallowed_tools: list[str] = field(default_factory=list)
    model: str | None = None
    permission_prompt_tool_name: str | None = None
    cwd: str | Path | None = None
    settings: str | None = None
    add_dirs: list[str | Path] = field(default_factory=list)
    env: dict[str, str] = field(default_factory=dict)
    extra_args: dict[str, str | None] = field(default_factory=dict)
    max_buffer_size: int | None = None
    debug_stderr: Any = sys.stderr  # Deprecated
    stderr: Callable[[str], None] | None = None
    can_use_tool: CanUseTool | None = None
    hooks: dict[HookEvent, list[HookMatcher]] | None = None
    user: str | None = None
    include_partial_messages: bool = False
    fork_session: bool = False
    agents: dict[str, AgentDefinition] | None = None
    setting_sources: list[SettingSource] | None = None
속성타입기본값설명
allowed_toolslist[str][]허용된 도구 이름 목록
system_promptstr | SystemPromptPreset | NoneNone시스템 프롬프트 구성. 사용자 정의 프롬프트의 경우 문자열을 전달하거나 Claude Code의 시스템 프롬프트의 경우 {"type": "preset", "preset": "claude_code"}를 사용합니다. 프리셋을 확장하려면 "append"를 추가합니다
mcp_serversdict[str, McpServerConfig] | str | Path{}MCP 서버 구성 또는 구성 파일 경로
permission_modePermissionMode | NoneNone도구 사용을 위한 권한 모드
continue_conversationboolFalse가장 최근 대화 계속하기
resumestr | NoneNone재개할 세션 ID
max_turnsint | NoneNone최대 대화 턴
disallowed_toolslist[str][]허용되지 않는 도구 이름 목록
modelstr | NoneNone사용할 Claude 모델
permission_prompt_tool_namestr | NoneNone권한 프롬프트용 MCP 도구 이름
cwdstr | Path | NoneNone현재 작업 디렉토리
settingsstr | NoneNone설정 파일 경로
add_dirslist[str | Path][]Claude가 액세스할 수 있는 추가 디렉토리
envdict[str, str]{}환경 변수
extra_argsdict[str, str | None]{}CLI에 직접 전달할 추가 CLI 인수
max_buffer_sizeint | NoneNoneCLI stdout 버퍼링 시 최대 바이트
debug_stderrAnysys.stderr더 이상 사용되지 않음 - 디버그 출력용 파일 유사 객체. 대신 stderr 콜백 사용
stderrCallable[[str], None] | NoneNoneCLI의 stderr 출력을 위한 콜백 함수
can_use_toolCanUseTool | NoneNone도구 권한 콜백 함수
hooksdict[HookEvent, list[HookMatcher]] | NoneNone이벤트 가로채기를 위한 훅 구성
userstr | NoneNone사용자 식별자
include_partial_messagesboolFalse부분 메시지 스트리밍 이벤트 포함
fork_sessionboolFalseresume으로 재개할 때 원본 세션을 계속하는 대신 새 세션 ID로 포크
agentsdict[str, AgentDefinition] | NoneNone프로그래밍 방식으로 정의된 서브에이전트
pluginslist[SdkPluginConfig][]로컬 경로에서 사용자 정의 플러그인 로드. 자세한 내용은 플러그인을 참조하세요
setting_sourceslist[SettingSource] | NoneNone (설정 없음)로드할 파일 시스템 설정을 제어합니다. 생략하면 설정이 로드되지 않습니다. 참고: CLAUDE.md 파일을 로드하려면 "project"를 포함해야 합니다

SystemPromptPreset

선택적 추가 사항과 함께 Claude Code의 프리셋 시스템 프롬프트 사용을 위한 구성입니다.
class SystemPromptPreset(TypedDict):
    type: Literal["preset"]
    preset: Literal["claude_code"]
    append: NotRequired[str]
필드필수설명
type프리셋 시스템 프롬프트를 사용하려면 "preset"이어야 합니다
presetClaude Code의 시스템 프롬프트를 사용하려면 "claude_code"이어야 합니다
append아니오프리셋 시스템 프롬프트에 추가할 추가 지침

SettingSource

SDK가 로드하는 파일 시스템 기반 구성 소스를 제어합니다.
SettingSource = Literal["user", "project", "local"]
설명위치
"user"전역 사용자 설정~/.claude/settings.json
"project"공유 프로젝트 설정 (버전 제어됨).claude/settings.json
"local"로컬 프로젝트 설정 (gitignored).claude/settings.local.json

기본 동작

setting_sources생략되거나 **None**일 때 SDK는 파일 시스템 설정을 로드하지 않습니다. 이는 SDK 애플리케이션에 격리를 제공합니다.

setting_sources를 사용하는 이유는 무엇입니까?

모든 파일 시스템 설정 로드 (레거시 동작):
# 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)
특정 설정 소스만 로드:
# 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)
테스트 및 CI 환경:
# Ensure consistent behavior in CI by excluding local settings
async for message in query(
    prompt="Run tests",
    options=ClaudeAgentOptions(
        setting_sources=["project"],  # Only team-shared settings
        permission_mode="bypassPermissions"
    )
):
    print(message)
SDK 전용 애플리케이션:
# Define everything programmatically (default behavior)
# No filesystem dependencies - setting_sources defaults to None
async for message in query(
    prompt="Review this PR",
    options=ClaudeAgentOptions(
        # setting_sources=None is the default, no need to specify
        agents={ /* ... */ },
        mcp_servers={ /* ... */ },
        allowed_tools=["Read", "Grep", "Glob"]
    )
):
    print(message)
CLAUDE.md 프로젝트 지침 로드:
# Load project settings to include CLAUDE.md files
async for message in query(
    prompt="Add a new feature following project conventions",
    options=ClaudeAgentOptions(
        system_prompt={
            "type": "preset",
            "preset": "claude_code"  # Use Claude Code's system prompt
        },
        setting_sources=["project"],  # Required to load CLAUDE.md from project
        allowed_tools=["Read", "Write", "Edit"]
    )
):
    print(message)

설정 우선순위

여러 소스가 로드될 때 설정은 이 우선순위(높음에서 낮음)로 병합됩니다:
  1. 로컬 설정 (.claude/settings.local.json)
  2. 프로젝트 설정 (.claude/settings.json)
  3. 사용자 설정 (~/.claude/settings.json)
프로그래밍 방식의 옵션 (예: agents, allowed_tools)은 항상 파일 시스템 설정을 재정의합니다.

AgentDefinition

프로그래밍 방식으로 정의된 서브에이전트의 구성입니다.
@dataclass
class AgentDefinition:
    description: str
    prompt: str
    tools: list[str] | None = None
    model: Literal["sonnet", "opus", "haiku", "inherit"] | None = None
필드필수설명
description이 에이전트를 사용할 시기에 대한 자연어 설명
tools아니오허용된 도구 이름 배열. 생략하면 모든 도구를 상속합니다
prompt에이전트의 시스템 프롬프트
model아니오이 에이전트의 모델 재정의. 생략하면 주 모델을 사용합니다

PermissionMode

도구 실행을 제어하기 위한 권한 모드입니다.
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

create_sdk_mcp_server()로 생성된 SDK MCP 서버의 구성입니다.
class McpSdkServerConfig(TypedDict):
    type: Literal["sdk"]
    name: str
    instance: Any  # MCP Server instance

McpServerConfig

MCP 서버 구성의 합집합 타입입니다.
McpServerConfig = McpStdioServerConfig | McpSSEServerConfig | McpHttpServerConfig | McpSdkServerConfig

McpStdioServerConfig

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

McpSSEServerConfig

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

McpHttpServerConfig

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

SdkPluginConfig

SDK에서 플러그인을 로드하기 위한 구성입니다.
class SdkPluginConfig(TypedDict):
    type: Literal["local"]
    path: str
필드타입설명
typeLiteral["local"]"local"이어야 합니다 (현재 로컬 플러그인만 지원됨)
pathstr플러그인 디렉토리의 절대 또는 상대 경로
예제:
plugins=[
    {"type": "local", "path": "./my-plugin"},
    {"type": "local", "path": "/absolute/path/to/plugin"}
]
플러그인 생성 및 사용에 대한 완전한 정보는 플러그인을 참조하세요.

메시지 타입

Message

모든 가능한 메시지의 합집합 타입입니다.
Message = UserMessage | AssistantMessage | SystemMessage | ResultMessage

UserMessage

사용자 입력 메시지입니다.
@dataclass
class UserMessage:
    content: str | list[ContentBlock]

AssistantMessage

콘텐츠 블록이 있는 어시스턴트 응답 메시지입니다.
@dataclass
class AssistantMessage:
    content: list[ContentBlock]
    model: str

SystemMessage

메타데이터가 있는 시스템 메시지입니다.
@dataclass
class SystemMessage:
    subtype: str
    data: dict[str, Any]

ResultMessage

비용 및 사용 정보가 있는 최종 결과 메시지입니다.
@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

콘텐츠 블록 타입

ContentBlock

모든 콘텐츠 블록의 합집합 타입입니다.
ContentBlock = TextBlock | ThinkingBlock | ToolUseBlock | ToolResultBlock

TextBlock

텍스트 콘텐츠 블록입니다.
@dataclass
class TextBlock:
    text: str

ThinkingBlock

사고 콘텐츠 블록입니다 (사고 능력이 있는 모델용).
@dataclass
class ThinkingBlock:
    thinking: str
    signature: str

ToolUseBlock

도구 사용 요청 블록입니다.
@dataclass
class ToolUseBlock:
    id: str
    name: str
    input: dict[str, Any]

ToolResultBlock

도구 실행 결과 블록입니다.
@dataclass
class ToolResultBlock:
    tool_use_id: str
    content: str | list[dict[str, Any]] | None = None
    is_error: bool | None = None

오류 타입

ClaudeSDKError

모든 SDK 오류의 기본 예외 클래스입니다.
class ClaudeSDKError(Exception):
    """Base error for Claude SDK."""

CLINotFoundError

Claude Code CLI가 설치되지 않았거나 찾을 수 없을 때 발생합니다.
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

Claude Code 연결이 실패할 때 발생합니다.
class CLIConnectionError(ClaudeSDKError):
    """Failed to connect to Claude Code."""

ProcessError

Claude Code 프로세스가 실패할 때 발생합니다.
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

JSON 파싱이 실패할 때 발생합니다.
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

훅 타입

HookEvent

지원되는 훅 이벤트 타입입니다. 설정 제한으로 인해 Python SDK는 SessionStart, SessionEnd 및 Notification 훅을 지원하지 않습니다.
HookEvent = Literal[
    "PreToolUse",      # Called before tool execution
    "PostToolUse",     # Called after tool execution
    "UserPromptSubmit", # Called when user submits a prompt
    "Stop",            # Called when stopping execution
    "SubagentStop",    # Called when a subagent stops
    "PreCompact"       # Called before message compaction
]

HookCallback

훅 콜백 함수의 타입 정의입니다.
HookCallback = Callable[
    [dict[str, Any], str | None, HookContext],
    Awaitable[dict[str, Any]]
]
매개변수:
  • input_data: 훅 특정 입력 데이터 (훅 문서 참조)
  • tool_use_id: 선택적 도구 사용 식별자 (도구 관련 훅용)
  • context: 추가 정보가 있는 훅 컨텍스트
다음을 포함할 수 있는 딕셔너리를 반환합니다:
  • decision: 작업을 차단하려면 "block"
  • systemMessage: 트랜스크립트에 추가할 시스템 메시지
  • hookSpecificOutput: 훅 특정 출력 데이터

HookContext

훅 콜백에 전달되는 컨텍스트 정보입니다.
@dataclass
class HookContext:
    signal: Any | None = None  # Future: abort signal support

HookMatcher

특정 이벤트 또는 도구에 훅을 일치시키기 위한 구성입니다.
@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

훅 사용 예제

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)

도구 입력/출력 타입

모든 기본 제공 Claude Code 도구의 입력/출력 스키마 문서입니다. Python SDK는 이를 타입으로 내보내지 않지만 메시지의 도구 입력 및 출력 구조를 나타냅니다.

Task

도구 이름: Task 입력:
{
    "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
}
출력:
{
    "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

도구 이름: Bash 입력:
{
    "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
}
출력:
{
    "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

도구 이름: Edit 입력:
{
    "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)
}
출력:
{
    "message": str,      # Confirmation message
    "replacements": int, # Number of replacements made
    "file_path": str     # File path that was edited
}

Read

도구 이름: Read 입력:
{
    "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
}
출력 (텍스트 파일):
{
    "content": str,         # File contents with line numbers
    "total_lines": int,     # Total number of lines in file
    "lines_returned": int   # Lines actually returned
}
출력 (이미지):
{
    "image": str,       # Base64 encoded image data
    "mime_type": str,   # Image MIME type
    "file_size": int    # File size in bytes
}

Write

도구 이름: Write 입력:
{
    "file_path": str,  # The absolute path to the file to write
    "content": str     # The content to write to the file
}
출력:
{
    "message": str,        # Success message
    "bytes_written": int,  # Number of bytes written
    "file_path": str       # File path that was written
}

Glob

도구 이름: Glob 입력:
{
    "pattern": str,       # The glob pattern to match files against
    "path": str | None    # The directory to search in (defaults to cwd)
}
출력:
{
    "matches": list[str],  # Array of matching file paths
    "count": int,          # Number of matches found
    "search_path": str     # Search directory used
}

Grep

도구 이름: Grep 입력:
{
    "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
}
출력 (content 모드):
{
    "matches": [
        {
            "file": str,
            "line_number": int | None,
            "line": str,
            "before_context": list[str] | None,
            "after_context": list[str] | None
        }
    ],
    "total_matches": int
}
출력 (files_with_matches 모드):
{
    "files": list[str],  # Files containing matches
    "count": int         # Number of files with matches
}

NotebookEdit

도구 이름: NotebookEdit 입력:
{
    "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
}
출력:
{
    "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

도구 이름: WebFetch 입력:
{
    "url": str,     # The URL to fetch content from
    "prompt": str   # The prompt to run on the fetched content
}
출력:
{
    "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

도구 이름: WebSearch 입력:
{
    "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
}
출력:
{
    "results": [
        {
            "title": str,
            "url": str,
            "snippet": str,
            "metadata": dict | None
        }
    ],
    "total_results": int,
    "query": str
}

TodoWrite

도구 이름: TodoWrite 입력:
{
    "todos": [
        {
            "content": str, # The task description
            "status": "pending" | "in_progress" | "completed",  # Task status
            "activeForm": str                            # Active form of the description
        }
    ]
}
출력:
{
    "message": str,  # Success message
    "stats": {
        "total": int,
        "pending": int,
        "in_progress": int,
        "completed": int
    }
}

BashOutput

도구 이름: BashOutput 입력:
{
    "bash_id": str,       # The ID of the background shell
    "filter": str | None  # Optional regex to filter output lines
}
출력:
{
    "output": str, # New output since last check
    "status": "running" | "completed" | "failed",       # Current shell status
    "exitCode": int | None # Exit code when completed
}

KillBash

도구 이름: KillBash 입력:
{
    "shell_id": str  # The ID of the background shell to kill
}
출력:
{
    "message": str,  # Success message
    "shell_id": str  # ID of the killed shell
}

ExitPlanMode

도구 이름: ExitPlanMode 입력:
{
    "plan": str  # The plan to run by the user for approval
}
출력:
{
    "message": str,          # Confirmation message
    "approved": bool | None  # Whether user approved the plan
}

ListMcpResources

도구 이름: ListMcpResources 입력:
{
    "server": str | None  # Optional server name to filter resources by
}
출력:
{
    "resources": [
        {
            "uri": str,
            "name": str,
            "description": str | None,
            "mimeType": str | None,
            "server": str
        }
    ],
    "total": int
}

ReadMcpResource

도구 이름: ReadMcpResource 입력:
{
    "server": str,  # The MCP server name
    "uri": str      # The resource URI to read
}
출력:
{
    "contents": [
        {
            "uri": str,
            "mimeType": str | None,
            "text": str | None,
            "blob": str | None
        }
    ],
    "server": str
}

ClaudeSDKClient를 사용한 고급 기능

지속적인 대화 인터페이스 구축

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())

동작 수정을 위해 훅 사용

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())

실시간 진행 상황 모니터링

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())

사용 예제

기본 파일 작업 (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())

오류 처리

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}")

클라이언트를 사용한 스트리밍 모드

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())

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())

참고 항목