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 的权限控制能力用于管理模型在一次 query() 会话里能做什么。它可以限制模型可见的工具,设置默认授权策略,在工具执行前交给宿主应用审批,也可以在用户授权后把新规则应用到当前会话。
权限控制不是一个单独的 API,而是一组放在 QoderAgentOptions 里的配置。通常你会先决定本次会话允许模型使用哪些工具,再决定这些工具在什么情况下可以执行,最后按需要接入运行时审批、动态规则更新、settings 或 hooks。
from qoder_agent_sdk import QoderAgentOptions, query
async for message in query(
prompt="Inspect the repository and summarize risky changes.",
options=QoderAgentOptions(
cwd="/path/to/project",
tools=["Read", "Grep", "Bash"],
allowed_tools=["Read", "Grep"],
disallowed_tools=["Bash"],
permission_mode="default",
),
):
print(message)
上面这个例子表达了一个常见策略:模型可以看到 Read、Grep、Bash,其中 Read 和 Grep 预授权,Bash 禁止执行。实际项目里,你可以继续加上 can_use_tool,把未预授权的操作交给自己的产品 UI、审批系统或风控服务判断。
能力总览
query() 的权限相关 options 大致分成四类。第一类决定默认策略,例如计划模式、自动允许编辑、禁止交互询问。第二类决定工具范围和工具规则。第三类让宿主应用参与运行时审批。第四类用于更高级的 settings、hooks 和 MCP 工具策略。
| 想解决的问题 | 推荐入口 | 说明 |
|---|
| 设置会话默认权限行为 | permission_mode | 决定未命中显式规则时如何处理工具调用 |
| 明确确认跳过权限检查 | allow_dangerously_skip_permissions | 仅配合 bypassPermissions 或 yolo 使用 |
| 限制本次会话可见工具 | tools | 未包含的工具不会作为可用工具提供给模型 |
| 预授权某些工具 | allowed_tools | 命中后通常不再进入授权询问 |
| 禁止某些工具 | disallowed_tools | 命中后拒绝,优先级高于 allow |
| 由宿主应用审批工具调用 | can_use_tool | SDK host 在运行时返回 allow 或 deny |
| 把审批交给外部 prompt tool | permission_prompt_tool_name | 适合运行环境已经提供 permission prompt tool 的场景 |
| 授权后更新当前会话规则 | PermissionUpdate | 常用于“本次允许”或“本会话始终允许” |
| 允许访问 cwd 外目录 | add_dirs | 扩展本次会话可访问的目录范围 |
| 从 settings 配置权限规则 | settings | 适合启动会话时提供静态权限配置 |
| 在生命周期里拦截或审计 | hooks | 适合高级拦截、审计和告警 |
| 给 MCP server 声明工具策略 | MCP tool policy | 在 MCP server 配置里声明工具级 allow/ask/deny |
常见选择方式:
- 只想让模型读代码,不允许修改:设置
tools,再用 allowed_tools 预授权 Read、Grep,用 disallowed_tools 禁止 Write、Edit、Bash。
- 想让模型先给计划,不做实际变更:使用
permission_mode="plan"。
- 想让安全的编辑自动通过:使用
permission_mode="acceptEdits" 或 "auto"。
- 想让自己的 UI 弹出审批框:使用
can_use_tool。
- 想让用户选择“始终允许本会话”:在
can_use_tool 的 allow 结果里返回 updated_permissions。
- 想完全不弹交互确认,未预授权直接拒绝:使用
permission_mode="dontAsk"。
- 想访问 monorepo 之外的共享目录:使用
add_dirs。
后续示例为了聚焦权限配置,会省略遍历消息的代码;真实使用时仍需要消费 query() 返回的异步消息流。Python SDK 默认可以使用本地 qodercli 登录态;如果你的应用需要显式认证,可以在 QoderAgentOptions.auth 中传入自己的认证配置。
快速开始:让宿主应用审批工具调用
当你需要把工具调用接入自己的审批逻辑时,使用 can_use_tool。SDK 会在运行时把工具名、工具入参和一组可展示的审批信息交给你的 callback。callback 返回 PermissionResultAllow 时工具继续执行,返回 PermissionResultDeny 时工具被拒绝。
from typing import Any
from qoder_agent_sdk import create_sdk_mcp_server, query, tool
from qoder_agent_sdk.types import (
PermissionResultAllow,
PermissionResultDeny,
QoderAgentOptions,
ToolPermissionContext,
)
@tool("read_order", "Read an order by ID.", {"order_id": str})
async def read_order(args: dict[str, Any]) -> dict[str, Any]:
return {"content": [{"type": "text", "text": f"order:{args['order_id']}"}]}
server = create_sdk_mcp_server(name="orders", tools=[read_order])
async def can_use_tool(
tool_name: str,
input_data: dict[str, Any],
context: ToolPermissionContext,
):
if tool_name != "mcp__orders__read_order":
return PermissionResultDeny(
message="Only order reads are allowed in this workflow."
)
return PermissionResultAllow(updated_input=input_data)
async for _ in query(
prompt="Read order 1001.",
options=QoderAgentOptions(
mcp_servers={"orders": server},
permission_mode="default",
can_use_tool=can_use_tool,
),
):
pass
这个例子里,read_order 是一个 SDK MCP 工具。模型调用它时,完整工具名会是 mcp__orders__read_order。can_use_tool 只允许这个工具执行,并把原始入参作为 updated_input 返回。
控制默认策略:permission_mode
permission_mode 决定会话的默认权限策略。它适合表达“这次会话整体处于什么模式”,例如先计划、自动接受编辑、不询问直接拒绝,或者在受控环境里跳过权限检查。
QoderAgentOptions(
cwd="/path/to/project",
permission_mode="plan",
)
plan 模式适合让模型先产出计划,默认不执行实际变更。
| Mode | 行为 |
|---|
default | 标准权限行为。工具调用按 tools、allow/deny 规则、动态审批或运行时策略处理 |
acceptEdits | 自动接受文件编辑类操作,适合已确认可以修改工作区的场景 |
bypassPermissions | 跳过权限检查,必须同时设置 allow_dangerously_skip_permissions=True |
yolo | bypassPermissions 的兼容别名,也必须同时设置 allow_dangerously_skip_permissions=True |
plan | 计划模式,适合先产出执行计划,默认不执行实际变更 |
dontAsk | 不进行交互询问。未预授权、未被规则允许的操作会被拒绝 |
auto | 由运行时能力自动判断 allow 或 deny。安全的工作区内文件编辑可能自动放行 |
需要在同一个会话里切换模式时,可以使用 QoderSDKClient:
from qoder_agent_sdk import QoderSDKClient
async with QoderSDKClient(
options=QoderAgentOptions(
cwd="/path/to/project",
permission_mode="plan",
)
) as client:
await client.set_permission_mode("default")
bypassPermissions 和 yolo 都是高风险模式。SDK 要求显式传入 allow_dangerously_skip_permissions=True,避免调用方误把普通会话变成跳过权限检查的会话。
QoderAgentOptions(
cwd="/path/to/project",
permission_mode="bypassPermissions",
allow_dangerously_skip_permissions=True,
)
工具控制回答的是“模型能看到哪些工具,以及哪些工具默认允许或禁止”。这三个字段经常一起出现,但语义不同。
QoderAgentOptions(
cwd="/path/to/project",
tools=["Read", "Grep", "Bash"],
allowed_tools=["Read", "Grep"],
disallowed_tools=["Bash"],
)
这段配置表示:本次会话只提供 Read、Grep、Bash 三个工具;其中 Read 和 Grep 预授权;Bash 被禁止,即使模型想调用也不会执行。
| 字段 | 作用 | 适合场景 |
|---|
tools | 限制本次会话可用工具集合 | 缩小模型能力边界 |
allowed_tools | 添加允许规则 | 让低风险工具无需反复审批 |
disallowed_tools | 添加拒绝规则 | 明确禁止高风险工具 |
当同一个工具同时匹配 allow 和 deny 时,deny 优先。这能确保禁止规则不会被更宽泛的允许规则绕过。
MCP 工具也使用完整工具名匹配。例如 SDK MCP server 名为 orders,工具名为 read_order,完整工具名就是 mcp__orders__read_order。
QoderAgentOptions(
mcp_servers={"orders": server},
allowed_tools=["mcp__orders__read_order"],
)
can_use_tool 适合宿主应用需要参与审批的场景。比如你要把权限请求展示在自己的 UI 里,让用户点“允许一次”“始终允许本会话”或“拒绝”;或者你要调用企业风控服务判断某个命令是否可执行。
async def can_use_tool(
tool_name: str,
input_data: dict[str, Any],
context: ToolPermissionContext,
):
show_approval_dialog(
title=context.title or tool_name,
description=context.description,
input_data=input_data,
)
approved = await wait_for_user_approval(context.signal)
if not approved:
return PermissionResultDeny(message="Rejected by user.")
return PermissionResultAllow(updated_input=input_data)
QoderAgentOptions(
cwd="/path/to/project",
permission_mode="default",
can_use_tool=can_use_tool,
)
can_use_tool 的签名如下:
CanUseTool = Callable[
[str, dict[str, Any], ToolPermissionContext],
Awaitable[PermissionResult],
]
关键字段解释:
| 字段 | 说明 |
|---|
tool_name | 完整工具名,例如 Read、Bash、mcp__orders__read_order |
input_data | 本次工具调用的原始参数 |
context.tool_use_id | 本次工具调用 ID |
context.signal | 授权请求被取消时会被设置,UI 或远程审批应监听它 |
context.title / display_name / description | 运行时生成的人类可读文案,适合直接用于审批 UI |
context.suggestions | 运行时建议的权限更新,可用于“始终允许本会话” |
context.blocked_path | 路径相关授权场景中的受限路径 |
context.decision_reason | 运行时提供的审批原因说明,可用于展示或审计 |
context.agent_id | 子 Agent 发起工具调用时的 Agent ID |
返回 PermissionResultAllow 表示继续执行工具:
return PermissionResultAllow(updated_input=input_data)
updated_input 是工具最终收到的参数。你可以原样返回,也可以在审批通过后修改参数。例如给查询增加租户 ID、把路径改写到安全目录,或者去掉不允许的字段。
返回 PermissionResultDeny 表示拒绝工具:
return PermissionResultDeny(
message="This command is not allowed in the current workspace."
)
deny.message 必填,它会成为拒绝原因的一部分,供模型、日志或宿主应用展示。SDK 收到 CLI 的授权请求但没有配置 can_use_tool 时,会返回错误,不会默认放行。
当权限系统直接拒绝工具调用时,消息流中可能出现结构化的权限拒绝消息:
from qoder_agent_sdk import SDKPermissionDeniedMessage
if isinstance(message, SDKPermissionDeniedMessage):
print(
message.tool_name,
message.tool_use_id,
message.message,
message.decision_reason,
message.decision_reason_type,
)
这类消息常见于 permission_mode="dontAsk"、自动拒绝或规则拒绝等场景。宿主应用可以用它更新 UI 状态或写审计日志。
会话内更新权限:PermissionUpdate
PermissionUpdate 用于在一次审批之后更新当前会话里的权限规则。最常见的场景是用户在审批 UI 里选择“始终允许本会话”。这时你可以把运行时给出的 suggestions 原样返回,也可以自己构造明确的规则。
async def can_use_tool(
tool_name: str,
input_data: dict[str, Any],
context: ToolPermissionContext,
):
decision = await show_approval_dialog(
tool_name=tool_name,
suggestions=context.suggestions,
)
if decision == "always-allow-this-session":
return PermissionResultAllow(
updated_input=input_data,
updated_permissions=context.suggestions,
)
if decision == "allow-once":
return PermissionResultAllow(updated_input=input_data)
return PermissionResultDeny(message="Rejected by user.")
也可以直接构造规则:
from qoder_agent_sdk.types import PermissionRuleValue, PermissionUpdate
return PermissionResultAllow(
updated_input=input_data,
updated_permissions=[
PermissionUpdate(
type="addRules",
behavior="allow",
destination="session",
rules=[PermissionRuleValue(tool_name="mcp__orders__read_order")],
)
],
)
支持的更新类型:
| 类型 | 作用 |
|---|
addRules | 追加 allow、ask 或 deny 规则 |
replaceRules | 替换规则 |
removeRules | 删除规则 |
setMode | 切换权限模式 |
addDirectories | 追加允许访问的目录 |
removeDirectories | 移除目录授权 |
推荐把动态权限更新写入当前会话:
session 只影响当前 query 会话后续的权限检查。需要持久化到本地、项目或用户级配置时,优先使用 settings 管理流程,而不是依赖单次工具审批回调里的动态更新。
访问额外目录:add_dirs
默认情况下,会话以 cwd 作为主要工作目录。模型需要读取或修改 cwd 之外的目录时,应显式传入 add_dirs。
QoderAgentOptions(
cwd="/repo/app",
add_dirs=["/repo/packages/shared"],
)
这个配置表示会话主工作目录是 /repo/app,同时允许模型访问 /repo/packages/shared。这适合 monorepo、跨仓库调试、共享库排查等场景。
运行过程中也可以通过 PermissionUpdate 调整目录授权:
return PermissionResultAllow(
updated_input=input_data,
updated_permissions=[
PermissionUpdate(
type="addDirectories",
destination="session",
directories=["/repo/packages/shared"],
)
],
)
目录授权属于权限边界的一部分。不要把大范围目录加入 add_dirs 作为通用默认值;更稳妥的做法是按任务需要添加最小目录集合。
permission_prompt_tool_name 用于把权限请求交给运行环境中的 permission prompt tool,而不是在 SDK host 中实现 can_use_tool。它适合已有外部审批工具、远程运行环境或统一权限网关的场景。
QoderAgentOptions(
permission_prompt_tool_name="mcp__permission_server__approve",
)
使用时要注意三点:
permission_prompt_tool_name 必须是当前运行环境可以识别的 prompt tool 名称。
permission_prompt_tool_name 与 can_use_tool 互斥,不能同时传入。
- SDK host 自己要决定审批时,优先使用
can_use_tool。
permission prompt tool 会收到下面的输入:
{
"tool_name": "mcp__orders__read_order",
"input": {"order_id": "1001"},
"tool_use_id": "toolu_...",
}
它需要返回权限结果:
{
"behavior": "allow",
"updatedInput": {"order_id": "1001"},
"updatedPermissions": [],
"toolUseID": "toolu_...",
}
{
"behavior": "deny",
"message": "Denied by policy.",
"interrupt": True,
"toolUseID": "toolu_...",
}
allow.updatedInput 是最终执行工具时使用的入参。如果希望保持原始入参,应该原样返回收到的 input。deny.message 必填。interrupt=True 表示拒绝后中断当前 Agent 流程。
用 settings 提供权限规则
settings 适合在会话启动前提供静态权限配置。它比 can_use_tool 更适合表达“这个项目默认允许什么、拒绝什么、额外目录有哪些”。
QoderAgentOptions(
cwd="/path/to/project",
settings={
"permissions": {
"allow": ["Read", "Grep"],
"deny": ["Bash"],
"ask": ["Write"],
"defaultMode": "default",
"additionalDirectories": ["/path/to/shared-lib"],
}
},
)
字段说明:
| 字段 | 说明 |
|---|
permissions.allow | 允许规则 |
permissions.deny | 拒绝规则 |
permissions.ask | 始终询问规则 |
permissions.defaultMode | 默认权限模式 |
permissions.disableBypassPermissionsMode | 设置为 "disable" 时禁用跳过权限检查模式 |
permissions.additionalDirectories | 额外可访问目录 |
如果你的应用会读取并应用 settings 中的默认权限模式,建议在执行高风险模式前做自己的产品级确认。bypassPermissions、yolo 这类模式应该只出现在明确受信任的环境中。
用 hooks 做高级拦截和审计
Hooks 适合已经接入 SDK hooks 体系,并希望在工具生命周期里做更细粒度控制的场景。和 can_use_tool 相比,hooks 更适合横切逻辑,例如审计、告警、统一拦截、记录拒绝原因。
from qoder_agent_sdk import HookMatcher
async def pre_tool_use_hook(inp, tool_use_id, context):
return {
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": "Shell commands are disabled here.",
}
}
QoderAgentOptions(
cwd="/path/to/project",
hooks={
"PreToolUse": [
HookMatcher(matcher="Bash", hooks=[pre_tool_use_hook]),
],
},
)
权限相关 hook 主要有三类:
| Hook | 触发时机 | 常见用途 |
|---|
PreToolUse | 工具调用前 | 提前允许、拒绝、要求询问或交给后续流程 |
PermissionRequest | 进入权限请求时 | 在正常 prompt 前直接返回允许或拒绝 |
PermissionDenied | 权限被拒绝后 | 审计、告警、记录拒绝原因 |
PreToolUse 可以返回:
{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": "...",
"updatedInput": {},
}
}
其中 permissionDecision 可以是 "allow"、"deny"、"ask" 或 "defer"。
PermissionRequest 可以返回类似工具审批的权限结果:
{
"hookSpecificOutput": {
"hookEventName": "PermissionRequest",
"decision": {
"behavior": "deny",
"message": "Denied by policy.",
},
}
}
PermissionDenied 通常用于观察结果,不负责放行工具。它的输入会包含被拒绝的工具名、工具入参、工具调用 ID,以及拒绝原因。
如果权限策略天然属于某个 MCP server,也可以直接在 MCP server config 里声明 tool-level permission policy。这样策略跟随 MCP server 配置,而不是散落在全局 allowed_tools 或 disallowed_tools 中。
import os
QoderAgentOptions(
mcp_servers={
"repo_tools": {
"type": "http",
"url": os.environ["REPO_TOOLS_MCP_URL"],
"tools": [
{"name": "search", "permission_policy": "always_allow"},
{"name": "write_file", "permission_policy": "always_ask"},
{"name": "delete_file", "permission_policy": "always_deny"},
],
}
},
)
策略含义:
| 策略 | 行为 |
|---|
always_allow | 匹配工具直接允许 |
always_ask | 匹配工具进入授权流程 |
always_deny | 匹配工具直接拒绝 |
name 可以是 MCP tool 的原始名称,也可以是完整工具名,例如 mcp__repo_tools__search。实际匹配时,运行时会把策略名称映射到当前 MCP 工具调用。