설치

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="당신은 전문 Python 개발자입니다",
        permission_mode='acceptEdits',
        cwd="/home/user/project"
    )

    async for message in query(
        prompt="Python 웹 서버를 만들어주세요",
        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", "사용자에게 인사하기", {"name": str})
async def greet(args: dict[str, Any]) -> dict[str, Any]:
    return {
        "content": [{
            "type": "text",
            "text": f"안녕하세요, {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", "두 숫자 더하기", {"a": float, "b": float})
async def add(args):
    return {
        "content": [{
            "type": "text",
            "text": f"합계: {args['a'] + args['b']}"
        }]
    }

@tool("multiply", "두 숫자 곱하기", {"a": float, "b": float})
async def multiply(args):
    return {
        "content": [{
            "type": "text",
            "text": f"곱: {args['a'] * args['b']}"
        }]
    }

calculator = create_sdk_mcp_server(
    name="calculator",
    version="2.0.0",
    tools=[add, multiply]  # 데코레이트된 함수 전달
)

# 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("안녕하세요 Claude")
    async for message in client.receive_response():
        print(message)
중요: 메시지를 반복할 때, asyncio 정리 문제를 일으킬 수 있으므로 조기 종료를 위해 break를 사용하지 마세요. 대신 반복이 자연스럽게 완료되도록 하거나 플래그를 사용하여 필요한 것을 찾았을 때를 추적하세요.

예제 - 대화 계속하기

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

async def main():
    async with ClaudeSDKClient() as client:
        # 첫 번째 질문
        await client.query("프랑스의 수도는 어디인가요?")
        
        # 응답 처리
        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}")
        
        # 후속 질문 - Claude가 이전 컨텍스트를 기억함
        await client.query("그 도시의 인구는 얼마나 되나요?")
        
        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}")
        
        # 또 다른 후속 질문 - 여전히 동일한 대화에서
        await client.query("그곳의 유명한 랜드마크는 무엇인가요?")
        
        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():
    """메시지를 동적으로 생성합니다."""
    yield {"type": "text", "text": "다음 데이터를 분석해주세요:"}
    await asyncio.sleep(0.5)
    yield {"type": "text", "text": "온도: 25°C"}
    await asyncio.sleep(0.5)
    yield {"type": "text", "text": "습도: 60%"}
    await asyncio.sleep(0.5)
    yield {"type": "text", "text": "어떤 패턴을 보시나요?"}

async def main():
    async with ClaudeSDKClient() as client:
        # Claude에 입력 스트리밍
        await client.query(message_stream())
        
        # 응답 처리
        async for message in client.receive_response():
            print(message)
        
        # 동일한 세션에서 후속 조치
        await client.query("이 수치들에 대해 걱정해야 할까요?")
        
        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:
        # 장시간 실행되는 작업 시작
        await client.query("1부터 100까지 천천히 세어주세요")
        
        # 잠시 실행하도록 둠
        await asyncio.sleep(2)
        
        # 작업 중단
        await client.interrupt()
        print("작업이 중단되었습니다!")
        
        # 새 명령 전송
        await client.query("대신 그냥 안녕이라고 말해주세요")
        
        async for message in client.receive_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
):
    """도구 권한에 대한 사용자 정의 로직."""

    # 시스템 디렉토리에 대한 쓰기 차단
    if tool_name == "Write" and input_data.get("file_path", "").startswith("/system/"):
        return {
            "behavior": "deny",
            "message": "시스템 디렉토리 쓰기가 허용되지 않습니다",
            "interrupt": True
        }

    # 민감한 파일 작업 리디렉션
    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}
        }

    # 나머지는 모두 허용
    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("시스템 구성 파일을 업데이트해주세요")
        
        async for message in client.receive_response():
            # 대신 샌드박스 경로를 사용할 것입니다
            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)
    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)
