Skip to content
FunCoding

Search

Search docs, agents, posts, Skills and MCP servers

Python SDK 参考:安装、函数与 ClaudeSDKClient

Agent SDK Python 的安装、query() 与 ClaudeSDKClient 的选择,query、tool、create_sdk_mcp_server、会话函数的完整签名,以及 ClaudeSDKClient 类的方法与示例(继续对话、流式输入、中断、权限控制)。

本页是 Python Agent SDK 参考的第一部分:安装、函数和 ClaudeSDKClient 类。配置类 ClaudeAgentOptions 和各种类型见「Python SDK 参考:选项与类型」,消息、内容块和错误类型见「Python SDK 参考:消息与错误」,hook 类型、工具输入输出和沙盒配置见「Python SDK 参考:Hook、工具与沙盒」。

安装

把包安装到虚拟环境里(在较新的 Debian、Ubuntu 和 Homebrew Python 上,对系统 Python 运行 pip install 会失败并报 error: externally-managed-environment):

python3 -m venv .venv
source .venv/bin/activate
pip install claude-agent-sdk

uv、Windows PowerShell 和 API Key 设置见 Agent SDK 快速开始的 Setup 一节。

在 query() 与 ClaudeSDKClient 之间选择

Python SDK 提供两种与 Claude Code 交互的方式:

特性query()ClaudeSDKClient
会话默认创建新会话复用同一个会话
对话单次交换同一上下文里多次交换
连接自动管理手动控制
流式输入✅ 支持✅ 支持
中断❌ 不支持✅ 支持
Hooks✅ 支持✅ 支持
自定义工具✅ 支持✅ 支持
继续聊天通过 continue_conversation 或 resume 手动✅ 自动
用例一次性任务持续对话

对聊天界面这类交互式应用,或下一步动作取决于 Claude 的响应时,用 ClaudeSDKClient。

函数

本页的签名块和裸的 async for / async with 片段只是示意:要运行它们,把代码体包进 async def main(): ... 并调用 asyncio.run(main())。

query()

默认为与 Claude Code 的每次交互创建新会话,返回一个在消息到达时产出它们的异步迭代器。除非你在 ClaudeAgentOptions 里传 continue_conversation=True 或 resume,每次 query() 调用都从头开始,不记得先前的交互。

async def query(
    *,
    prompt: str | AsyncIterable[dict[str, Any]],
    options: ClaudeAgentOptions | None = None,
    transport: Transport | None = None
) -> AsyncIterator[Message]
参数类型说明
promptstr | AsyncIterable[dict]输入提示:字符串,或流式模式下的异步可迭代对象
optionsClaudeAgentOptions | None可选的配置对象(为 None 时默认 ClaudeAgentOptions())
transportTransport | None与 CLI 进程通信的可选自定义传输

返回产出对话消息的 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",
    )

    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],
    annotations: ToolAnnotations | None = None
) -> Callable[[Callable[[Any], Awaitable[dict[str, Any]]]], SdkMcpTool[Any]]
参数类型说明
namestr工具的唯一标识
descriptionstr工具做什么的人类可读描述
input_schematype | dict[str, Any]定义工具输入参数的 schema,见下
annotationsToolAnnotations | None可选的 MCP 工具注解,向客户端提供行为提示

输入 schema 选项:1. 简单类型映射(推荐):{"text": str, "count": int, "enabled": bool};2. JSON Schema 格式(用于复杂校验):

{
    "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']}!"}]}

ToolAnnotations

工具的行为提示,作为 tool() 的 annotations 参数传入。ToolAnnotations 扩展 MCP SDK 的 mcp.types.ToolAnnotations,多一个 maxResultSizeChars 字段,每个提示可用 camelCase 或 snake_case 书写:ToolAnnotations(readOnlyHint=True) 与 ToolAnnotations(read_only_hint=True) 等价;SDK 接受注解的地方也可以传普通的 mcp.types.ToolAnnotations。snake_case 名字和带类型的 maxResultSizeChars 字段需要 Python Agent SDK 0.2.140 及以上;0.1.31 到 0.2.139 原样重新导出 mcp.types.ToolAnnotations,在 0.1.55 到 0.2.139 上你仍可把 maxResultSizeChars 作为关键字参数传入(MCP 类接受额外字段,SDK 把值转发给 Claude Code)。所有字段都是可选的,客户端不应依赖这些提示做安全决定。

