
1. 为什么 SubAgent 配置总是跑不起来Claude code 的 Agent SDK 到了高级阶段绕不开三件事用tool把 Python 函数注册成 Claude 能调用的工具、用create_sdk_mcp_server把这些工具打包成进程内 MCP server、再让主 Agent 通过 SubAgent 做任务隔离。听起来是一条清晰的链路但真正动手时多数人卡在同一个地方——配置骨架搭不起来。我见过太多人把tool函数写好了create_sdk_mcp_server也调用了结果 Claude 在推理时压根看不到工具或者看到了却传错参数。问题往往不在业务逻辑而在骨架type hints 漏了、docstring 没写、mcp_servers的 key 和 server name 对不上、SubAgent 的allowed_tools没放行对应工具。这些细节单看都不难凑在一起就变成明明照抄了代码却跑不通。这篇面向的是已经用过 Claude code 基础 SDK、想往工程化多工具编排走的人。我会给出一套可以直接复制的settings.json/config.toml骨架配合 TaoToken 统一 Key 接入然后演示一次 SubAgent 调用验证动作。目标很明确让你在本地把主 Agent 调 SubAgent、SubAgent 调自定义工具这条链路跑通而不是停在代码看起来对的阶段。核心检索词先摆出来Claude code 的 Agent SDK 高级应用围绕tool注册、create_sdk_mcp_server打包、SubAgent 协作三件事展开。适合谁适合已经能跑通query()基础调用、想进一步做多工具编排和权限隔离的开发者。2. TaoToken 前置统一 Key 与接入配置在搭 SubAgent 骨架之前先把模型接入这一层固定下来。Agent SDK 的调用最终要落到一个可用的 API 端点上TaoToken 在这里的作用是提供统一的 Key 和兼容的接入地址省去在每个 SubAgent 里重复配置鉴权的麻烦。你需要准备两样东西一个 TaoToken 的 API Key以及接入地址。地址分两个用途官网入口是https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentAPI 调用地址是https://taotoken.net/api这个不加 UTM 参数。Key 在控制台的 API Keys 页面生成地址是https://taotoken.net/console/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite。拿到 Key 之后推荐用环境变量注入而不是硬编码在脚本里。这样主 Agent 和所有 SubAgent 共享同一份鉴权切换环境时只改一处export TAOTOKEN_API_KEYsk-你的key export ANTHROPIC_BASE_URLhttps://taotoken.net/api注意环境变量名要和 SDK 读取的字段对齐。如果你的 SDK 版本读的是ANTHROPIC_API_KEY就把上面第一行的变量名改成它值不变。变量名写错是Key 明明对却报鉴权失败的头号原因。如果你更习惯用配置文件管理可以在项目根目录放一个.env然后用python-dotenv加载。但无论哪种方式原则是同一条Key 只出现一次主 Agent 和 SubAgent 都从同一个来源读。SubAgent 是独立上下文但它继承主进程的环境变量所以不需要在每个 SubAgent 里单独传 Key。这一步做完接入层就固定了。后面所有tool、create_sdk_mcp_server、SubAgent 的配置都建立在这个统一 Key 之上。3. 可复制配置settings.json 与 config.toml 骨架这一节给两套骨架。settings.json管 Claude code 的运行时行为权限、Hook、MCP server 引用config.toml管项目级参数模型、超时、SubAgent 定义。两者配合构成 SubAgent 配置的完整底座。先看settings.json。放在项目根目录的.claude/settings.json{ permissions: { allow: [ Read, Grep, Glob, Bash(pytest:*), Bash(ruff:*) ], deny: [ Bash(rm -rf:*), Bash(curl:*) ] }, mcpServers: { my-custom-tools: { command: python, args: [-m, my_tools.server], env: { TAOTOKEN_API_KEY: ${TAOTOKEN_API_KEY} } } }, hooks: { PreToolUse: [ { matcher: Bash, hooks: [ { type: command, command: bash .claude/hooks/deny-dangerous.sh } ] } ], PostToolUse: [ { matcher: Edit|Write, hooks: [ { type: command, command: bash .claude/hooks/audit-log.sh } ] } ] } }这里有几个关键点。permissions.allow是白名单SubAgent 能用的工具必须在这里放行否则即使tool注册成功Claude 调用时也会被拦。mcpServers里的 key这里是my-custom-tools必须和后面create_sdk_mcp_server的name完全一致这是最常见的对不上错误。hooks里的PreToolUse做危险命令拦截PostToolUse做审计日志这两层是 SubAgent 权限管理的基础。再看config.toml放在项目根目录[model] name claude-sonnet-4-20250514 max_turns 20 timeout_seconds 120 [api] base_url https://taotoken.net/api api_key_env TAOTOKEN_API_KEY [subagent.code_reviewer] description 代码审查 SubAgent只读分析 allowed_tools [Read, Grep, Glob] permission_mode plan system_prompt 你是代码审查专家只输出问题不修改文件。 [subagent.test_runner] description 测试执行 SubAgent allowed_tools [Bash(pytest:*), Read] permission_mode acceptEdits system_prompt 你是测试执行专家只输出测试结果。 [subagent.linter] description 代码风格检查 SubAgent allowed_tools [Bash(ruff:*), Read] permission_mode plan system_prompt 你是 lint 专家只输出风格问题。config.toml的价值在于把 SubAgent 的定义从代码里抽出来。每个 SubAgent 的allowed_tools、permission_mode、system_prompt都在这里声明主 Agent 启动时读取动态构造ClaudeCodeOptions。这样加一个 SubAgent 只需要改配置不用动主流程代码。提示permission_mode用plan表示只读分析用acceptEdits表示允许编辑。SubAgent 做分析时用plan做修复时用acceptEdits这是两阶段工作流的基础。两套骨架就位后接下来写tool和create_sdk_mcp_server的代码把它们和上面的配置对接起来。4. 验证请求一次 SubAgent 调用跑通现在把骨架填上血肉。先写自定义工具用tool装饰器注册再用create_sdk_mcp_server打包#!/usr/bin/env python3 # my_tools/server.py import asyncio from claude_code_sdk import ( query, ClaudeCodeOptions, tool, create_sdk_mcp_server ) tool def get_user(user_id: int) - dict: 根据用户 ID 获取用户对象。 Args: user_id: 用户 ID整数 Returns: 包含 id, name, email 的字典 return { id: user_id, name: fUser {user_id}, email: fuser{user_id}example.com } tool def count_lines(file_path: str) - int: 统计文件行数。 Args: file_path: 文件路径 Returns: 文件行数 with open(file_path, r, encodingutf-8) as f: return len(f.readlines()) custom_server create_sdk_mcp_server( namemy-custom-tools, version1.0.0, tools[get_user, count_lines] )注意create_sdk_mcp_server的name是my-custom-tools和settings.json里mcpServers的 key 一致。tool装饰器会自动从 type hints 生成 JSON schema从 docstring 生成工具描述所以 type hints 和 docstring 一个都不能少。接下来写主 Agent 调 SubAgent 的验证脚本#!/usr/bin/env python3 # main.py import asyncio from claude_code_sdk import query, ClaudeCodeOptions, AssistantMessage from my_tools.server import custom_server async def run_subagent(name: str, prompt: str, options: ClaudeCodeOptions) - str: 跑一个 SubAgent返回报告 report async for msg in query(promptprompt, optionsoptions): if isinstance(msg, AssistantMessage): for block in msg.content: if hasattr(block, text): report block.text return report async def main(): # 主 Agent 的 options挂载 in-process MCP server main_options ClaudeCodeOptions( mcp_servers{my-custom-tools: custom_server}, allowed_tools[mcp__my-custom-tools__get_user], max_turns5 ) # 验证 1主 Agent 直接调自定义工具 print( 验证 1主 Agent 调 get_user ) async for msg in query( prompt查询用户 ID 42 的信息, optionsmain_options ): if isinstance(msg, AssistantMessage): for block in msg.content: if hasattr(block, text): print(block.text, end, flushTrue) # 验证 2SubAgent 隔离调用 print(\n\n 验证 2SubAgent 调 count_lines ) sub_options ClaudeCodeOptions( mcp_servers{my-custom-tools: custom_server}, allowed_tools[mcp__my-custom-tools__count_lines, Read], system_prompt你是文件分析 SubAgent只统计行数。, max_turns5 ) report await run_subagent( namefile-analyzer, prompt统计 main.py 的行数, optionssub_options ) print(report) asyncio.run(main())跑起来之后验证 1 应该看到 Claude 调用get_user(42)并返回用户信息验证 2 应该看到 SubAgent 调用count_lines返回行数。如果验证 1 通过、验证 2 失败问题多半在 SubAgent 的allowed_tools没放行mcp__my-custom-tools__count_lines。工具命名规则要记牢in-process MCP server 暴露的工具在 Claude 眼里是mcp__server_name__tool_name。server_name是create_sdk_mcp_server的nametool_name是tool函数的函数名。这个命名规则对不上工具就调不到。5. 本篇常见错排查骨架跑通之前下面几个错误几乎每个人都会踩一遍。我按出现频率排一下。错误一tool函数漏写 type hints。tool装饰器依赖 type hints 生成 JSON schema。如果写成def get_user(user_id)而不是def get_user(user_id: int)Claude 看不到参数类型工具无法调用。判断标准很简单你的每个tool函数参数和返回值都标了类型吗没标就补上。错误二mcp_servers的 key 和 server name 不一致。settings.json里写my-custom-tools代码里create_sdk_mcp_server(namemy-tools)两者对不上Claude 就找不到工具。排查方法把两处的字符串打印出来对比或者统一用一个常量。错误三SubAgent 的allowed_tools没放行 MCP 工具。SubAgent 是独立上下文它的allowed_tools不会继承主 Agent。如果 SubAgent 要用mcp__my-custom-tools__count_lines就必须在它自己的allowed_tools里显式列出。漏了这一步SubAgent 会报工具不可用。错误四流式输出忘了flushTrue。如果你用print(block.text, end, flushTrue)做流式输出漏掉flushTrue会导致输出卡在缓冲区用户等很久才看到结果。这个错误不报错但体验极差容易被忽略。错误五SubAgent 报告没汇总。主 Agent 调多个 SubAgent 并行跑SubAgent 完成后报告写在各自上下文里主 Agent 如果没显式读取就只看到任务完成信号看不到具体报告。修正方式是让 SubAgent 把报告写到约定路径比如.claude/state/name.json主 Agent 显式读取并汇总。错误六重试没有次数上限。网络持续失败时无限重试会让长任务变成超长任务。重试次数控制在 3 次以内第 4 次走 fallback 降级。指数退避用 1s / 2s / 4s避免持续打 API。注意排查顺序建议从工具是否被 Claude 看到开始再到SubAgent 是否有权限调最后到报告是否被汇总。这个顺序能覆盖 90% 的配置问题。6. 从骨架到工程化下一步怎么走骨架跑通之后你已经有了一个可用的 SubAgent 配置底座。接下来可以往两个方向深化。一个方向是权限分层。把permission_mode用起来分析阶段用plan只读修复阶段用acceptEdits允许编辑再配合PreToolUseHook 拦截危险命令、PostToolUseHook 记录审计日志。四层叠加形成纵深防御。这套配置在settings.json里已经预留了位置你只需要补上 Hook 脚本。另一个方向是多 SubAgent 编排。用asyncio.gather并行跑多个 SubAgent比如代码审查、测试执行、lint 检查同时进行主 Agent 汇总三份报告。并行比串行快数倍但要注意 SubAgent 之间不能有依赖有依赖就得串行。如果你想把模型调用也统一管理TaoToken 的模型对话入口可以配合调试https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_contentmodelsutm_campaignrewrite。长期做编码和 Agent 编排的话Coding Plan 更适合持续使用https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding-planutm_campaignrewrite。接入文档在https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite遇到 SDK 字段对不上时查这里最快。最后留一个实操建议把config.toml里的 SubAgent 定义和settings.json里的mcpServers当成两个独立的真相来源用脚本在启动时校验它们的 key 是否一致。这个校验脚本不到二十行但能省掉大量配置对不上的排查时间。骨架的价值不在于一次跑通而在于跑通之后能稳定复用。