속성타입기본값설명
allowed_toolslist[str][]허용된 도구 이름 목록
max_thinking_tokensint8000사고 과정을 위한 최대 토큰 수
system_promptstr | NoneNone시스템 프롬프트 구성. 사용자 정의 프롬프트의 경우 문자열을 전달하거나, Claude Code의 시스템 프롬프트에 대해 사전 설정 형식을 사용
mcp_serversdict[str, McpServerConfig] | str | Path{}MCP 서버 구성 또는 구성 파일 경로
permission_modePermissionMode | NoneNone도구 사용을 위한 권한 모드
continue_conversationboolFalse가장 최근 대화 계속하기
resumestr | NoneNone재개할 세션 ID
fork_sessionboolFalseresume으로 재개할 때, 원래 세션을 계속하는 대신 새 세션 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가 액세스할 수 있는 추가 디렉토리
extra_argsdict[str, str | None]{}CLI에 직접 전달할 추가 CLI 인수
can_use_toolCanUseTool | NoneNone도구 권한 콜백 함수
hooksdict[HookEvent, list[HookMatcher]] | NoneNone이벤트 가로채기를 위한 훅 구성
agentsdict[str, AgentDefinition] | NoneNone프로그래밍 방식으로 정의된 하위 에이전트
setting_sourceslist[SettingSource] | NoneNone (설정 없음)SDK가 설정을 로드할 파일시스템 설정을 제어합니다. 생략하면 설정이 로드되지 않습니다

SettingSource

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

기본 동작

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

setting_sources를 사용하는 이유?

모든 파일시스템 설정 로드 (레거시 동작):
# SDK v0.0.x처럼 모든 설정 로드
from claude_agent_sdk import query, ClaudeAgentOptions

async for message in query(
    prompt="이 코드를 분석해주세요",
    options=ClaudeAgentOptions(
        setting_sources=["user", "project", "local"]  # 모든 설정 로드
    )
):
    print(message)
특정 설정 소스만 로드:
# 프로젝트 설정만 로드, 사용자 및 로컬 무시
async for message in query(
    prompt="CI 검사 실행",
    options=ClaudeAgentOptions(
        setting_sources=["project"]  # .claude/settings.json만
    )
):
    print(message)
테스트 및 CI 환경:
# 로컬 설정을 제외하여 CI에서 일관된 동작 보장
async for message in query(
    prompt="테스트 실행",
    options=ClaudeAgentOptions(
        setting_sources=["project"],  # 팀 공유 설정만
        permission_mode="bypassPermissions"
    )
):
    print(message)
SDK 전용 애플리케이션:
# 모든 것을 프로그래밍 방식으로 정의 (기본 동작)
# 파일시스템 종속성 없음 - setting_sources는 기본적으로 None
async for message in query(
    prompt="이 PR을 검토해주세요",
    options=ClaudeAgentOptions(
        # setting_sources=None이 기본값이므로 지정할 필요 없음
        agents={ /* ... */ },
        mcp_servers={ /* ... */ },
        allowed_tools=["Read", "Grep", "Glob"]
    )
):
    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",           # 표준 권한 동작
    "acceptEdits",       # 파일 편집 자동 승인
    "plan",              # 계획 모드 - 실행 없음
    "bypassPermissions"  # 모든 권한 검사 우회 (주의해서 사용)
]

McpSdkServerConfig

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

McpServerConfig

MCP 서버 구성을 위한 유니온 타입입니다.
McpServerConfig = McpStdioServerConfig | McpSSEServerConfig | McpHttpServerConfig | McpSdkServerConfig

McpStdioServerConfig

class McpStdioServerConfig(TypedDict):
    type: NotRequired[Literal["stdio"]]  # 하위 호환성을 위해 선택사항
    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]]

메시지 타입

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):
    """Claude SDK의 기본 오류."""

CLINotFoundError

Claude Code CLI가 설치되지 않았거나 찾을 수 없을 때 발생합니다.
class CLINotFoundError(CLIConnectionError):
    def __init__(self, message: str = "Claude Code를 찾을 수 없습니다", cli_path: str | None = None):
        """
        Args:
            message: 오류 메시지 (기본값: "Claude Code를 찾을 수 없습니다")
            cli_path: 찾을 수 없는 CLI의 선택적 경로
        """

CLIConnectionError

Claude Code에 대한 연결이 실패할 때 발생합니다.
class CLIConnectionError(ClaudeSDKError):
    """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: 파싱에 실패한 줄
            original_error: 원래 JSON 디코드 예외
        """
        self.line = line
        self.original_error = original_error

훅 타입

HookEvent

지원되는 훅 이벤트 타입입니다. 설정 제한으로 인해 Python SDK는 SessionStart, SessionEnd, Notification 훅을 지원하지 않습니다.
HookEvent = Literal[
    "PreToolUse",      # 도구 실행 전 호출
    "PostToolUse",     # 도구 실행 후 호출
    "UserPromptSubmit", # 사용자가 프롬프트를 제출할 때 호출
    "Stop",            # 실행을 중지할 때 호출
    "SubagentStop",    # 하위 에이전트가 중지할 때 호출
    "PreCompact"       # 메시지 압축 전 호출
]

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  # 미래: 중단 신호 지원