字段类型默认说明
titlestr | NoneNone工具的人类可读标题
readOnlyHintbool | NoneFalse为 True 时工具不修改其环境
destructiveHintbool | NoneTrue为 True 时工具可能做破坏性更新(仅当 readOnlyHint 为 False 时有意义)
idempotentHintbool | NoneFalse为 True 时用相同参数重复调用没有额外影响(仅当 readOnlyHint 为 False 时有意义)
openWorldHintbool | NoneTrue为 True 时工具与外部实体交互(如网页搜索);为 False 时工具的领域是封闭的(如记忆工具)
maxResultSizeCharsint | NoneNoneClaude Code 把该工具的文本结果保留在对话里而不是存到文件的字符数上限,最高 500,000;含图片的结果不受影响。这是 Claude Code 设置而不是 MCP 提示:SDK 把它放在工具的 _meta 里,作为 anthropic/maxResultSizeChars 发送
from claude_agent_sdk import tool, ToolAnnotations
from typing import Any


@tool(
    "search",
    "Search the web",
    {"query": str},
    annotations=ToolAnnotations(readOnlyHint=True, openWorldHint=True),
)
async def search(args: dict[str, Any]) -> dict[str, Any]:
    return {"content": [{"type": "text", "text": f"Results for: {args['query']}"}]}

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, ClaudeAgentOptions


@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],  # 传入被装饰的函数
)

# 与 Claude 一起使用
options = ClaudeAgentOptions(
    mcp_servers={"calc": calculator},
    allowed_tools=["mcp__calc__add", "mcp__calc__multiply"],
)

list_sessions()

列出过去的会话及其元数据;可按项目目录过滤,也可列出所有项目的会话。同步,立即返回。

def list_sessions(
    directory: str | None = None,
    limit: int | None = None,
    offset: int = 0,
    include_worktrees: bool = True
) -> list[SDKSessionInfo]
参数类型默认说明
directorystr | NoneNone要列出会话的目录;省略时返回所有项目的会话
limitint | NoneNone最多返回的会话数
offsetint0从排序结果开头跳过的会话数,配合 limit 做分页
include_worktreesboolTruedirectory 位于 git 仓库内时,包含所有 worktree 路径的会话

返回类型 SDKSessionInfo:

属性类型说明
session_idstr唯一的会话标识
summarystr显示标题:自定义标题、最近的提示、自动生成的摘要,或第一个提示
last_modifiedint最后修改时间,自 epoch 起的毫秒数
file_sizeint | None会话文件字节数(远程存储后端为 None)
custom_titlestr | None会话标题:用户设置的标题,没设置时是自动生成的标题
first_promptstr | None会话里第一个有意义的用户提示
git_branchstr | None会话结束时的 git 分支
cwdstr | None会话的工作目录
tagstr | None用户设置的会话标签(见 tag_session())
created_atint | None会话创建时间,自 epoch 起的毫秒数

打印某个项目最近的 10 个会话(结果按 last_modified 降序排序,第一项最新;省略 directory 则跨所有项目搜索):

from claude_agent_sdk import list_sessions

for session in list_sessions(directory="/path/to/project", limit=10):
    print(f"{session.summary} ({session.session_id})")

get_session_messages()

读取过去会话的消息。同步,立即返回。

def get_session_messages(
    session_id: str,
    directory: str | None = None,
    limit: int | None = None,
    offset: int = 0
) -> list[SessionMessage]
参数类型默认说明
session_idstr必填要读取消息的会话 ID
directorystr | NoneNone在其中查找的项目目录;省略时搜索所有项目
limitint | NoneNone最多返回的消息数
offsetint0从开头跳过的消息数

返回类型 SessionMessage:

属性类型说明
typeLiteral["user", "assistant"]消息角色
uuidstr唯一的消息标识
session_idstr会话标识
messageAny原始消息内容
parent_tool_use_idstr | None对子智能体消息,是派生它的 Agent 工具使用块的 id;主会话消息和较旧的会话为 None
parent_agent_idstr | None对来自嵌套子智能体的消息,是父子智能体的智能体 id;主会话消息、顶层子智能体消息和较旧的会话为 None(需要 Python Agent SDK 0.2.140 及以上)
from claude_agent_sdk import list_sessions, get_session_messages

sessions = list_sessions(limit=1)
if sessions:
    messages = get_session_messages(sessions[0].session_id)
    for msg in messages:
        print(f"[{msg.type}] {msg.uuid}")

get_session_info()

