# 豆包智能服务 Manifest 配置指南

## 豆包智能服务 Manifest 编写指南

本 Skill 用于编写一个应用版本对应的一份 Manifest。Manifest 是版本完整配置的声明式文件，描述这个版本的基础信息、业务实体 schema、工具运行配置、MCP Server 连接配置和权限配置。

涉及出卡协议正确性时，Manifest 写完后先用 `dbx simulator eval` 验证 MCP tools、entity schema、`tool_card_binding` 和 `card_delta`；如果只是验证本地 Manifest 是否能被 Web 模拟器加载，可以直接执行 `dbx dev`。

### 使用方式

先按整体骨架装配 Manifest，再按需要读取专项 reference：

- 基础信息：读 [基础信息](#基础信息)。
- MCP 服务入口：读 [MCP 服务入口](#mcp-服务入口)。
- 认证和权限：读 [认证与权限](#认证与权限)。
- 工具运行配置：读 [工具运行配置](#工具运行配置)。
- `entities`、`tool_card_binding`、`tools.output`：读 [业务协议映射](#业务协议映射)，必要时先读取 [mcp-protocol.md](mcp-protocol.md) 完成协议建模。
- 写完后校验：读 [Manifest 校验](#manifest-校验)，优先使用 validator 或平台预检能力。

### Manifest 骨架

```yaml
manifest_version: 2
app_key: db_app_mock_app
name: <app_name>

entities: {}

tools: {}

mcp_server:
  end_point: <mcp_server_url>
  description: <mcp_server_description>
  mcp_config:
    protocol: Streamable

permissions: []
```

这个骨架表达的是当前 YAML 规范中的配置块职责。服务通知和多任务管理不写入 Manifest；涉及这些能力时，在项目根目录另建 `business-templates.yaml`，并分别读取 [service-notice.md](service-notice.md) 和 [task-management.md](task-management.md)。

### 编写流程

1. 收集基础信息：`manifest_version`、AppID（写入 YAML 字段 `app_key`）、`name`。
2. 确认 MCP Server 地址和协议配置，写入 `mcp_server`。
3. 拉取或确认 MCP tool 列表，确保 `tools` 中的 tool 名和服务端暴露名称一致。
4. 如果还没有业务建模结果，先读取 [mcp-protocol.md](mcp-protocol.md) 产出 `entities_designed`、`entities`、`tools.output` 和 `tool_card_binding`。
5. 补齐每个工具的运行配置：入参 schema、输出类型、超时、登录类型、客户端工具标记、工具级权限。
6. 补齐版本级 `permissions`，并确认 tool 级 `tool_permissions` 引用到的权限已在版本级声明。
7. 运行 validator 或平台预检；根据错误定位到对应 reference 修正。

### 设计边界

- 本 Skill 负责 Manifest 的字段组织、装配和校验入口。
- `entities`、列表字段、`llm_visible`、`llm_modifiable` 和 `tool_card_binding` 的深度设计由 [mcp-protocol.md](mcp-protocol.md) 负责；本 Skill 只接入其配置结果。
- Manifest 只通过 `tool_card_binding` 引用前端工程中声明的 `widget_id`，不内联模板定义，不声明 UI 字段映射。
- 卡片展示字段、按钮、跳转、列表点位、空态和降级由业务卡片代码实现。

### widget_id 来源

`entities.<entity_type>.tool_card_binding.<tool_name>` 的 value 是卡片 `widget_id`，不是 `@doubao-apps/template` 的模板组件名，也不是卡片模板 ID。

`widget_id` 来自前端工程 `src/app.config.ts` 的 `defineAppConfig({ widgets })`：

- 显式对象写法：`widgets: [{ entry: 'widgets/fortune-result/index', id: 'tpl_fortune_result_card', ... }]`，其中 `id` 的值 `tpl_fortune_result_card` 就是 `widget_id`。
- 字符串简写：`widgets: ['widgets/weather/index']`，`widgets` 目录下的目录名 `weather` 就是 `widget_id`。

写 Manifest 前，如果本轮新增或修改卡片，应先确认前端已注册对应 widget，并把 `tool_card_binding` 的 value 写成这个 `widget_id`。
## 认证与权限

权限配置分两层：版本级 `permissions` 声明当前版本可能使用哪些权限，工具级 `tool_permissions` 声明某个工具调用需要哪些权限。

### 版本级 permissions

```yaml
permissions:
  - scope_name: scope.userLocation
    description: 精确定位权限
    type: Tool
  - scope_name: scope.camera
    description: 相机权限
    type: JSB
```

| 字段 | 必填 | 业务语义 |
|---|---|---|
| `scope_name` | 是 | 权限标识。 |
| `description` | 建议填 | 权限用途说明。 |
| `type` | 是 | 权限类别，枚举：`JSB`、`Tool`。 |

`type` 枚举：

- `JSB`：端侧 JSB 能力权限，例如相机、日历、蓝牙等由智能服务前端调用的端能力。
- `Tool`：MCP Tool 调用权限，例如定位、录音等工具执行时需要的平台能力授权。

文档中的 `scope_name` 枚举：

- `scope.userLocation`：精确定位权限
- `scope.userFuzzyLocation`：模糊定位权限
- `scope.userLocationBackground`：后台持续定位权限
- `scope.record`：录音权限（麦克风）
- `scope.bluetooth`：蓝牙权限
- `scope.addPhoneCalendar`：日历权限
- `scope.camera`：相机权限

### 工具级权限

```yaml
tools:
  search_routes:
    tool_permissions:
      - scope_name: scope.userLocation
        priority: 1
        is_must_need: true
      - scope_name: scope.record
        priority: 1
        is_must_need: false
```

工具级权限表达“调用这个工具需要哪些能力”。版本级 `permissions` 应覆盖 tool 级引用到的 `scope_name`。

| 字段 | 业务语义 |
|---|---|
| `scope_name` | 权限标识。 |
| `priority` | 权限优先级。 |
| `is_must_need` | 是否强依赖该权限；`true` 为强关联，`false` 为弱关联。 |

### 登录相关字段

```yaml
mcp_server:
  user_auth:
    fetch_token_url: https://example.com/auth/fetch
    refresh_token_url: https://example.com/auth/refresh
    delete_userinfo_url: https://example.com/auth/delete

tools:
  search_routes:
    login_type: normal
    login_params: [user_base_info]
```

| 字段 | 业务语义 |
|---|---|
| `mcp_server.user_auth.fetch_token_url` | 获取用户授权 token 的 URL。 |
| `mcp_server.user_auth.refresh_token_url` | 刷新用户授权 token 的 URL。 |
| `mcp_server.user_auth.delete_userinfo_url` | 删除用户授权信息的 URL。 |
| `tools.<tool>.login_type` | 登录类型，枚举：`normal`、`no_login`。 |
| `tools.<tool>.login_params` | 登录参数列表，枚举：`user_base_info`、`soda_music_info`、`user_mobile`。 |

`login_type`：

- `normal`：普通登录，使用智能服务自身或业务侧普通登录态。
- `no_login`：无需登录，工具调用不依赖用户登录态。

`login_params`：

- `user_base_info`：用户基础信息，例如头像、昵称等。
- `soda_music_info`：汽水音乐收藏列表。
- `user_mobile`：用户手机号。

### 编写要点

- tool 级权限使用 `tool_permissions`，版本级权限使用 `permissions`。
- `tool_permissions[].scope_name` 应在版本级 `permissions[].scope_name` 中声明。
- JSB 能力和工具能力用 `type: JSB` / `type: Tool` 区分。
- 登录回调 URL 写在 `mcp_server.user_auth`，不写在单个 tool 下。
- 登录 URL 应使用正式可访问地址，不使用本地调试地址。
## MCP 服务入口

`mcp_server` 描述平台调用业务 MCP Server 的地址、协议、固定请求头和用户授权回调地址。

```yaml
mcp_server:
  end_point: https://example.com/mcp
  description: 打车 mcp
  mcp_config:
    headers:
      - key: auth1
        source: constant
        value: "1"
      - key: auth2
        source: constant
        value: "2"
    protocol: Streamable
  user_auth:
    fetch_token_url: https://example.com/auth/fetch
    refresh_token_url: https://example.com/auth/refresh
    delete_userinfo_url: https://example.com/auth/delete
```

### 字段定义

| 字段 | 必填 | 业务语义 |
|---|---|---|
| `mcp_server` | 是 | MCP Server 连接配置块。 |
| `mcp_server.end_point` | 是 | MCP Server 地址。平台调用业务 MCP tool 时访问该地址。 |
| `mcp_server.description` | 建议填 | MCP Server 业务说明。 |
| `mcp_server.mcp_config` | 建议填 | MCP 协议和请求头配置。 |
| `mcp_server.mcp_config.protocol` | 是 | MCP 协议，枚举：`Streamable`、`SSE`。 |
| `mcp_server.mcp_config.headers` | 否 | 平台调用 MCP Server 时附加的请求头列表。 |
| `mcp_server.mcp_config.headers[].key` | 是 | 请求头名称。 |
| `mcp_server.mcp_config.headers[].source` | 是 | 请求头取值来源；文档示例为 `constant`。 |
| `mcp_server.mcp_config.headers[].value` | 是 | 请求头取值。 |
| `mcp_server.user_auth` | 否 | 用户授权相关回调 URL。 |
| `mcp_server.user_auth.fetch_token_url` | 否 | 获取用户授权 token 的 URL。 |
| `mcp_server.user_auth.refresh_token_url` | 否 | 刷新用户授权 token 的 URL。 |
| `mcp_server.user_auth.delete_userinfo_url` | 否 | 删除用户授权信息的 URL。 |

### 枚举值

`mcp_server.mcp_config.protocol`：

- `Streamable`：Streamable HTTP MCP 协议模式，优先用于新接入的 MCP Server。
- `SSE`：Server-Sent Events MCP 协议模式，用于兼容 SSE 形态的 MCP Server。

`mcp_server.mcp_config.headers[].source`：

- `constant`：固定值请求头，平台调用 MCP Server 时直接使用 `value` 中配置的值。

当前文档只明确了 `headers[].source=constant`。如果还有其它 `source` 取值，需要补充平台 schema 后再扩展。

### 编写要点

- `mcp_server.end_point` 应填写完整 URL，不手动拼接运行时路径。
- 预览调试时可使用本地临时预览地址；正式版本应使用稳定可访问的 MCP Server。
- 上传前确认 `app_key` 是目标应用 AppID、`mcp_server.end_point` 是正式版本使用的公网地址。
- 写 Manifest 前，先确认 MCP Server 能返回工具列表，并记录实际 tool 名。
- 工具级超时、登录类型、客户端工具标记和权限写在 `tools.<tool>` 中。
- 用户授权 URL 写在 `mcp_server.user_auth` 中，不写在单个 tool 下。

### 工具名确认

如果需要先确认 MCP Server 暴露的工具名，使用项目脚本或 MCP Inspector 嗅探 MCP Server。`dbx mcp list-tools` 已下线。返回的工具名必须和 Manifest `tools` 中的 key 保持一致。
## 基础信息

基础信息描述这个 Manifest 使用哪一版 schema，以及绑定到哪个智能服务应用。

```yaml
manifest_version: 2
app_key: db_app_mock_app
name: GitHub MCP
```

### 字段定义

| 字段 | 必填 | 业务语义 |
|---|---|---|
| `manifest_version` | 是 | Manifest schema 版本。平台据此选择解析和校验规则。 |
| `app_key` | 是 | AppID。版本、权限、日志、MCP 场景都围绕它关联。用户没有指定时，使用 `db_app_mock_app` 作为默认值。 |
| `name` | 是 | 当前智能服务/工具应用展示名称。 |

### 编写要点

- AppID 必须写入 `app_key` 字段，并指向当前要创建或更新版本的应用；用户没有指定时，使用 `db_app_mock_app` 作为默认值。
- `manifest_version` 当前按 YAML 规范写 `2`。
- 不要把应用 meta、隐私协议、图标或服务端生成的 `version_tag` 写进本块；这些不在当前 YAML 规范示例中。
## 业务协议映射

本文件说明 Manifest 如何接入协议建模产物，不重复展开实体拆分、列表卡、`llm_visible`、`llm_modifiable` 等设计细节。需要深度设计时，先读取 [mcp-protocol.md](mcp-protocol.md)。

### 输入结果

优先从 [mcp-protocol.md](mcp-protocol.md) 获取以下配置结果：

```yaml
entities_designed: []
entities: {}
tools_output: {}
tool_card_binding: {}
```

这些配置结果进入完整 Manifest：

```yaml
tools:
  search_repos:
    output:
      kind: entities
      entity_types: [github_repo]

  star_repo:
    kind: execution
    output:
      kind: execution_result

entities:
  github_repo:
    schema:
      entity_id: { type: string, description: 仓库业务 ID, is_entity_id: true, is_refresh_key: true }
      entity_type: { type: string, description: 业务实体类型, llm_visible: false }
      full_name: { type: string, description: 仓库全名 }
      stars: { type: int, description: Star 数 }
      summary: { type: string, description: 仓库摘要 }
      url: { type: string, description: 跳转链接, llm_visible: false }
    tool_card_binding:
      search_repos: repo_card_widget
    history_card_refresh:
      enabled: true
      scope: same_conversation
      refresh_to: fixed_style
```

### 装配规则

- `entities` 是业务对象 schema、模型可见字段、模型可修改字段和工具到卡片 widget 的配置。
- `entities.<entity_type>.schema` 中必须有一个顶层业务 ID 字段配置 `is_entity_id: true`。
- entity 结构复杂或字段很多时，`schema` 不必完整枚举业务返回；只强制声明稳定业务 ID、明确需要进入模型上下文的字段，以及使用 `llm_modifiable` 等平台能力所需的字段。其它未声明字段仍会原样透传给卡片，但不会自动进入模型上下文。
- `entities.<entity_type>.tool_card_binding` 与 `schema` 同级；key 是工具名，value 是该工具返回该 entity 时使用的前端 `widget_id`。
- `widget_id` 来自前端工程 `src/app.config.ts` 的 `defineAppConfig({ widgets })`：显式对象写法取 `widgets[].id`，字符串简写取 `widgets` 目录下的目录名。
- `entities.<entity_type>.history_card_refresh` 与 `schema` 同级，用于声明是否支持历史卡片刷新。历史卡片刷新的匹配键来自 `schema` 中被 `is_refresh_key: true` 标记的字段值；实体 schema 中必须声明 `is_refresh_key` 字段才能使用历史卡刷新。
- `tools.<tool>.output.kind` 声明工具返回按哪类结果处理。
- `tools.<tool>.output.entity_types` 只在 `kind=entities` 时配置，并且每个类型都应出现在 `entities`。
- Manifest 不声明模板字段、JSONPath、列表槽位、按钮动作、点击参数或 tool 级 `rendering`。

### entities 字段定义

| 字段 | 业务语义 |
|---|---|
| `entities` | 业务实体定义集合。 |
| `entities.<entity_type>` | 某一种业务实体类型，必须能被业务返回中的 `entity_type` 命中。 |
| `schema` | 当前 entity 的字段 schema。 |
| `schema.<field>.type` | 字段类型；使用平台支持的 Manifest schema 类型。 |
| `schema.<field>.description` | 字段说明。 |
| `schema.<field>.is_entity_id` | 是否为顶层 entity 的稳定业务 ID；每个 entity 应选择一个稳定 ID 字段。 |
| `schema.<field>.is_refresh_key` | 是否为卡片刷新键；卡片下发时该字段值会作为卡片的 `refresh_key`，服务端可通过「刷新卡片实例」API 用相同值更新卡片数据。同一 entity 内最多一个字段可标记为刷新键，允许位于顶层或 object 内，不能位于 array items 内。历史卡片刷新也依赖该字段匹配。 |
| `schema.<field>.llm_visible` | 字段是否给模型感知；不配置时按可见处理，跳转、图片、内部 ID 等渲染/执行字段通常设为 `false`。 |
| `schema.<field>.llm_modifiable` | 字段是否允许模型修改；当前用于 `type: array` 的列表字段，语义是模型可从原始 item 中筛选子集。 |
| `schema.<field>.items` | `type: array` 时的 item schema。 |
| `schema.<field>.properties` | `type: object` 或 array item 为 object 时的子字段定义。 |
| `tool_card_binding` | 当前 entity 在不同 tool 返回时使用的卡片 widget 映射。 |
| `tool_card_binding.<tool_name>` | key 是实际 MCP tool 名，value 是前端工程中声明或推导出的 `widget_id`。 |
| `history_card_refresh` | 历史卡片刷新配置。匹配键来自 `schema` 中被 `is_refresh_key: true` 标记的字段值。 |
| `history_card_refresh.enabled` | 是否支持历史卡片刷新。 |
| `history_card_refresh.scope` | 刷新范围；文档示例为 `same_conversation`。 |
| `history_card_refresh.refresh_to` | 刷新目标样式；文档示例为 `fixed_style`。 |

### 枚举值

`schema.<field>.type`：

- `string`：字符串。
- `int`：整数数值。
- `float`：浮点数值。
- `boolean`：布尔值。
- `object`：对象结构，通常配合 `properties` 描述子字段。
- `array`：数组结构，通常配合 `items` 描述 item schema；需要模型筛选 item 时可配置 `llm_modifiable: true`。

`history_card_refresh.scope`：

- `same_conversation`：同一会话范围内刷新历史卡片。

`history_card_refresh.refresh_to`：

- `fixed_style`：刷新为固定样式卡片。

当前文档只明确了 `history_card_refresh.scope=same_conversation` 和 `history_card_refresh.refresh_to=fixed_style`。如果还有其它取值，需要补充平台 schema 后再扩展。

### output.kind 决策

`output.kind` 是 Manifest 中的工具输出类型，目前按工具成功返回分成两类：

```text
工具成功返回 structuredContent.entities，且这些 entity 需要被模型理解、比较、选择或渲染成卡
  -> output.kind: entities
  -> 配 output.entity_types
  -> 需要出卡时，在对应 entity 下配 tool_card_binding.<tool_name>: <widget_id>

工具成功返回纯动作结果、确认信息或轻量状态
  -> output.kind: execution_result
  -> 不配 output.entity_types
  -> 不配 tool_card_binding
```

业务失败不是 `output.kind` 的第三种取值。失败由 MCP tool 运行时返回表达：

```json
{
  "structuredContent": {
    "code": 40001,
    "message": "缺少目的地"
  },
  "isError": true
}
```

常见判断：

- 搜索、推荐、查询候选：通常 `entities`。
- 详情查询：如果详情需要展示为卡或后续操作，通常 `entities`。
- 创建订单并返回订单详情：通常 `entities`，订单是可展示和可追问的业务对象。
- 收藏成功、订阅成功、提交成功这类纯确认：通常 `execution_result`。
- 工具业务失败：返回 `isError: true`，不要设计成新的 `output.kind`。

### 接入检查

- `output.entity_types` 中每个类型都应出现在 `entities`。
- 业务 ToolResult 的 `structuredContent.entities[].entity_type` 应命中 `output.entity_types` 和 Manifest `entities`。
- 每个顶层 entity schema 应有且只有合理的稳定业务 ID 字段标记 `is_entity_id: true`。
- `tool_card_binding` 的 key 应是实际 MCP tool 名。
- `tool_card_binding` 的 value 应是前端工程中已注册的卡片 `widget_id`，不是 `@doubao-apps/template` 组件名。
- 如需支持历史卡片刷新，需在 `schema` 中声明 `is_refresh_key: true` 字段，并按文档配置 `history_card_refresh.enabled`、`scope`、`refresh_to`。
- 业务 ToolResult 只需要按工具类型返回 `content`、`structuredContent` 和 `isError`。
- 不要把 UI 字段映射写进 Manifest；字段展示由卡片代码读取 entity 数据后处理。
## 工具运行配置

`tools` 描述 MCP tool 的运行时配置、输出类型和权限。工具入参以 MCP Server `tools/list` 暴露的 `inputSchema` 为准，Manifest 不重复声明；工具本身也不声明卡片字段渲染。是否能出卡由返回 entity 的 `tool_card_binding` 是否包含当前工具名决定。

```yaml
tools:
  search_routes:
    description: 搜索路线
    output:
      kind: entities
      entity_types: [route]
    auth_header_key: "123"
    request_timeout: 5000
    connection_timeout: 5000
    login_type: normal
    login_params: [user_base_info]
    is_client_tool: true
    tool_permissions:
      - scope_name: scope.userLocation
        priority: 1
        is_must_need: true
      - scope_name: scope.record
        priority: 1
        is_must_need: false

  subscribe_route:
    description: 订阅路线
    output:
      kind: execution_result
```

### 字段定义

| 字段 | 业务语义 |
|---|---|
| `tools.<tool_name>` | MCP tool 名，必须和 MCP Server 暴露名称一致。 |
| `description` | 工具说明，用于平台和模型理解工具能力。 |
| `request_timeout` | 请求超时时间。 |
| `connection_timeout` | 连接超时时间。 |
| `output.kind` | 输出类型，支持 `entities` 或 `execution_result`。 |
| `output.entity_types` | `kind=entities` 时，该工具可能返回的业务实体类型。 |
| `auth_header_key` | 工具调用时使用的 header key。 |
| `login_type` | 登录类型，枚举：`normal`、`no_login`。**此字段必填**，不需要登录时填 `no_login`。 |
| `login_params` | 登录参数列表，枚举：`user_base_info`、`soda_music_info`、`user_mobile`。 |
| `is_client_tool` | 是否为客户端工具。 |
| `tool_permissions` | 当前工具调用需要的权限列表。 |
| `tool_permissions[].scope_name` | 权限标识。 |
| `tool_permissions[].priority` | 权限优先级。 |
| `tool_permissions[].is_must_need` | 是否强依赖该权限；`true` 为强关联，`false` 为弱关联。 |

### 枚举值

`login_type`：

- `normal`：普通登录，使用智能服务自身或业务侧普通登录态。
- `no_login`：无需登录，工具调用不依赖用户登录态。

`login_params`：

- `user_base_info`：用户基础信息，例如头像、昵称等。
- `soda_music_info`：汽水音乐收藏列表。
- `user_mobile`：用户手机号。

`tool_permissions[].is_must_need`：

- `true`：强关联权限，工具运行强依赖该权限。
- `false`：弱关联权限，工具可使用该权限增强效果，但不应作为绝对阻塞条件。

### 编写要点

- 先用 MCP Server 工具列表确认 `tool_name`，不要凭业务名猜。
- 工具入参来自 MCP Server `tools/list` 的 `inputSchema`，Manifest 中不要重复声明。
- 搜索、查询、推荐类工具通常使用 `output.kind=entities`，业务返回 `structuredContent.entities`。
- 纯动作确认类工具通常使用 `output.kind=execution_result`，业务返回执行结果 object。
- `output.entity_types` 中的类型必须出现在 Manifest `entities`。
- 不要在 `tools` 下配置 `rendering`、`binding_id`、JSONPath 或 UI 字段映射。
- 工具级权限使用 `tool_permissions`，不要写成 `permissions: [...]`。
- 登录态用 `login_type` 和 `login_params` 表达；用户授权 URL 写在 `mcp_server.user_auth`。

## Manifest 校验

写完或修改 Manifest 后，**必须使用 `dbx app artifacts validate` 进行校验**。

### 校验入口

```bash
dbx app artifacts validate <manifest.yaml> --json
```

必须显式传入 Manifest 路径。若在本 lifecycle 流程中，先用 `workspace.py manifest path --create --json` 获取路径，再把返回的 `path` 传给本命令。

### 结果解读

命令输出是精简 JSON。成功时：

```json
{
  "valid": true,
  "errors": [],
  "warnings": [],
  "log_id": "..."
}
```

失败时命令退出非 0，并直接输出可修复信息：

```json
{
  "valid": false,
  "errors": [
    {
      "code": "manifest.field.type_invalid",
      "path": "$.entities.calculation_result.schema.result.type",
      "message": "..."
    }
  ],
  "warnings": [],
  "log_id": "..."
}
```

字段含义：

- `valid`：是否通过校验；只有 `true` 才能继续上传 manifest。
- `errors`：必须修复的问题。逐条读取 `path` 和 `message`，按 `path` 定位 YAML 字段，按 `message` 修改。
- `warnings`：建议修复的问题。没有 `errors` 时通常不阻塞上传，但应尽量处理。
- `code`：稳定错误码，只用于辅助分类；不要只根据 code 猜修复方式。
- `log_id`：平台请求链路 ID。需要查后端日志时使用。

### 修复原则

- 优先修 `errors[0]`，修完重新执行 validate，再处理下一批错误。
- `path` 指向的是 Manifest YAML 里的逻辑路径，例如 `$.entities.<entity_type>.schema.<field>.type`。
- `message` 是最权威的修复提示。例如提示 type 只能是 `string/int/float/boolean/object/array` 时，不要继续使用 JSON Schema 的 `number`，应改成平台支持的 `int` 或 `float`。
- tool 名错误时，以 MCP Server 实际暴露的 tool 名为准；`dbx mcp list-tools` 已下线，需要通过项目脚本或 MCP Inspector 嗅探。
- validate 只用于预检，不代表版本产物已经上传成功。
- 修复 validate 的错误后需要同步修复运行态 Skill 中对应的工具说明，文件位于 `skill/SKILL.md`。