HookMatcher

특정 이벤트나 도구에 훅을 매칭하기 위한 구성입니다.
@dataclass
class HookMatcher:
    matcher: str | None = None        # 매칭할 도구 이름 또는 패턴 (예: "Bash", "Write|Edit")
    hooks: list[HookCallback] = field(default_factory=list)  # 실행할 콜백 목록

훅 사용 예제

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]:
    """위험한 bash 명령을 검증하고 잠재적으로 차단합니다."""
    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': '위험한 명령이 차단되었습니다'
                }
            }
    return {}

async def log_tool_use(
    input_data: dict[str, Any],
    tool_use_id: str | None,
    context: HookContext
) -> dict[str, Any]:
    """감사를 위해 모든 도구 사용을 로그합니다."""
    print(f"사용된 도구: {input_data.get('tool_name')}")
    return {}

options = ClaudeAgentOptions(
    hooks={
        'PreToolUse': [
            HookMatcher(matcher='Bash', hooks=[validate_bash_command]),
            HookMatcher(hooks=[log_tool_use])  # 모든 도구에 적용
        ],
        'PostToolUse': [
            HookMatcher(hooks=[log_tool_use])
        ]
    }
)

async for message in query(
    prompt="이 코드베이스를 분석해주세요",
    options=options
):
    print(message)

도구 입력/출력 타입

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

Task

도구 이름: Task 입력:
{
    "description": str,      # 작업에 대한 짧은 (3-5단어) 설명
    "prompt": str,           # 에이전트가 수행할 작업
    "subagent_type": str     # 사용할 전문 에이전트 타입
}
출력:
{
    "result": str,                    # 하위 에이전트의 최종 결과
    "usage": dict | None,             # 토큰 사용량 통계
    "total_cost_usd": float | None,  # USD 총 비용
    "duration_ms": int | None         # 실행 시간 (밀리초)
}

Bash

도구 이름: Bash 입력:
{
    "command": str,                  # 실행할 명령
    "timeout": int | None,           # 선택적 타임아웃 (밀리초, 최대 600000)
    "description": str | None,       # 명확하고 간결한 설명 (5-10단어)
    "run_in_background": bool | None # 백그라운드에서 실행하려면 true로 설정
}
출력:
{
    "output": str,              # stdout과 stderr 출력 결합
    "exitCode": int,            # 명령의 종료 코드
    "killed": bool | None,      # 타임아웃으로 인해 명령이 종료되었는지 여부
    "shellId": str | None       # 백그라운드 프로세스용 셸 ID
}

Edit

도구 이름: Edit 입력:
{
    "file_path": str,           # 수정할 파일의 절대 경로
    "old_string": str,          # 교체할 텍스트
    "new_string": str,          # 교체할 텍스트
    "replace_all": bool | None  # 모든 발생을 교체 (기본값 False)
}
출력:
{
    "message": str,      # 확인 메시지
    "replacements": int, # 수행된 교체 수
    "file_path": str     # 편집된 파일 경로
}

MultiEdit

도구 이름: MultiEdit 입력:
{
    "file_path": str,     # 수정할 파일의 절대 경로
    "edits": [            # 편집 작업 배열
        {
            "old_string": str,          # 교체할 텍스트
            "new_string": str,          # 교체할 텍스트
            "replace_all": bool | None  # 모든 발생을 교체
        }
    ]
}
출력:
{
    "message": str,       # 성공 메시지
    "edits_applied": int, # 적용된 총 편집 수
    "file_path": str      # 편집된 파일 경로
}

Read

도구 이름: Read 입력:
{
    "file_path": str,       # 읽을 파일의 절대 경로
    "offset": int | None,   # 읽기를 시작할 줄 번호
    "limit": int | None     # 읽을 줄 수
}
출력 (텍스트 파일):
{
    "content": str,         # 줄 번호가 있는 파일 내용
    "total_lines": int,     # 파일의 총 줄 수
    "lines_returned": int   # 실제로 반환된 줄 수
}
출력 (이미지):
{
    
    "image": str,       # Base64 인코딩된 이미지 데이터
    "mime_type": str,   # 이미지 MIME 타입
    "file_size": int    # 파일 크기 (바이트)
}