按 ID 读取单个会话的元数据,而不必扫描整个项目目录。同步,立即返回。

def get_session_info(
    session_id: str,
    directory: str | None = None,
) -> SDKSessionInfo | None
参数类型默认说明
session_idstr必填要查找的会话 UUID
directorystr | NoneNone项目目录路径;省略时搜索所有项目目录

返回 SDKSessionInfo,找不到会话时返回 None。已有来自先前运行的会话 ID 时很有用:

from claude_agent_sdk import get_session_info

info = get_session_info("550e8400-e29b-41d4-a716-446655440000")
if info:
    print(f"{info.summary} (branch: {info.git_branch}, tag: {info.tag})")

rename_session()

通过追加一个自定义标题条目来重命名会话。重复调用是安全的,最近的标题生效。同步。

def rename_session(
    session_id: str,
    title: str,
    directory: str | None = None,
) -> None
参数类型默认说明
session_idstr必填要重命名的会话 UUID
titlestr必填新标题,去掉空白后必须非空
directorystr | NoneNone项目目录路径;省略时搜索所有项目目录

session_id 不是有效 UUID 或 title 为空时抛 ValueError;找不到会话时抛 FileNotFoundError。

from claude_agent_sdk import list_sessions, rename_session

sessions = list_sessions(directory="/path/to/project", limit=1)
if sessions:
    rename_session(sessions[0].session_id, "Refactor auth module")

新标题在之后的读取里出现在 SDKSessionInfo.custom_title 中。

tag_session()

给会话打标签;传 None 清除标签。重复调用是安全的,最近的标签生效。同步。

def tag_session(
    session_id: str,
    tag: str | None,
    directory: str | None = None,
) -> None
参数类型默认说明
session_idstr必填要打标签的会话 UUID
tagstr | None必填标签字符串,或 None 以清除;存储前做 Unicode 清理
directorystr | NoneNone项目目录路径;省略时搜索所有项目目录

session_id 不是有效 UUID 或 tag 清理后为空时抛 ValueError;找不到会话时抛 FileNotFoundError。

from claude_agent_sdk import list_sessions, tag_session

# 给最近的会话打标签
sessions = list_sessions(directory="/path/to/project", limit=1)
if sessions:
    tag_session(sessions[0].session_id, "needs-review")

# 之后:找出所有带该标签的会话
for session in list_sessions(directory="/path/to/project"):
    if session.tag == "needs-review":
        print(session.summary)

类

ClaudeSDKClient

在多次交换之间维持一个对话会话。 这相当于 TypeScript SDK 的 query() 函数在内部的工作方式:它创建一个能继续对话的客户端对象。

