Skip to main content
工具是模型执行任务时可以调用的能力。Qoder Agent SDK Python 版支持两类工具:
  • 内置工具:由 Qoder CLI 提供,例如读文件、搜索、执行命令、调用子 Agent。
  • 自定义工具:由 SDK 使用者通过 @tool()create_sdk_mcp_server() 定义 Python 函数,并以进程内 MCP server 的方式暴露给模型调用。
本文重点讲如何自定义工具。MCP server 的更多接入方式见 MCP 集成,权限体系的完整说明见 权限控制

内置工具

使用内置工具时,你不需要实现工具本身,只需要在 QoderAgentOptions 中控制本次会话能看到哪些工具、哪些工具预授权、哪些工具禁止。
常用内置工具包括 ReadEditWriteBashGlobGrepWebFetchWebSearchAgent 等。工具名称由底层 Qoder CLI 决定;在权限配置里应使用 CLI 暴露给模型的工具名,例如 ReadWriteBash。完整内置工具列表见 Tools Reference - 内置工具列表

自定义工具

当你希望模型调用自己的业务能力时,可以自定义工具。例如查询订单、搜索内部知识库、调用审批系统、访问只读数据库等。 Python 版自定义工具通常分三步:
  1. @tool() 装饰一个 async def handler。
  2. create_sdk_mcp_server() 把一个或多个工具注册到进程内 MCP server。
  3. QoderAgentOptions 中通过 mcp_servers 接入,并用权限配置控制调用。

自定义工具接入步骤

先看一个完整的最小示例,然后按三步拆开说明每一步可以配置什么:

第一步:使用 @tool() 创建工具

这一步负责定义工具本身,包括工具名、描述、输入参数、执行逻辑和工具元信息。

@tool() 入参

@tool() 是一个装饰器。它有 4 个入参:
完整 API 签名和返回的 SdkMcpTool 类型见 Tools Reference - tool() 工具 handler 必须是 async 函数,通常接收一个 args dict:
如果 handler 声明第二个位置参数,SDK 会传入 ToolInvocationContext。其中 signal 是一个 asyncio.Event,当 CLI 取消正在执行的工具调用时会被设置,适合长任务主动停止:

配置输入参数

Python 版 input_schema 支持三类写法。它们最终都会被规范化为 MCP 协议的 JSON Schema。 写法一:简单 dict 适合几个简单参数的场景。dict 的 key 是参数名,value 是 Python 类型;这种写法下所有 key 都是 required。
可以用 typing.Annotated 给字段增加描述:
常见类型会被转换为 JSON Schema: 写法二:TypedDict 适合字段较多、需要可选字段或希望复用类型定义的场景。可选字段用 NotRequired
Python 3.11 及以上可以直接从 typing 导入 NotRequired;Python 3.10 需要从 typing_extensions 导入。 写法三:完整 JSON Schema dict 需要 enum、数值范围、字符串格式约束或嵌套对象时,使用完整 JSON Schema:
关于可选参数:简单 dict 写法中所有字段都是 required。要表达可选字段,优先使用 TypedDict + NotRequired 或完整 JSON Schema 的 required 列表。

配置工具元信息

annotations 使用 mcp.types.ToolAnnotations。SDK 会把它放到 MCP tool 定义里,CLI 可据此做调度、权限或状态展示。
常用字段: 注意:annotations 不是权限配置的替代品。是否允许调用工具仍由 toolsallowed_toolsdisallowed_toolspermission_modecan_use_tool 和 hooks 决定。get_mcp_status() / MCP status 中回显的 annotations 字段名也可能是 CLI 投影后的 readOnlydestructiveopenWorld,而不是 MCP 原始的 *Hint 名称。

第二步:注册到 MCP server

create_sdk_mcp_server() 把一个或多个工具注册为同进程 MCP server。server 名会进入完整工具名,因此建议短、稳定。
返回值是 McpSdkServerConfig,可直接传给 QoderAgentOptions.mcp_servers 完整返回类型见 Tools Reference - create_sdk_mcp_server() create_sdk_mcp_server() 会做同步校验:
  • server 名必须是非空字符串。
  • tool 名必须是非空字符串。
  • tool 描述必须是非空字符串。
  • 同一个 server 内不能有重复 tool 名。

第三步:接入 query()

把 server 放进 options.mcp_servers 后,CLI 会发现其中的工具,并在模型需要时通过 SDK 调回你的 handler。
自定义工具的完整名称格式是:
例如 server 名是 orders,tool 名是 lookup_order,完整工具名就是 mcp__orders__lookup_order。这个完整名称会用于 allowed_toolsdisallowed_toolscan_use_tool、hooks matcher 和子 Agent 的 tools 配置。 QoderSDKClient 多轮会话也使用同样的 mcp_servers 配置:

控制 tool 权限

当模型调用工具时,SDK 提供多层权限控制。你可以决定:
  • 本次会话提供哪些工具。
  • 哪些工具可以默认放行。
  • 哪些工具明确禁止。
  • 每次工具调用前是否交给宿主应用动态判断。

权限控制方式总览

这些方式可以组合使用。常见做法是:先用 tools 收窄可见工具集合,再用 allowed_tools / disallowed_tools 设置静态规则,最后用 can_use_tool 做参数级判断。

方式一:toolsallowed_toolsdisallowed_tools

tools 控制本次会话可见的工具集合;allowed_toolsdisallowed_tools 控制权限规则。自定义 MCP 工具要使用完整工具名。
当同一个工具同时匹配允许和禁止规则时,禁止规则优先。

方式二:permission_mode

permission_mode 用一行配置设置整次会话的默认权限行为。

方式三:can_use_tool

can_use_tool 会在工具调用前执行。你可以根据工具名、参数内容和审批上下文返回允许或拒绝。
常见返回值: ToolPermissionContext 中常用字段包括 tool_use_idagent_idsignaltitledisplay_namedescriptionsuggestionsblocked_pathdecision_reason。更完整的权限策略见 权限控制 在子 Agent 中使用自定义工具时,也使用完整工具名:

方式四:hooks["PreToolUse"]

如果你已经使用 hooks 体系,可以通过 PreToolUse 统一拦截或审计工具调用。
PreToolUsepermissionDecision 可以是 "allow""deny""ask""defer"

SDK 如何处理 tool 返回的错误

工具 handler 有三类错误路径。

业务失败:返回 is_error: True

可预期的业务失败推荐返回 is_error: True。SDK 会把这个结果转换成 MCP CallToolResult 交给 CLI,模型能看到失败内容,并可能重试或选择其他方式。
适合使用 is_error: True 的场景:
  • 参数合法但业务上找不到结果,例如订单不存在。
  • 安全策略拒绝执行,例如只允许 SELECT 查询。
  • 外部服务返回可理解的业务错误。

非预期异常:handler 抛错

如果 handler 抛出异常,MCP 层会把异常转换成错误结果,agent loop 不会因为普通工具异常直接崩掉。但模型通常只能看到异常消息,格式和内容不如显式返回 is_error: True 可控。
建议:业务上可预期的失败用 is_error: True;真正意外的异常再抛出。

畸形返回:SDK 包装为错误

Python SDK 会在运行时兜底检查 handler 返回值:
  • 返回 None:转换为错误文本,说明 handler 必须返回包含 "content" 的 dict。
  • 返回非 dict,例如字符串、数字、列表:转换为文本内容,并标记 isError=True
  • 返回 dict 但没有 "content":转换为错误文本,并列出实际 keys。
  • 返回不支持的 content 类型:该内容块会被跳过并写 warning 日志。
这些兜底能避免模型看到空成功结果,但文档和业务代码里仍应始终返回标准结构。

Tool 返回值

工具 handler 返回一个 dict,SDK 会把它转换为 MCP 的 CallToolResult。最常用的是文本内容:
也可以返回结构化 JSON 字符串,方便模型理解和继续处理:
常见内容块: Python 版还有两个需要注意的结果差异:
  • handler 返回 dict 顶层 _meta 不会透传到 CallToolResult
  • handler 返回错误标记时使用 Python 字段名 "is_error": True,不是 MCP/TypeScript 风格的 isError。SDK 内部会映射到 MCP 结果。

常见踩坑

  • 权限配置里写自定义工具时,要写 mcp__server__tool 完整名称。
  • Python 简单 dict schema 的所有字段都是 required;需要可选字段时用 TypedDict + NotRequired 或完整 JSON Schema。
  • 需要 enum、数值范围、嵌套对象、字符串 pattern/format 时,使用完整 JSON Schema dict。
  • handler 必须是 async def,并返回包含 "content" 列表的 dict。
  • 工具描述要写“什么时候用、做什么、返回什么”,不要只写 queryhelper 这类模糊描述。
  • readOnlyHint 是工具元信息和调度/权限提示,不是权限开关;是否允许执行仍由权限配置决定。
  • 不要把大而全的业务入口都塞进一个万能工具。一个工具最好完成一类清晰动作。

继续阅读

  • MCP 集成:in-process、stdio、SSE、HTTP、OAuth 等 MCP server 接入方式。
  • 权限控制permission_modeallowed_toolscan_use_tool、hooks、权限规则更新。
  • HooksPreToolUsePostToolUsePermissionRequest 等生命周期扩展。
  • 子 Agent 使用指南:让不同 Agent 使用不同工具集。