Write

도구 이름: Write 입력:
{
    "file_path": str,  # 쓸 파일의 절대 경로
    "content": str     # 파일에 쓸 내용
}
출력:
{
    "message": str,        # 성공 메시지
    "bytes_written": int,  # 쓰여진 바이트 수
    "file_path": str       # 쓰여진 파일 경로
}

Glob

도구 이름: Glob 입력:
{
    "pattern": str,       # 파일을 매칭할 glob 패턴
    "path": str | None    # 검색할 디렉토리 (기본값은 cwd)
}
출력:
{
    "matches": list[str],  # 매칭되는 파일 경로 배열
    "count": int,          # 찾은 매치 수
    "search_path": str     # 사용된 검색 디렉토리
}

Grep

도구 이름: Grep 입력:
{
    "pattern": str,                    # 정규 표현식 패턴
    "path": str | None,                # 검색할 파일 또는 디렉토리
    "glob": str | None,                # 파일을 필터링할 glob 패턴
    "type": str | None,                # 검색할 파일 타입
    "output_mode": str | None,         # "content", "files_with_matches", 또는 "count"
    "-i": bool | None,                 # 대소문자 구분 없는 검색
    "-n": bool | None,                 # 줄 번호 표시
    "-B": int | None,                  # 각 매치 전에 표시할 줄 수
    "-A": int | None,                  # 각 매치 후에 표시할 줄 수
    "-C": int | None,                  # 전후에 표시할 줄 수
    "head_limit": int | None,          # 출력을 첫 N줄/항목으로 제한
    "multiline": bool | None           # 멀티라인 모드 활성화
}
출력 (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],  # 매치를 포함하는 파일들
    "count": int         # 매치가 있는 파일 수
}

NotebookEdit

도구 이름: NotebookEdit 입력:
{
    "notebook_path": str,                     # Jupyter 노트북의 절대 경로
    "cell_id": str | None,                    # 편집할 셀의 ID
    "new_source": str,                        # 셀의 새 소스
    "cell_type": "code" | "markdown" | None,  # 셀의 타입
    "edit_mode": "replace" | "insert" | "delete" | None  # 편집 작업 타입
}
출력:
{
    "message": str, # 성공 메시지
    "edit_type": "replaced" | "inserted" | "deleted",  # 수행된 편집 타입
    "cell_id": str | None,                       # 영향을 받은 셀 ID
    "total_cells": int                           # 편집 후 노트북의 총 셀 수
}

WebFetch

도구 이름: WebFetch 입력:
{
    "url": str,     # 콘텐츠를 가져올 URL
    "prompt": str   # 가져온 콘텐츠에 실행할 프롬프트
}
출력:
{
    "response": str,           # 프롬프트에 대한 AI 모델의 응답
    "url": str,                # 가져온 URL
    "final_url": str | None,   # 리디렉션 후 최종 URL
    "status_code": int | None  # HTTP 상태 코드
}

WebSearch

도구 이름: WebSearch 입력:
{
    "query": str,                        # 사용할 검색 쿼리
    "allowed_domains": list[str] | None, # 이 도메인의 결과만 포함
    "blocked_domains": list[str] | None  # 이 도메인의 결과는 절대 포함하지 않음
}
출력:
{
    "results": [
        {
            "title": str,
            "url": str,
            "snippet": str,
            "metadata": dict | None
        }
    ],
    "total_results": int,
    "query": str
}

TodoWrite

도구 이름: TodoWrite 입력:
{
    "todos": [
        {
            "content": str, # 작업 설명
            "status": "pending" | "in_progress" | "completed",  # 작업 상태
            "activeForm": str                            # 설명의 활성 형태
        }
    ]
}
출력:
{
    "message": str,  # 성공 메시지
    "stats": {
        "total": int,
        "pending": int,
        "in_progress": int,
        "completed": int
    }
}

BashOutput

도구 이름: BashOutput 입력:
{
    "bash_id": str,       # 백그라운드 셸의 ID
    "filter": str | None  # 출력 줄을 필터링할 선택적 정규식
}
출력:
{
    "output": str, # 마지막 확인 이후의 새 출력
    "status": "running" | "completed" | "failed",       # 현재 셸 상태
    "exitCode": int | None # 완료 시 종료 코드
}

KillBash