class ClaudeSDKClient:
    def __init__(self, options: ClaudeAgentOptions | None = None, transport: Transport | 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 set_permission_mode(self, mode: PermissionMode) -> None
    async def set_model(self, model: str | None = None) -> None
    async def rewind_files(self, user_message_id: str) -> None
    async def get_mcp_status(self) -> McpStatusResponse
    async def get_context_usage(self) -> ContextUsageResponse
    async def reconnect_mcp_server(self, server_name: str) -> None
    async def toggle_mcp_server(self, server_name: str, enabled: bool) -> None
    async def stop_task(self, task_id: str) -> None
    async def get_server_info(self) -> dict[str, Any] | None
    async def disconnect(self) -> None
方法说明
__init__(options)用可选配置初始化客户端
connect(prompt)连接到 Claude,可带可选的初始提示或消息流
query(prompt, session_id)在流式模式下发送新请求
receive_messages()以异步迭代器接收来自 Claude 的所有消息
receive_response()接收消息直到并包括一个 ResultMessage
interrupt()发送中断信号(只在流式模式下有效)
set_permission_mode(mode)更改当前会话的权限模式
set_model(model)更改当前会话的模型;传 None 重置为 Claude Code 的默认模型
rewind_files(user_message_id)把文件恢复到指定用户消息时的状态,需要 enable_file_checkpointing=True
get_mcp_status()获取所有已配置 MCP 服务器的状态,返回 McpStatusResponse
get_context_usage()获取按类别、skill 和工具拆解的上下文窗口用量,与交互式会话里 /context 显示的数据相同,返回 ContextUsageResponse;为计算该拆解,Claude Code 会发出几个不出现在消息流里的 token 计数 API 请求
reconnect_mcp_server(server_name)重试连接失败或已断开的 MCP 服务器
toggle_mcp_server(server_name, enabled)在会话中途启用或禁用 MCP 服务器;禁用 stdio、SSE 或 HTTP 服务器会移除其工具
stop_task(task_id)停止运行中的后台任务;消息流里随后跟一条状态为 "stopped" 的 TaskNotificationMessage
get_server_info()获取服务器的初始化信息,包括可用命令和输出样式
disconnect()断开与 Claude 的连接

上下文管理器支持

客户端可以用作异步上下文管理器来自动管理连接:

import asyncio
from claude_agent_sdk import ClaudeSDKClient


async def main():
    async with ClaudeSDKClient() as client:
        await client.query("Hello Claude")
        async for message in client.receive_response():
            print(message)


asyncio.run(main())

重要:迭代消息时,避免用 break 提前退出,这会引起 asyncio 清理问题;要让迭代自然完成,或用标志位跟踪你是否已找到所需内容。

示例:继续对话

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


async def main():
    async with ClaudeSDKClient() as client:
        # 第一个问题
        await client.query("What's the capital of France?")

        # 处理响应
        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("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}")

        # 再追问——仍在同一个对话里
        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 流式输入

query() 也接受用户消息字典的异步可迭代对象,所以你可以在发送时组装提示,或包含图片这类内容块。Claude Code 一收到第一条产出的消息就开始响应,不等可迭代对象结束,receive_response() 在结束该响应的 ResultMessage 处停止。要把 Claude 在回答之前应读取的一切放进一条消息(如这个生成器所做的),并把每个 query() 调用与它自己的 receive_response() 配对。

import asyncio
from claude_agent_sdk import ClaudeSDKClient


async def message_stream():
    """在发送时组装提示,并作为一条用户消息产出。"""
    readings = {"Temperature": "25°C", "Humidity": "60%"}
    data = ", ".join(f"{name}: {value}" for name, value in readings.items())
    yield {
        "type": "user",
        "message": {
            "role": "user",
            "content": f"Analyze the following sensor data and describe any patterns you see: {data}",
        },
    }


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("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, ResultMessage


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

    async with ClaudeSDKClient(options=options) as client:
        # 启动一个长时间运行的任务
        await client.query("Count from 1 to 100 slowly, using the bash sleep command")

        # 让它运行一会儿
        await asyncio.sleep(2)

        # 中断任务
        await client.interrupt()
        print("Task interrupted!")

        # 排空被中断任务的消息(包括它的 ResultMessage)
        async for message in client.receive_response():
            if isinstance(message, ResultMessage):
                print(f"Interrupted task: terminal_reason={message.terminal_reason!r}")
                # 被中断的轮次的 terminal_reason 是 "aborted_streaming" 或 "aborted_tools"

        # 发送新命令
        await client.query("Just say hello instead")

        # 现在接收新的响应
        async for message in client.receive_response():
            if isinstance(message, ResultMessage) and message.subtype == "success":
                print(f"New result: {message.result}")


asyncio.run(interruptible_task())

中断之后的缓冲区行为:interrupt() 发送停止信号,但不清空消息缓冲区。被中断任务已经产生的消息(包括它的 ResultMessage)仍留在流里。你必须先用 receive_response() 排空它们,才能读取新查询的响应;如果在 interrupt() 之后立即发送新查询并只调用一次 receive_response(),你收到的是被中断任务的消息,而不是新查询的响应。

示例:高级权限控制

import asyncio
from claude_agent_sdk import ClaudeSDKClient, ClaudeAgentOptions
from claude_agent_sdk.types import (
    PermissionResultAllow,
    PermissionResultDeny,
    ToolPermissionContext,
)


async def custom_permission_handler(
    tool_name: str, input_data: dict, context: ToolPermissionContext
) -> PermissionResultAllow | PermissionResultDeny:
    """工具权限的自定义逻辑。"""

    # 阻止写入系统目录
    if tool_name == "Write" and input_data.get("file_path", "").startswith("/system/"):
        return PermissionResultDeny(
            message="System directory write not allowed", 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 PermissionResultAllow(
            updated_input={**input_data, "file_path": safe_path}
        )

    # 允许其他一切
    return PermissionResultAllow(updated_input=input_data)


async def main():
    # 不要同时把被把关的工具列在 allowed_tools 里:允许规则在 can_use_tool 运行之前就批准调用
    options = ClaudeAgentOptions(can_use_tool=custom_permission_handler)

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

        async for message in client.receive_response():
            # 会改用沙盒路径
            print(message)


asyncio.run(main())