# 工具授权 (/docs/cloud/services/agent/embed/tool-authorization)



智能体能调用你的系统：查订单、改地址、申请退货。这一页讲两件事：**哪些调用要先让用户确认**，以及**真正的权限在哪里卡住**。

<Callout title="询问不是安全手段">
  确认卡只是让终端用户确认「这是我想做的」，防的是智能体理解错了意图。它挡不住有意构造的请求：用户本人就是在和智能体对话，也可以在页面上改任何前端数据。**权限必须由你的工具服务端按终端用户鉴权**，见下文。
</Callout>

## 两档策略 [#两档策略]

每个工具有一个权限策略，只有两档：

| 策略             | 行为                            |
| -------------- | ----------------------------- |
| `always_allow` | 智能体直接调用，不打断用户                 |
| `always_ask`   | 调用前暂停，在对话里显示确认卡，用户选择允许或拒绝后才继续 |

没有第三档。写入 `auto` 会被拒绝（`auto 尚未支持`）。

策略写在智能体定义的 `tools` 里，MCP 工具集可以设整体默认，也可以逐个工具覆盖：

```json
{
  "type": "mcp_toolset",
  "mcp_server_name": "orders",
  "default_config": { "permission_policy": { "type": "always_ask" } },
  "configs": [
    { "name": "list_orders", "permission_policy": { "type": "always_allow" } },
    { "name": "request_return", "permission_policy": { "type": "always_ask" } }
  ]
}
```

优先级：单个工具的 `configs[].permission_policy`，然后是 `default_config.permission_policy`，都没写则使用默认值。

## 默认值 [#默认值]

| 工具类型                            | 没有写策略时                                                                              |
| ------------------------------- | ----------------------------------------------------------------------------------- |
| MCP 工具（`mcp_toolset`）           | `annotations.readOnlyHint === true` 时为 `always_allow`；缺失、false 或非布尔值时为 `always_ask` |
| 内置工具集（`agent_toolset_20260401`） | 工具集的 `default_config` 没写时为 `always_allow`                                           |
| 前端工具（`type: "custom"`）          | 没有确认步骤。它由你页面里的代码执行，见[前端工具](/docs/cloud/services/agent/embed/client-tools)           |

此缺省规则适用于所有智能体的 MCP 工具。hint 来自 MCP 服务器，是不可信的提示，只影响缺省；单工具和工具集的显式配置始终优先。询问同样不是安全手段。给每个工具设策略时，按「用户自己能不能撤销、做错了代价多大」来决定：查询类放行，退款、删除、发送类保留询问。

## 确认卡 [#确认卡]

当某个调用的策略是 `always_ask`，对话会暂停，Kit 在对话里显示确认卡。终端用户选择允许或拒绝：

* 允许：调用继续，结果回到智能体。
* 拒绝：调用不执行，对话继续。

确认卡由 Kit 自带，你不需要写界面。会话预算用完后，会话不再接收新的任务，但仍然接收工具确认和中断。需要在自有界面里处理确认的宿主，可以用 API 发送 `user.tool_confirmation` 事件，字段为 `tool_use_id` 和允许 / 拒绝，详见 [API 参考](/docs/api/native/agent)。

## 真正的安全：在工具服务端鉴权 [#真正的安全在工具服务端鉴权]

智能体调用你的 MCP 服务器时，平台会带上三个请求头，说明这次调用代表谁：

| 请求头                         | 含义                                     |
| --------------------------- | -------------------------------------- |
| `X-Praxis-External-User-Id` | 签发令牌时传入的 `external_user_id`，即你系统里的终端用户 |
| `X-Praxis-Agent-Id`         | 智能体 id                                 |
| `X-Praxis-Session-Id`       | 会话 id                                  |

这三个头只在会话属于某位终端用户时才带；没有终端用户的会话不带，你的服务要把缺失当作拒绝。它们来自平台对令牌的验证，**不是模型可以填写的参数**，所以是你唯一应该信任的身份来源。

规则：

1. **身份只取请求头**。不要让工具接收 `user_id` 之类的参数再相信它：模型可以被诱导填任何值。
2. **每次查询都按用户限定范围**。查询条件里带上用户，而不是查出来再过滤；操作一个具体对象（订单、地址）前先确认它属于这个用户。
3. **缺少身份就拒绝**，返回错误，不要退化成匿名访问。
4. **服务本身也要认证调用方**。MCP 服务器是公网地址，请求头可以被伪造；在智能体定义里为 MCP 服务器配置 `headers`（例如 `Authorization: Bearer …`），服务端先校验这个凭据，再信任 `X-Praxis-*` 头。
5. **危险动作在服务端再检查一遍**业务规则（订单是否已签收、是否超过退货期），不要依赖「用户在确认卡上点过允许」。

示例：一个按终端用户限定范围的订单查询和退货工具（与 MCP 实现无关的部分）。

```ts
// 你的 MCP 服务器：每个请求先认证，再按终端用户执行
type Caller = { userId: string };

function authenticate(request: Request): Caller | Response {
  // 1) 认证 Praxis：智能体定义里为这个 MCP 服务器配置的 Authorization 头
  if (request.headers.get("authorization") !== `Bearer ${process.env.PRAXIS_MCP_SECRET}`) {
    return new Response("Unauthorized", { status: 401 });
  }
  // 2) 终端用户身份：只信平台附带的请求头
  const userId = request.headers.get("X-Praxis-External-User-Id");
  if (!userId) return new Response("Missing end-user identity", { status: 403 });
  return { userId };
}

// list_orders：只读，没有任何用户可选的参数
async function listOrders(caller: Caller) {
  return db.orders.findMany({ where: { userId: caller.userId } }); // 范围在查询里
}

// request_return：参数 order_id 来自模型，必须再校验归属和业务规则
async function requestReturn(caller: Caller, input: { order_id: string; reason: string }) {
  const order = await db.orders.findFirst({
    where: { id: input.order_id, userId: caller.userId }, // 不属于该用户就查不到
  });
  if (!order) return { isError: true, text: "订单不存在" }; // 与「不属于你」不作区分
  if (order.status !== "delivered") return { isError: true, text: "只有已签收的订单可以退货" };
  return db.returns.create({ data: { orderId: order.id, reason: input.reason } });
}
```

`db` 表示你自己的数据访问层。要点是：`userId` 永远来自 `X-Praxis-External-User-Id`，`order_id` 虽然由模型提供，但在带着 `userId` 的查询里才有效，别人的订单号查不出东西。

仓库里的「好物集」演示站（`apps/kit-demo`）的订单 MCP 就是这样做的：`list_orders` 声明 `readOnlyHint: true`，`request_return` 声明 `destructiveHint: true`，两者都从同一个请求头取用户。

## 选择策略的建议 [#选择策略的建议]

| 工具                | 建议                                 |
| ----------------- | ---------------------------------- |
| 只读查询，且服务端已按用户限定范围 | `always_allow`                     |
| 会改变数据，用户可以自己撤销    | 视情况，确认卡能减少误操作                      |
| 不可撤销、涉及金额或对外发送    | `always_ask`，同时服务端再校验              |
| 前端工具              | 没有确认卡。不要让它执行不可撤销的动作，需要时在你自己的界面里弹确认 |

下一步：[安全](/docs/cloud/services/agent/embed/security) · [传入上下文](/docs/cloud/services/agent/embed/context)