도구 이름: KillBash 입력:
{
    "shell_id": str  # 종료할 백그라운드 셸의 ID
}
출력:
{
    "message": str,  # 성공 메시지
    "shell_id": str  # 종료된 셸의 ID
}

ExitPlanMode

도구 이름: ExitPlanMode 입력:
{
    "plan": str  # 사용자 승인을 위해 실행할 계획
}
출력:
{
    "message": str,          # 확인 메시지
    "approved": bool | None  # 사용자가 계획을 승인했는지 여부
}

ListMcpResources

도구 이름: ListMcpResources 입력:
{
    "server": str | None  # 리소스를 필터링할 선택적 서버 이름
}
출력:
{
    "resources": [
        {
            "uri": str,
            "name": str,
            "description": str | None,
            "mimeType": str | None,
            "server": str
        }
    ],
    "total": int
}

ReadMcpResource

도구 이름: ReadMcpResource 입력:
{
    "server": str,  # MCP 서버 이름
    "uri": str      # 읽을 리소스 URI
}
출력:
{
    "contents": [
        {
            "uri": str,
            "mimeType": str | None,
            "text": str | None,
            "blob": str | None
        }
    ],
    "server": str
}

ClaudeSDKClient를 사용한 고급 기능

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

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

class ConversationSession:
    """Claude와의 단일 대화 세션을 유지합니다."""
    
    def __init__(self, options: ClaudeCodeOptions = None):
        self.client = ClaudeSDKClient(options)
        self.turn_count = 0
    
    async def start(self):
        await self.client.connect()
        print("대화 세션을 시작합니다. Claude가 컨텍스트를 기억할 것입니다.")
        print("명령: 종료하려면 'exit', 현재 작업을 중지하려면 'interrupt', 새 세션을 위해 'new'")
        
        while True:
            user_input = input(f"\n[턴 {self.turn_count + 1}] 당신: ")
            
            if user_input.lower() == 'exit':
                break
            elif user_input.lower() == 'interrupt':
                await self.client.interrupt()
                print("작업이 중단되었습니다!")
                continue
            elif user_input.lower() == 'new':
                # 새로운 세션을 위해 연결 해제 후 재연결
                await self.client.disconnect()
                await self.client.connect()
                self.turn_count = 0
                print("새 대화 세션을 시작했습니다 (이전 컨텍스트가 지워졌습니다)")
                continue
            
            # 메시지 전송 - Claude가 이 세션의 모든 이전 메시지를 기억함
            await self.client.query(user_input)
            self.turn_count += 1
            
            # 응답 처리
            print(f"[턴 {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()  # 응답 후 새 줄
        
        await self.client.disconnect()
        print(f"대화가 {self.turn_count}턴 후에 종료되었습니다.")

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

# 예제 대화:
# 턴 1 - 당신: "hello.py라는 파일을 만들어주세요"
# 턴 1 - Claude: "hello.py 파일을 만들어드리겠습니다..."
# 턴 2 - 당신: "그 파일에 무엇이 들어있나요?"  
# 턴 2 - Claude: "방금 만든 hello.py 파일에는..." (기억함!)
# 턴 3 - 당신: "거기에 main 함수를 추가해주세요"
# 턴 3 - Claude: "hello.py에 main 함수를 추가하겠습니다..." (어떤 파일인지 알고 있음!)

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]:
    """실행 전에 모든 도구 사용을 로그합니다."""
    tool_name = input_data.get('tool_name', 'unknown')
    print(f"[PRE-TOOL] 사용 예정: {tool_name}")

    # 여기서 도구 실행을 수정하거나 차단할 수 있습니다
    if tool_name == "Bash" and "rm -rf" in str(input_data.get('tool_input', {})):
        return {
            'hookSpecificOutput': {
                'hookEventName': 'PreToolUse',
                'permissionDecision': 'deny',
                'permissionDecisionReason': '위험한 명령이 차단되었습니다'
            }
        }
    return {}

async def post_tool_logger(
    input_data: dict[str, Any],
    tool_use_id: str | None,
    context: HookContext
) -> dict[str, Any]:
    """도구 실행 후 결과를 로그합니다."""
    tool_name = input_data.get('tool_name', 'unknown')
    print(f"[POST-TOOL] 완료됨: {tool_name}")
    return {}

