> ## Documentation Index
> Fetch the complete documentation index at: https://docs.qoder.com/llms.txt
> Use this file to discover all available pages before exploring further.

# 工具

工具是模型执行任务时可以调用的能力。Qoder Agent SDK Python 版支持两类工具：

* **内置工具**：由 Qoder CLI 提供，例如读文件、搜索、执行命令、调用子 Agent。
* **自定义工具**：由 SDK 使用者通过 `@tool()` 和 `create_sdk_mcp_server()` 定义 Python 函数，并以进程内 MCP server 的方式暴露给模型调用。

本文重点讲如何自定义工具。MCP server 的更多接入方式见 [MCP 集成](/zh/cli/sdk/python/mcp)，权限体系的完整说明见 [权限控制](/zh/cli/sdk/python/permissions)。

<div id="内置工具" />

## 内置工具

使用内置工具时，你不需要实现工具本身，只需要在 `QoderAgentOptions` 中控制本次会话能看到哪些工具、哪些工具预授权、哪些工具禁止。

```python theme={null}
import asyncio

from qoder_agent_sdk import QoderAgentOptions, qodercli_auth, query


async def main():
    options = QoderAgentOptions(
        auth=qodercli_auth(),
        cwd="/path/to/project",
        tools=["Read", "Grep", "Glob"],
        allowed_tools=["Read", "Grep", "Glob"],
    )

    async for message in query(
        prompt=(
            "Read this repository and summarize risks in the authentication "
            "module. Do not modify files."
        ),
        options=options,
    ):
        print(message)


asyncio.run(main())
```

