简介
Claude Code的本质是"大模型+工具调用循环+有限上下文窗口",前面各章的扩展手段,都没有离开Claude Code自己的进程。上一章《7.Headless》的Headless模式(非交互模式)把人移出了循环,但它仍然是从进程外整只调用CLI:字符串进、字符串出,控制粒度到命令行参数为止。要把工具调用循环(agent loop)嵌进自有应用,还缺三样东西:进程内的自定义工具、类型化的消息、代码级的权限决策。补齐这三样的,就是本章的主角Claude Agent SDK——工具调用循环的库形态。
先交代一句更名:Agent SDK原名Claude Code SDK,2025年9月更名,以反映它从"Claude Code的编程接口"演变为通用的Agent运行时。本文统一称Agent SDK,或简称SDK。
本文,我们从定位与选型讲起,依次讨论安装与两种调用模式、配置选项、权限的程序化控制、进程内自定义工具、Hooks(钩子)与消息流,以及会话管理。贯穿全章的示例场景是同一个:一个嵌在内部Web服务里的代码审查应用。
- 官方文档:https://code.claude.com/docs/en/agent-sdk
- TypeScript参考:https://code.claude.com/docs/en/agent-sdk/typescript
- Python参考:https://code.claude.com/docs/en/agent-sdk/python
Agent SDK是什么
从控制粒度看,使用Claude Code有三层递进:
- 交互CLI
人守着终端,逐轮下指令、答权限提示,控制手段是配置与斜杠命令。 - Headless
claude -p把人移出循环,输入输出变成文本与JSON,控制手段是命令行参数。但每次调用都要启动一个完整的CLI进程,Claude Code对我们的应用而言是个黑盒。 - Agent SDK
循环本身以库的形态进入我们的进程:工具是我们的函数,权限决策是我们的回调,消息是类型化对象,会话是可编程的资源。
还有一个容易混淆的对象:Anthropic Client SDK,即直接调用Claude API的官方SDK。Client SDK只负责把请求发给模型、把回复取回来,工具调用循环要我们自己实现——定义工具、解析模型的调用请求、执行、把结果拼回对话、再次请求,循环往复。Agent SDK则把循环连同权限系统、会话管理一起接管,我们提供的只是工具和策略。一句话:Client SDK给我们模型,Agent SDK给我们循环。
Headless与SDK都能无人值守地运行,怎么选:
| 维度 | Headless(claude -p) |
Agent SDK |
|---|---|---|
| 运行形态 | 进程外调用完整CLI | 进程内库,嵌入自有应用 |
| 输入输出 | 字符串与JSON文本 | 类型化消息对象 |
| 自定义工具 | 需在进程外另起工具服务器 | 进程内函数即工具 |
| 权限决策 | 启动前用参数预声明 | 运行时由代码回调逐次决定 |
| 多轮会话 | 用session_id串联多次进程调用 |
会话常驻进程内,可编程管理 |
| 部署依赖 | 只需CLI本身 | 需要Node 18+或Python 3.10+运行时 |
| 典型场景 | CI/CD单步任务、shell脚本、一次性任务 | Web服务、内部工具、需要细粒度控制的长期服务 |
判据可以压缩成一句话:字符串进出够用,就留在Headless;需要进程内控制,就上SDK。
安装与两种模式
两个语言版本的包:
| 语言 | 包名 | 运行时要求 | 安装命令 |
|---|---|---|---|
| TypeScript | @anthropic-ai/claude-agent-sdk |
Node 18+ | npm install @anthropic-ai/claude-agent-sdk |
| Python | claude-agent-sdk |
Python 3.10+ | pip install claude-agent-sdk |
TypeScript包附带可选的原生Claude Code二进制,不需要单独安装Claude Code。认证走ANTHROPIC_API_KEY环境变量。
SDK提供两种调用模式:
query()
单轮查询。默认每次调用都开一个全新会话(可用resume选项恢复历史会话,见后文"会话管理"一节),返回异步生成器,Claude每生成一条消息就即时产出一条。适合一次性任务。ClaudeSDKClient
连续会话,Python独有的类。支持多轮追问,还提供set_permission_mode()、set_model()等方法在会话中途改变配置。TypeScript没有对应的类,等价能力由query()的流式输入模式承担:以异步可迭代对象作为prompt持续送入消息,并在返回对象上调用setPermissionMode()、setModel()。适合交互式应用。
命名约定:同一个配置项,TypeScript用驼峰(如allowedTools),Python用蛇形(如allowed_tools)。本文的表格会并列给出两种写法,只写一种的表示两侧相同。
先看query()的最小用法,让代码审查服务跑起来。Python版示例代码:
1 | import asyncio |
TypeScript版功能等价。示例代码:
1 | import { query } from "@anthropic-ai/claude-agent-sdk"; |
循环里拿到的message不是字符串,而是类型化的消息对象,具体类型见后文"Hooks与消息流"一节。
配置选项
Headless模式下,模型、工具、权限这些控制项都是命令行参数;SDK把它们连同MCP(Model Context Protocol,模型上下文协议)服务器、Hooks回调、Sub-Agent(子智能体)定义一起,收进一个配置对象ClaudeAgentOptions。参数从此不再是启动前定死的静态值,而是可以按用户、按任务动态计算的代码。
| 字段(Python / TypeScript) | 默认值 | 说明 |
|---|---|---|
model |
CLI默认模型 | 指定模型 |
system_prompt / systemPrompt |
最小系统提示 | 传字符串自定义,或选用预设系统提示 |
allowed_tools / allowedTools |
空 | 预批准的工具列表,逐项自动放行 |
disallowed_tools / disallowedTools |
空 | 禁用工具,裸名与带说明符的模式语义不同,见下文 |
permission_mode / permissionMode |
default |
权限模式,见下一节 |
max_turns / maxTurns |
不限 | 最大执行轮数 |
cwd |
进程当前目录 | 工作目录 |
thinking |
TypeScript为{ type: 'adaptive' },Python为None |
推理行为,结构化对象:type取adaptive/enabled/disabled,enabled时可另设token预算 |
mcp_servers / mcpServers |
空 | MCP服务器:外部服务器配置,或进程内服务器对象 |
hooks |
空 | 事件到回调函数的映射 |
can_use_tool / canUseTool |
无 | 权限决策回调,见下一节 |
agents |
空 | 用代码定义Sub-Agent |
resume |
无 | 传session_id恢复历史会话 |
setting_sources / settingSources |
缺省时等同加载user、project、local三级 |
从文件系统加载哪些层级的配置 |
skills |
自动发现 | 限定加载的Skill(技能)列表 |
其中disallowed_tools的两种写法要分清:裸工具名(如"Bash")把该工具从工具集中整个移除;带说明符的模式(如"Bash(rm *)")只阻止匹配的那类调用,工具本身仍在。
四个字段牵涉其他章展开过的机制,SDK只是换了形态:
setting_sources/settingSources
控制从文件系统加载哪些层级的配置("user"/"project"/"local"),决定.claude/目录与CLAUDE.md是否进入会话,记忆的层级体系见《2.Memory》。agents
不需要.claude/agents/目录,直接在代码里给出Sub-Agent定义,委派机制见《4.SubAgent》。hooks
Hook从配置文件里的shell命令变成进程内回调函数,形态差异见本章后文,协议本身见《5.Hooks》。mcp_servers/mcpServers
除了外部MCP服务器配置,还可以直接传进程内服务器对象。外部服务器的配置与认证见《6.MCP》,进程内形态是SDK独有的增量,见本章后文。
权限的程序化控制
交互模式下,权限提示弹给终端前的人;SDK嵌在Web服务里时,终端前没有人。权限决策必须整体交给代码。
SDK评估一次工具调用是否放行,按固定顺序过六道关卡,先到先决:
- Hooks——
PreToolUse回调最先执行,它的deny是终局,后面的allow救不回来 - Deny规则
- Ask规则
- 权限模式(
permission_mode/permissionMode) - Allow规则
canUseTool回调——前面都没给出决策时的兜底
其中Deny/Ask/Allow规则就是Tool(specifier)语法的权限规则,语法与优先级见《1.概述》,SDK里通常经setting_sources从settings.json加载,或由allowed_tools/disallowed_tools给出。
六种权限模式(default/acceptEdits/plan/auto/dontAsk/bypassPermissions)的定义见《1.概述》,SDK侧只有两处差异需要单独交代:auto模式仅TypeScript版支持;plan模式下文件编辑类操作会强制走canUseTool询问。
canUseTool是SDK特有的能力:一个运行时回调,收到工具名与工具输入,返回allow/deny/ask/defer四种决策之一——allow放行、deny拒绝、ask强制发起一次询问、defer不表态。在没有终端的场景里,"询问用户"这个动作也由它接管:我们可以把询问转成Web页面上的一次确认。
以代码审查服务为例,限制写操作只能落在服务自己的工作目录内。示例代码:
1 | const options = { |
Python侧对应字段为can_use_tool,回调逻辑同理。
进程内自定义工具
循环的能力上限等于工具集上限。外部MCP服务器解决了标准化接入,但它是独立进程:要单独部署、走进程间通信(IPC),工具逻辑与应用逻辑之间隔着协议与进程边界。当工具要调用的就是应用自己的函数——查自家数据库、调内部服务——为一个函数起一个服务器进程并不划算。
SDK给出的答案是进程内自定义工具:用普通函数定义工具,打包成进程内MCP服务器,工具调用就是函数调用,没有子进程,也没有IPC开销。
给代码审查服务加一个查询内部工单系统的工具。TypeScript用tool()加zod(TypeScript生态的参数模式校验库)定义参数结构,再用createSdkMcpServer打包。示例代码:
1 | import { query, tool, createSdkMcpServer } from "@anthropic-ai/claude-agent-sdk"; |
Python用@tool装饰器与create_sdk_mcp_server,功能等价。示例代码:
1 | from claude_agent_sdk import tool, create_sdk_mcp_server, ClaudeAgentOptions |
三点说明:
- 命名
进程内工具同样遵循mcp__<server>__<tool>命名,把全名加进allowed_tools/allowedTools即预批准,权限规则里也按这个名字写。 - 返回格式
工具函数直接返回MCP工具结果格式(content数组),与外部服务器的返回一致,模型侧无感知。 - 与外部服务器的分工
外部MCP服务器适合跨团队复用、独立演进的能力;进程内工具适合与应用共生的业务逻辑。外部形态的配置、传输与认证见《6.MCP》。
注意:工具的描述是写给模型看的使用说明,参数是模型生成的输入。不要信任模型传入的任何参数,工具内部必须自行校验。
Hooks与消息流
CLI里,Hook是settings.json中的一条配置加一个shell命令,数据经stdin/stdout以JSON交换;SDK里,Hook是注册在hooks选项上的进程内异步函数——事件数据从函数参数进,决策从返回值出,可以直接读写应用自己的状态,也可以打断点调试。返回值里的关键字段与CLI协议一致:PreToolUse可返回permissionDecision与updatedInput,PostToolUse可返回additionalContext与updatedToolOutput,UserPromptSubmit可返回additionalContext等,完整协议见《5.Hooks》,这里只讲SDK侧的差异。
第一个差异是事件覆盖面,两个语言版本并不对等:
| 事件 | Python | TypeScript |
|---|---|---|
PreToolUse |
支持 | 支持 |
PostToolUse |
支持 | 支持 |
PostToolUseFailure |
支持 | 支持 |
UserPromptSubmit |
支持 | 支持 |
Stop |
支持 | 支持 |
SubagentStart / SubagentStop |
支持 | 支持 |
PermissionRequest |
支持 | 支持 |
Notification |
支持 | 支持 |
PreCompact |
支持 | 支持 |
PostToolBatch |
不支持 | 支持 |
SessionStart / SessionEnd |
不支持 | 支持 |
MessageDisplay |
不支持 | 支持 |
第二个差异是matcher的写法要点:多个工具做精确列表用管道分隔(如Write|Edit);用正则时以^、$包裹;单个工具名不含其他字符时按精确匹配处理;MCP工具(含进程内工具)写全名mcp__<server>__<tool>。
消息流是SDK的另一半。上一章的stream-json是进程边界上的文本行协议,SDK把同样的信息升级为进程内的类型化消息对象,由query()的异步生成器逐条产出。消息是两层结构:外层区分"谁在说话",内层区分"说的是什么"。
外层的消息类型:
| 消息类型(TypeScript / Python) | 含义 |
|---|---|
SDKAssistantMessage / AssistantMessage |
Claude的一轮输出,content字段是内容块列表 |
SDKUserMessage / UserMessage |
用户侧消息,工具执行结果以这类消息回传 |
SDKSystemMessage / SystemMessage |
系统事件,如会话初始化(内含session_id等信息) |
SDKResultMessage / ResultMessage |
最终结果,含耗时、开销等统计 |
文本与工具调用不是独立的消息类型,而是消息内部的内容块:TextBlock是文本;ToolUseBlock是工具调用请求,带工具名、输入与id;ToolResultBlock是工具执行结果,带content与tool_use_id。遍历时的常见模式是两层分派:先按消息类型,再按内容块类型——文本块累积成最终报告,ToolUseBlock与ToolResultBlock按id配对,用来展示执行进度、记审计日志。对代码审查服务来说,这意味着页面可以实时渲染"Claude正在读哪个文件、跑到第几步",而不是挂一个加载动画等最终字符串。
会话管理
每个会话都有唯一的session_id,消息流开头的系统初始化消息里就带着它,应用可以自行记录、与业务数据关联。
恢复与分叉:resume选项传入session_id,新调用就在原有上下文上继续,不必在prompt里重复交代背景;在恢复的同时设置fork_session(TypeScript为forkSession),则从原会话复制出一个独立分支,原会话不受影响。对代码审查服务,这直接对应产品功能:用户点开旧审查继续追问用resume,"基于同一份分析尝试两种修复思路"用fork。
经验
allowedTools不制约bypassPermissions
两者同时设置时,所有工具仍会被自动批准。要硬性移除能力,用disallowed_tools/disallowedTools,并分清裸名与带说明符模式的语义差别。- 显式指定
settingSources时记得带上"project"
否则项目级.claude/settings.json不会被加载,在CLI里调好的权限规则与Hooks到了SDK里会"神秘失效"。 - Hook的matcher只匹配工具名,不匹配文件路径
想按路径过滤(比如只拦截对.env的写入),要在回调内部检查tool_input里的文件路径字段。 updatedInput必须伴随permissionDecision
修改工具输入时,返回值要放在hookSpecificOutput内,且同时给出permissionDecision(allow或ask),否则修改不会生效。- Hook没触发时的排查顺序
先查事件名大小写(是PreToolUse不是preToolUse),再查matcher是否精确匹配到工具名,最后确认回调注册在正确的事件下。 - 权限评估顺序常被误解
Hooks与Deny规则在前,canUseTool只是最后的兜底,不要指望Allow规则能越过前面的deny,完整的六道关卡见前文"权限的程序化控制"一节。 - Sub-Agent继承主会话的权限模式
权限模式会从主会话向Sub-Agent级联,且无法逐个覆盖,机制见《4.SubAgent》。给主会话开bypassPermissions之前,先想清楚它派出去的Sub-Agent也会带着同样的权限。 - 上手路径
从query()加allowed_tools起步;需要多轮交互再引入ClaudeSDKClient(TypeScript则启用流式输入);需要自定义权限时,用canUseTool回调或PreToolUseHook。
回望本章,我们在代码里定义了工具、回调、Sub-Agent与Hook;加上前几章在.claude/目录里攒下的CLAUDE.md、Skills与MCP配置,这些定制资产已经散落在各个项目里,团队之间的共享还停留在拷贝文件。把它们打包成可分发、可版本化的单元,是《9.Plugins》要解决的问题。