async def user_prompt_modifier(
    input_data: dict[str, Any],
    tool_use_id: str | None,
    context: HookContext
) -> dict[str, Any]:
    """사용자 프롬프트에 컨텍스트를 추가합니다."""
    original_prompt = input_data.get('prompt', '')

    # 모든 프롬프트에 타임스탬프 추가
    from datetime import datetime
    timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")

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

async def main():
    options = ClaudeAgentOptions(
        hooks={
            'PreToolUse': [
                HookMatcher(hooks=[pre_tool_logger]),
                HookMatcher(matcher='Bash', hooks=[pre_tool_logger])
            ],
            'PostToolUse': [
                HookMatcher(hooks=[post_tool_logger])
            ],
            'UserPromptSubmit': [
                HookMatcher(hooks=[user_prompt_modifier])
            ]
        },
        allowed_tools=["Read", "Write", "Bash"]
    )
    
    async with ClaudeSDKClient(options=options) as client:
        await client.query("현재 디렉토리의 파일을 나열해주세요")
        
        async for message in client.receive_response():
            # 훅이 자동으로 도구 사용을 로그할 것입니다
            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(
            "다양한 정렬 알고리즘으로 5개의 Python 파일을 만들어주세요"
        )
        
        # 실시간으로 진행 상황 모니터링
        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"🔨 생성 중: {file_path}")
                    elif isinstance(block, ToolResultBlock):
                        print(f"✅ 도구 실행 완료")
                    elif isinstance(block, TextBlock):
                        print(f"💭 Claude가 말합니다: {block.text[:100]}...")
            
            # 최종 결과를 받았는지 확인
            if hasattr(message, 'subtype') and message.subtype in ['success', 'error']:
                print(f"\n🎯 작업 완료!")
                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="setup.py가 있는 Python 프로젝트 구조를 만들어주세요",
        options=options
    ):
        if isinstance(message, AssistantMessage):
            for block in message.content:
                if isinstance(block, ToolUseBlock):
                    print(f"도구 사용: {block.name}")

asyncio.run(create_project())

오류 처리

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

try:
    async for message in query(prompt="안녕하세요"):
        print(message)
except CLINotFoundError:
    print("Claude Code를 설치해주세요: npm install -g @anthropic-ai/claude-code")
except ProcessError as e:
    print(f"프로세스가 종료 코드 {e.exit_code}로 실패했습니다")
except CLIJSONDecodeError as e:
    print(f"응답 파싱에 실패했습니다: {e}")

클라이언트와 스트리밍 모드

from claude_agent_sdk import ClaudeSDKClient
import asyncio

async def interactive_session():
    async with ClaudeSDKClient() as client:
        # 초기 메시지 전송
        await client.query("날씨가 어떤가요?")
        
        # 응답 처리
        async for msg in client.receive_response():
            print(msg)
        
        # 후속 조치 전송
        await client.query("그것에 대해 더 자세히 말해주세요")
        
        # 후속 응답 처리
        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

# @tool 데코레이터로 사용자 정의 도구 정의
@tool("calculate", "수학 계산 수행", {"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}"
            }]
        }
    except Exception as e:
        return {
            "content": [{
                "type": "text",
                "text": f"오류: {str(e)}"
            }],
            "is_error": True
        }

@tool("get_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}"
        }]
    }

async def main():
    # 사용자 정의 도구로 SDK MCP 서버 생성
    my_server = create_sdk_mcp_server(
        name="utilities",
        version="1.0.0",
        tools=[calculate, get_time]
    )

    # 서버로 옵션 구성
    options = ClaudeAgentOptions(
        mcp_servers={"utils": my_server},
        allowed_tools=[
            "mcp__utils__calculate",
            "mcp__utils__get_time"
        ]
    )
    
    # 대화형 도구 사용을 위해 ClaudeSDKClient 사용
    async with ClaudeSDKClient(options=options) as client:
        await client.query("123 * 456은 얼마인가요?")
        
        # 계산 응답 처리
        async for message in client.receive_response():
            if isinstance(message, AssistantMessage):
                for block in message.content:
                    if isinstance(block,TextBlock):
                        print(f"계산: {block.text}")
        
        # 시간 쿼리로 후속 조치
        await client.query("지금 몇 시인가요?")
        
        async for message in client.receive_response():
            if isinstance(message, AssistantMessage):
                for block in message.content:
                    if isinstance(block, TextBlock):
                        print(f"시간: {block.text}")

asyncio.run(main())

참고 자료