常用内置工具包括 `Read`、`Edit`、`Write`、`Bash`、`Glob`、`Grep`、`WebFetch`、`WebSearch`、`Agent` 等。工具名称由底层 Qoder CLI 决定；在权限配置里应使用 CLI 暴露给模型的工具名，例如 `Read`、`Write`、`Bash`。完整内置工具列表见 [Tools Reference - 内置工具列表](/zh/cli/sdk/python/references#内置工具列表)。

<div id="自定义工具" />

## 自定义工具

当你希望模型调用自己的业务能力时，可以自定义工具。例如查询订单、搜索内部知识库、调用审批系统、访问只读数据库等。

Python 版自定义工具通常分三步：

1. 用 `@tool()` 装饰一个 `async def` handler。
2. 用 `create_sdk_mcp_server()` 把一个或多个工具注册到进程内 MCP server。
3. 在 `QoderAgentOptions` 中通过 `mcp_servers` 接入，并用权限配置控制调用。

<div id="自定义工具接入步骤" />

## 自定义工具接入步骤

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

```python theme={null}
import asyncio
import json

from mcp.types import ToolAnnotations

from qoder_agent_sdk import (
    QoderAgentOptions,
    create_sdk_mcp_server,
    qodercli_auth,
    query,
    tool,
)


orders = {
    "O-1001": {"order_id": "O-1001", "status": "shipped", "eta": "2026-05-20"},
}


@tool(
    "lookup_order",
    "Look up an order by order ID and return its status as JSON.",
    {"order_id": str},
    annotations=ToolAnnotations(readOnlyHint=True),
)
async def lookup_order(args):
    order_id = args["order_id"]
    order = orders.get(order_id)

    if order is None:
        return {
            "is_error": True,
            "content": [{"type": "text", "text": f"Order not found: {order_id}"}],
        }

    return {"content": [{"type": "text", "text": json.dumps(order)}]}


order_tools = create_sdk_mcp_server(
    name="orders",
    tools=[lookup_order],
)


async def main():
    options = QoderAgentOptions(
        auth=qodercli_auth(),
        mcp_servers={"orders": order_tools},
        allowed_tools=["mcp__orders__lookup_order"],
    )

    async for message in query(
        prompt="Check the status of order O-1001 and summarize it in one sentence.",
        options=options,
    ):
        print(message)


asyncio.run(main())
```

<div id="第一步使用-tool-创建工具" />

<div id="第一步使用tool创建工具" />

### 第一步：使用 `@tool()` 创建工具

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

<div id="tool-入参" />

<div id="tool入参" />

#### `@tool()` 入参

`@tool()` 是一个装饰器。它有 4 个入参：

```python theme={null}
def tool(
    name: str,
    description: str,
    input_schema: type | dict[str, Any],
    annotations: ToolAnnotations | None = None,
): ...
```

| 入参             | 类型                        | 是否必填 | 语义                                                           |
| -------------- | ------------------------- | ---- | ------------------------------------------------------------ |
| `name`         | `str`                     | 是    | 工具在当前 MCP server 内的唯一标识                                      |
| `description`  | `str`                     | 是    | 给模型看的工具说明，描述工具何时使用、做什么、返回什么                                  |
| `input_schema` | `type \| dict[str, Any]`  | 是    | 定义工具输入参数，支持简单 dict、`TypedDict` 和完整 JSON Schema dict          |
| `annotations`  | `ToolAnnotations \| None` | 否    | MCP 工具注解，例如 `readOnlyHint`、`destructiveHint`、`openWorldHint` |

完整 API 签名和返回的 `SdkMcpTool` 类型见 [Tools Reference - `tool()`](/zh/cli/sdk/python/references#tool)。

工具 handler 必须是 async 函数，通常接收一个 `args` dict：

```python theme={null}
@tool("search_docs", "Search internal product documentation.", {"query": str})
async def search_docs(args):
    return {"content": [{"type": "text", "text": f"Searching: {args['query']}"}]}
```

如果 handler 声明第二个位置参数，SDK 会传入 `ToolInvocationContext`。其中 `signal` 是一个 `asyncio.Event`，当 CLI 取消正在执行的工具调用时会被设置，适合长任务主动停止：

```python theme={null}
@tool("watch", "Watch a counter until max.", {"max": int})
async def watch(args, extra):
    for i in range(args["max"]):
        if extra.signal.is_set():
            return {"content": [{"type": "text", "text": f"aborted at {i}"}]}
        await asyncio.sleep(0.01)

    return {"content": [{"type": "text", "text": "done"}]}
```

<div id="配置输入参数" />

#### 配置输入参数

Python 版 `input_schema` 支持三类写法。它们最终都会被规范化为 MCP 协议的 JSON Schema。

**写法一：简单 dict**

适合几个简单参数的场景。dict 的 key 是参数名，value 是 Python 类型；这种写法下所有 key 都是 required。

```python theme={null}
input_schema = {
    "query": str,
    "max_results": int,
    "include_archived": bool,
}
```

可以用 `typing.Annotated` 给字段增加描述：

```python theme={null}
from typing import Annotated


input_schema = {
    "query": Annotated[str, "Search keywords"],
    "max_results": Annotated[int, "Maximum number of snippets to return"],
}
```

常见类型会被转换为 JSON Schema：

| Python 写法             | JSON Schema 语义                   |
| --------------------- | -------------------------------- |
| `str`                 | `{"type": "string"}`             |
| `int`                 | `{"type": "integer"}`            |
| `float`               | `{"type": "number"}`             |
| `bool`                | `{"type": "boolean"}`            |
| `list[str]`           | 字符串数组                            |
| `dict`                | 对象                               |
| `Annotated[T, "..."]` | 在 `T` 的 schema 上增加 `description` |

**写法二：`TypedDict`**

适合字段较多、需要可选字段或希望复用类型定义的场景。可选字段用 `NotRequired`：

```python theme={null}
from typing import Annotated, TypedDict

from typing_extensions import NotRequired


class SearchInput(TypedDict):
    query: Annotated[str, "Search keywords"]
    max_results: NotRequired[Annotated[int, "Maximum snippets to return"]]


@tool("search_docs", "Search internal product documentation.", SearchInput)
async def search_docs(args):
    limit = args.get("max_results", 5)
    return {"content": [{"type": "text", "text": f"{args['query']} ({limit})"}]}
```

Python 3.11 及以上可以直接从 `typing` 导入 `NotRequired`；Python 3.10 需要从 `typing_extensions` 导入。

**写法三：完整 JSON Schema dict**

需要 enum、数值范围、字符串格式约束或嵌套对象时，使用完整 JSON Schema：

```python theme={null}
input_schema = {
    "type": "object",
    "properties": {
        "query": {"type": "string", "description": "Search keywords"},
        "source": {
            "type": "string",
            "enum": ["docs", "tickets", "wiki"],
            "description": "Where to search",
        },
        "max_results": {"type": "integer", "minimum": 1, "maximum": 10},
    },
    "required": ["query"],
}
```

关于可选参数：简单 dict 写法中所有字段都是 required。要表达可选字段，优先使用 `TypedDict + NotRequired` 或完整 JSON Schema 的 `required` 列表。

<div id="配置工具元信息" />

#### 配置工具元信息

`annotations` 使用 `mcp.types.ToolAnnotations`。SDK 会把它放到 MCP tool 定义里，CLI 可据此做调度、权限或状态展示。

```python theme={null}
from mcp.types import ToolAnnotations


@tool(
    "search_docs",
    "Search internal product documentation.",
    {"query": str},
    annotations=ToolAnnotations(
        title="Search docs",
        readOnlyHint=True,
        destructiveHint=False,
        openWorldHint=False,
    ),
)
async def search_docs(args):
    return {"content": [{"type": "text", "text": args["query"]}]}
```

常用字段：

| 字段                   | 类型     | 语义                                                                         |
| -------------------- | ------ | -------------------------------------------------------------------------- |
| `title`              | `str`  | 工具的人类可读标题                                                                  |
| `readOnlyHint`       | `bool` | 标记工具只读，不修改任何状态                                                             |
| `destructiveHint`    | `bool` | 标记工具可能修改或删除数据                                                              |
| `openWorldHint`      | `bool` | 标记工具会访问外部系统或网络                                                             |
| `maxResultSizeChars` | `int`  | Python SDK 会通过 `_meta["anthropic/maxResultSizeChars"]` 传给 CLI，用于放宽工具返回长度限制 |

注意：annotations 不是权限配置的替代品。是否允许调用工具仍由 `tools`、`allowed_tools`、`disallowed_tools`、`permission_mode`、`can_use_tool` 和 hooks 决定。`get_mcp_status()` / MCP status 中回显的 annotations 字段名也可能是 CLI 投影后的 `readOnly`、`destructive`、`openWorld`，而不是 MCP 原始的 `*Hint` 名称。

<div id="第二步注册到-mcp-server" />

<div id="第二步注册到mcpserver" />

### 第二步：注册到 MCP server

`create_sdk_mcp_server()` 把一个或多个工具注册为同进程 MCP server。server 名会进入完整工具名，因此建议短、稳定。

```python theme={null}
kb_tools = create_sdk_mcp_server(
    name="kb",
    version="1.0.0",
    tools=[search_docs],
)
```

| 字段        | 怎么填                           | 说明                                      |
| --------- | ----------------------------- | --------------------------------------- |
| `name`    | 如 `kb`、`orders`               | server 名，会组成完整工具名 `mcp__{name}__{tool}` |
| `version` | 如 `"1.0.0"`                   | 信息性版本号，默认 `"1.0.0"`                     |
| `tools`   | `[search_docs, lookup_order]` | 注册到这个 server 的工具列表                      |

返回值是 `McpSdkServerConfig`，可直接传给 `QoderAgentOptions.mcp_servers`。

完整返回类型见 [Tools Reference - `create_sdk_mcp_server()`](/zh/cli/sdk/python/references#create_sdk_mcp_server)。

`create_sdk_mcp_server()` 会做同步校验：

* server 名必须是非空字符串。
* tool 名必须是非空字符串。
* tool 描述必须是非空字符串。
* 同一个 server 内不能有重复 tool 名。

<div id="第三步接入-query" />

<div id="第三步接入query" />

### 第三步：接入 `query()`

把 server 放进 `options.mcp_servers` 后，CLI 会发现其中的工具，并在模型需要时通过 SDK 调回你的 handler。

```python theme={null}
options = QoderAgentOptions(
    auth=qodercli_auth(),
    mcp_servers={"kb": kb_tools},
    allowed_tools=["mcp__kb__search_docs"],
)

async for message in query(
    prompt="Search docs for the refund policy and summarize it.",
    options=options,
):
    print(message)
```

自定义工具的完整名称格式是：

```text theme={null}
mcp__{serverName}__{toolName}
```

例如 server 名是 `orders`，tool 名是 `lookup_order`，完整工具名就是 `mcp__orders__lookup_order`。这个完整名称会用于 `allowed_tools`、`disallowed_tools`、`can_use_tool`、hooks matcher 和子 Agent 的 `tools` 配置。

`QoderSDKClient` 多轮会话也使用同样的 `mcp_servers` 配置：

```python theme={null}
from qoder_agent_sdk import QoderSDKClient


options = QoderAgentOptions(
    auth=qodercli_auth(),
    mcp_servers={"kb": kb_tools},
    allowed_tools=["mcp__kb__search_docs"],
)

async with QoderSDKClient(options=options) as client:
    await client.query("Search docs for the refund policy.")

    async for message in client.receive_response():
        print(message)
```

<div id="控制-tool-权限" />

<div id="控制tool权限" />

## 控制 tool 权限

当模型调用工具时，SDK 提供多层权限控制。你可以决定：

* 本次会话提供哪些工具。
* 哪些工具可以默认放行。
* 哪些工具明确禁止。
* 每次工具调用前是否交给宿主应用动态判断。

<div id="权限控制方式总览" />

### 权限控制方式总览

| 方式                                   | 作用                  | 粒度  | 适用场景                    |
| ------------------------------------ | ------------------- | --- | ----------------------- |
| `tools`                              | 限制本次会话可见工具集合        | 会话级 | 想从源头收窄模型能看到的工具          |
| `allowed_tools` / `disallowed_tools` | 预授权或禁止指定工具          | 工具级 | 明确知道要允许或禁止哪些工具          |
| `permission_mode`                    | 设置整次会话的默认权限策略       | 全局  | 快速切换计划模式、自动接受编辑、跳过权限等   |
| `can_use_tool`                       | 每次调用前执行自定义判断逻辑      | 调用级 | 需要根据参数内容动态决策            |
| `hooks["PreToolUse"]`                | 通过 hooks 生命周期拦截工具调用 | 调用级 | 已经使用 hooks 体系，需要统一审计或拦截 |

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

<div id="方式一toolsallowed_toolsdisallowed_tools" />

### 方式一：`tools`、`allowed_tools`、`disallowed_tools`

`tools` 控制本次会话可见的工具集合；`allowed_tools` 和 `disallowed_tools` 控制权限规则。自定义 MCP 工具要使用完整工具名。

```python theme={null}
# Only expose read/search tools to this session.
QoderAgentOptions(
    tools=["Read", "Glob", "Grep"],
    allowed_tools=["Read", "Glob", "Grep"],
)

# Explicitly deny high-risk tools.
QoderAgentOptions(
    disallowed_tools=["Bash", "Write", "Edit"],
)

# Use full names for custom MCP tools.
QoderAgentOptions(
    mcp_servers={"orders": order_tools},
    allowed_tools=["mcp__orders__lookup_order"],
)

# Disable all tools. The model can only answer from its context.
QoderAgentOptions(tools=[])
```

当同一个工具同时匹配允许和禁止规则时，禁止规则优先。

<div id="方式二permission_mode" />

### 方式二：`permission_mode`

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

```python theme={null}
QoderAgentOptions(
    permission_mode="acceptEdits",
)
```

| 模式                    | 效果                                                                            |
| --------------------- | ----------------------------------------------------------------------------- |
| `"default"`           | 标准权限行为，敏感操作按规则或运行时策略处理                                                        |
| `"acceptEdits"`       | 自动接受文件编辑类操作，其他敏感操作仍按权限策略处理                                                    |
| `"bypassPermissions"` | 跳过权限检查，必须同时设置 `allow_dangerously_skip_permissions=True`                       |
| `"yolo"`              | `"bypassPermissions"` 的兼容别名，也必须同时设置 `allow_dangerously_skip_permissions=True` |
| `"plan"`              | 计划模式，适合先让模型产出方案                                                               |
| `"dontAsk"`           | 不进行交互询问，未预授权或未被规则允许的操作会被拒绝                                                    |
| `"auto"`              | 由运行时能力自动判断 allow 或 deny                                                       |

<div id="方式三can_use_tool" />

### 方式三：`can_use_tool`

`can_use_tool` 会在工具调用前执行。你可以根据工具名、参数内容和审批上下文返回允许或拒绝。

```python theme={null}
from typing import Any

from qoder_agent_sdk import (
    PermissionResultAllow,
    PermissionResultDeny,
    ToolPermissionContext,
)


async def can_use_tool(
    tool_name: str,
    input_data: dict[str, Any],
    context: ToolPermissionContext,
):
    if tool_name != "mcp__orders__lookup_order":
        return PermissionResultDeny(
            message="Only order lookup is allowed in this workflow.",
        )

    return PermissionResultAllow(updated_input=input_data)


options = QoderAgentOptions(
    auth=qodercli_auth(),
    mcp_servers={"orders": order_tools},
    allowed_tools=["mcp__orders__lookup_order"],
    can_use_tool=can_use_tool,
)
```

常见返回值：

| 返回                                                       | 效果                  |
| -------------------------------------------------------- | ------------------- |
| `PermissionResultAllow()`                                | 允许执行，使用原始参数         |
| `PermissionResultAllow(updated_input={...})`             | 允许执行，并替换工具参数        |
| `PermissionResultDeny(message="reason")`                 | 拒绝执行，模型能看到原因并尝试其他方式 |
| `PermissionResultDeny(message="reason", interrupt=True)` | 拒绝并中断当前 agent loop  |

`ToolPermissionContext` 中常用字段包括 `tool_use_id`、`agent_id`、`signal`、`title`、`display_name`、`description`、`suggestions`、`blocked_path` 和 `decision_reason`。更完整的权限策略见 [权限控制](/zh/cli/sdk/python/permissions)。

在子 Agent 中使用自定义工具时，也使用完整工具名：

```python theme={null}
from qoder_agent_sdk import AgentDefinition


options = QoderAgentOptions(
    auth=qodercli_auth(),
    mcp_servers={"orders": order_tools},
    allowed_tools=["Agent"],
    agents={
        "order-support": AgentDefinition(
            description="Handles order lookup and explains order status.",
            prompt="Use order tools to answer order status questions clearly.",
            tools=["mcp__orders__lookup_order"],
        ),
    },
)
```

<div id="方式四hookspretooluse" />

### 方式四：`hooks["PreToolUse"]`

如果你已经使用 hooks 体系，可以通过 `PreToolUse` 统一拦截或审计工具调用。

```python theme={null}
from qoder_agent_sdk import HookMatcher


async def block_dangerous_bash(inp, tool_use_id, context):
    command = inp.get("tool_input", {}).get("command", "")
    if "rm -rf" in command:
        return {
            "hookSpecificOutput": {
                "hookEventName": "PreToolUse",
                "permissionDecision": "deny",
                "permissionDecisionReason": "rm -rf is not allowed",
            }
        }

    return {
        "hookSpecificOutput": {
            "hookEventName": "PreToolUse",
            "permissionDecision": "allow",
        }
    }


options = QoderAgentOptions(
    allowed_tools=["Bash"],
    hooks={
        "PreToolUse": [
            HookMatcher(matcher="Bash", hooks=[block_dangerous_bash]),
        ],
    },
)
```

`PreToolUse` 的 `permissionDecision` 可以是 `"allow"`、`"deny"`、`"ask"` 或 `"defer"`。

<div id="sdk-如何处理-tool-返回的错误" />

<div id="sdk如何处理tool返回的错误" />

## SDK 如何处理 tool 返回的错误

工具 handler 有三类错误路径。

<div id="业务失败返回-is_error-true" />

<div id="业务失败返回is_errortrue" />

### 业务失败：返回 `is_error: True`

可预期的业务失败推荐返回 `is_error: True`。SDK 会把这个结果转换成 MCP `CallToolResult` 交给 CLI，模型能看到失败内容，并可能重试或选择其他方式。

```python theme={null}
return {
    "is_error": True,
    "content": [
        {
            "type": "text",
            "text": json.dumps(
                {
                    "error": "VALIDATION_ERROR",
                    "message": "Only SELECT statements are allowed.",
                }
            ),
        }
    ],
}
```

适合使用 `is_error: True` 的场景：

* 参数合法但业务上找不到结果，例如订单不存在。
* 安全策略拒绝执行，例如只允许 `SELECT` 查询。
* 外部服务返回可理解的业务错误。

<div id="非预期异常handler-抛错" />

<div id="非预期异常handler抛错" />

### 非预期异常：handler 抛错

如果 handler 抛出异常，MCP 层会把异常转换成错误结果，agent loop 不会因为普通工具异常直接崩掉。但模型通常只能看到异常消息，格式和内容不如显式返回 `is_error: True` 可控。

```python theme={null}
@tool("fetch_user", "Fetch a user by ID.", {"user_id": str})
async def fetch_user(args):
    response = await user_service.fetch(args["user_id"])
    if not response.ok:
        raise RuntimeError("User service failed")

    return {"content": [{"type": "text", "text": await response.text()}]}
```

建议：业务上可预期的失败用 `is_error: True`；真正意外的异常再抛出。

<div id="畸形返回sdk-包装为错误" />

<div id="畸形返回sdk包装为错误" />

### 畸形返回：SDK 包装为错误

Python SDK 会在运行时兜底检查 handler 返回值：

* 返回 `None`：转换为错误文本，说明 handler 必须返回包含 `"content"` 的 dict。
* 返回非 dict，例如字符串、数字、列表：转换为文本内容，并标记 `isError=True`。
* 返回 dict 但没有 `"content"`：转换为错误文本，并列出实际 keys。
* 返回不支持的 content 类型：该内容块会被跳过并写 warning 日志。

这些兜底能避免模型看到空成功结果，但文档和业务代码里仍应始终返回标准结构。

<div id="tool-返回值" />

<div id="tool返回值" />

## Tool 返回值

工具 handler 返回一个 dict，SDK 会把它转换为 MCP 的 `CallToolResult`。最常用的是文本内容：

```python theme={null}
return {
    "content": [{"type": "text", "text": "done"}],
}
```

也可以返回结构化 JSON 字符串，方便模型理解和继续处理：

```python theme={null}
return {
    "content": [
        {
            "type": "text",
            "text": json.dumps(
                {
                    "order_id": "O-1001",
                    "status": "shipped",
                    "eta": "2026-05-20",
                }
            ),
        }
    ],
}
```

常见内容块：

| 类型     | 形状                                                                             | Python SDK 行为                                |
| ------ | ------------------------------------------------------------------------------ | -------------------------------------------- |
| 文本     | `{"type": "text", "text": "..."}`                                              | 转换为 `TextContent`                            |
| 图片     | `{"type": "image", "data": "...", "mimeType": "image/png"}`                    | 转换为 `ImageContent`，`data` 是 base64           |
| 资源链接   | `{"type": "resource_link", "uri": "...", "name": "...", "description": "..."}` | 降级为文本，把 `name` / `uri` / `description` 拼接给模型 |
| 内嵌文本资源 | `{"type": "resource", "resource": {"text": "..."}}`                            | 转换为 `TextContent`                            |

Python 版还有两个需要注意的结果差异：

* handler 返回 dict 顶层 `_meta` 不会透传到 `CallToolResult`。
* handler 返回错误标记时使用 Python 字段名 `"is_error": True`，不是 MCP/TypeScript 风格的 `isError`。SDK 内部会映射到 MCP 结果。

<div id="常见踩坑" />

## 常见踩坑

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

<div id="继续阅读" />

## 继续阅读

* [MCP 集成](/zh/cli/sdk/python/mcp)：in-process、stdio、SSE、HTTP、OAuth 等 MCP server 接入方式。
* [权限控制](/zh/cli/sdk/python/permissions)：`permission_mode`、`allowed_tools`、`can_use_tool`、hooks、权限规则更新。
* [Hooks](/zh/cli/sdk/python/hooks)：`PreToolUse`、`PostToolUse`、`PermissionRequest` 等生命周期扩展。
* [子 Agent 使用指南](/zh/cli/sdk/python/agents)：让不同 Agent 使用不同工具集。
