---
title: "连接器"
description: "连接器和连接如何为代理提供受限的外部工具访问权限。"
---

连接器将项目连接到外部工具或服务。智能体将其作为工具调用。Beeuni 会代理每次调用，因此沙箱不会持有连接器凭据。

大多数连接器都在 `beeuni.yaml` 中声明。每个字段请参阅[清单参考](/docs/zh/project/manifest)。连接聊天平台或计算机时，Beeuni 会声明频道和计算机连接器。

<a id="browse-and-add-a-connector"></a>

## 浏览并添加连接器

打开 **Connectors** 浏览托管目录。默认列表包含可通过提供商管理的身份验证或已启用的自定义身份验证配置进行连接的托管提供商工具包。项目中无需预先存在连接。如果工具包需要未经验证的自定义 OAuth 配置，则在该配置通过验证之前会保持隐藏。

若要浏览直连来源，请在项目的功能标志中启用 **Connectors API Discover**，然后在同一页面选择 **API / MCP**。启用该标志不会替换默认的托管目录。直连目录按已发布的 OpenAPI、MCP、GraphQL、Postman 和其他 API 接口分组。选择卡片，选取已发布的接口，然后填写连接表单。仅提供文档的接口会打开提供商文档；不会作为可直接连接的模板展示。对于原始 API 或目录中没有的架构，请使用 **Add → Manual**。即使未配置托管提供商，也可以使用直连发现。

<a id="connectors-connections-and-accounts"></a>

## 连接器、连接和账户

连接器是面向智能体的访问包：声明一项能力（有哪些操作、如何访问服务）。它不是角色，也不持有 Beeuni 权限：只有当智能体的清单授权列出该连接器的 slug，且角色判定允许调用时，智能体才能访问连接器。请参阅[一种术语，两种绑定](/docs/zh/accounts#one-vocabulary-two-bindings)。连接器包含：

- 项目内唯一的 slug
- 显示名称
- 提供商应用
- 连接器策略

连接是该连接器的一个**账户**，即一个已授权身份：凭据、OAuth 授权或 API 密钥。一个连接器可以并列包含多个账户。所有账户都使用连接器的策略。每个账户的 `owner_type` 决定谁可以以该账户身份发起调用：

- `project` — **共享**账户。任何获准使用该连接器的人都可以访问，包括人类用户和服务账户（智能体、触发器）。
- `member` — **私人**账户，由一名项目成员（`owner_id`）持有。只有该成员可以在**私人**会话中访问。服务账户（无人值守运行的智能体或触发器）绝不能以成员的私人账户身份运行。

多个连接器可以引用同一个提供商应用。如果同一应用需要不同策略，请使用不同的连接器。

<a id="add-an-account"></a>

### 添加账户

在 **Customize → Connectors** 中打开连接器。**Accounts** 选项卡会在一个列表中列出你可以使用或管理的所有账户。每张卡片都会显示谁可以使用该账户：**Only you**、**Everyone in project**，或与其共享的群组和人员。

选择 **Add account**，为账户命名，然后选择 **Who can use it**：

- **Only you** — 私人账户。任何成员都可以添加。
- **Everyone in the project** — 共享账户。
- **Specific people or groups** — 共享账户，仅向你选择的人员、群组和智能体开放。

两个共享选项都需要 `project.connector.connections.manage`。对于 **Specific people or groups**，Beeuni 会在你授权账户前写入授权，因此账户在尚未授权时不会对整个项目开放。账户名称必须唯一：如果同一所有者已有账户使用该名称，则会拒绝创建。

智能体需要账户时，会在聊天中显示一张 **Connect** 卡片。卡片会打开同一个对话框，其中已填入智能体建议的名称，并预选其请求的受众。Beeuni 会创建新账户，不会重新授权现有账户，并将新账户名称告知智能体，以便它以该账户身份发起调用。

<a id="choose-who-can-use-a-shared-account"></a>

### 选择谁可以使用共享账户

共享账户默认可供项目中的所有人使用，直到你缩小其范围。在账户卡片上选择 **Share**。该对话框与智能体使用的 **Grant access** 对话框相同。它会列出当前可以使用账户的对象，你可以添加：

- **Everyone in the project** — 默认选项。
- **Groups** — 每个群组的所有成员。
- **People** — 每次添加一名成员。只选择你自己即可仅供自己使用。
- **Agents** — 该智能体的所有会话，包括触发器和共享会话。任何可以运行该智能体的人都可以通过它使用账户。

选择 **Save** 后更改才会生效。移除最后一个群组或人员后，账户会重新开放给所有人；对话框会在保存前说明此结果。

范围缩小后的账户遵循私人账户规则，只是以受众取代所有者：

- 受众中的人员可以使用该账户，且只能在私人会话中使用。
- 受众中的智能体可以在其所有会话中使用该账户。
- 其他任何智能体及其他智能体的触发器都不能使用该账户。
- 绑定该账户的会话不能共享，除非该账户已共享给该会话所属的智能体。
- 只有人员可以更改账户的使用者。智能体会话获得 `403`。

受众之外的人员看不到该账户。仍可管理项目连接的人员会看到该账户，并标记为 **Not shared with you**，以便更改谁可以使用它。

<a id="share-your-own-private-account"></a>

### 共享你自己的私人账户

你自己的 **Only you** 账户也有 **Share** 按钮。共享后，该账户会变成共享账户，开放给你选择的人员和群组（除非你将自己移除，否则你仍可使用），或者开放给项目中的所有人。从此它就是共享账户：管理项目连接的人员也可以管理它。

- 共享需要 `project.connector.connections.manage`，与添加共享账户所需的权限相同。没有该权限时，按钮会被禁用并说明原因。
- Beeuni 会先写入授权，再通过一次更新将账户设为共享，因此账户不会在此过程中向整个项目开放。
- 如果连接器上已有同名共享账户，则无法共享（`409`）：请先重命名其中一个账户。

```text
POST /projects/{projectId}/connections/{connectionId}/share
     {"principals": [{"principal_type": "group", "principal_id": "<group-id>"}]}
```

`principals` 列表为空时，账户会共享给所有人。使用 CLI：
`beeuni connectors connections share <connection-id> --group <group-id>`
（也可以使用 `--user <email|id>`，可重复指定，或使用 `--everyone`）。

每个受众条目都是授权存储中的一个 `connection` 对象分配：

```text
POST   /accounts/{accountId}/iam/assignments
       {"principal_type": "group", "principal_id": "<group-id>",
        "role_key": "agent-user", "scope_type": "project", "scope_id": "<project-id>",
        "object_type": "connection", "object_id": "<connection-id>"}
DELETE /accounts/{accountId}/iam/assignments/{assignmentId}
```

在 CLI 中，同样的写入操作为：

```bash
beeuni access grant --group <group-id> --connection <connection-id>
beeuni access grant --everyone --connection <connection-id>
beeuni access revoke <assignment-id>
beeuni connectors connections ls   # the WHO CAN USE column
```

- `principal_type` 可以是 `user`、`group` 或 `project`（项目中的所有人；`principal_id` 是项目 ID）。
- 授予或撤销单项授权需要 `project.connector.connections.manage`。
- 只有共享账户可以通过这些授权缩小范围。私人账户，或不属于此项目连接的 ID，会返回 `404`；若要共享你自己的私人账户，请改用上文的 `share` 路由。
- `GET /projects/{projectId}/connections` 会在 `shared_with` 中返回每个共享账户的授权；对于调用者只能查看以便管理的账户，`usable: false`。会话选择器只会提供可使用的账户。

<a id="which-identity-an-account-acts-as"></a>

### 账户使用哪个身份发起操作

账户的`label`是在授权前选择的名称。它不会说明使用了哪个登录账号。`connected_as`则会说明：服务提供商为已授权账户报告的电子邮箱、登录名或显示名称。

- Beeuni 会在 托管授权完成时读取该信息：先读取已连接账户的显示名称，再调用工具包的“我是谁”工具（Gmail、Google Calendar、Google Drive、Linear、GitHub、Slack、Notion、Outlook、Microsoft Teams、HubSpot、Jira、Asana、Figma、Airtable、Salesforce、Trello、Dropbox、Calendly）。Google Docs 和 Google Sheets 没有此类工具，因此它们的`connected_as`会保持为`null`。
- 如果账户仍使用默认标签（`Private connection`、`Project connection`或连接器名称），则会将其改为该身份信息。用户自行选择的标签绝不会被替换。
- 账户接入后，连接页面会立即显示“Connected as …”，因此使用个人登录账号授权的共享账户会立即显示出来。
- 每个连接（`GET /projects/{projectId}/connections`）都会显示`connected_as`；账户列表、`beeuni connectors accounts <slug>`和`beeuni connectors connections ls`（`CONNECTED AS`列）中也会显示；**Customize → Connectors** 中的账户行同样会显示。

<a id="rename-an-account"></a>

### 重命名账户

无需重新授权即可重命名账户：

```text
PUT /projects/{projectId}/connections/{connectionId}/label   {"label": "Support inbox"}
beeuni connectors connections rename <connection-id> Support inbox
```

在信息中心打开连接器，然后在账户行中选择 **⋯ → Rename**。只会更改标签。已授权账户、账户所有者、默认标记和服务提供商状态均保持不变。适用与断开连接相同的权限限制：只能操作自己的私人账户，或操作共享账户时具备`project.connector.connections.manage`权限。

如果标签是`me`、`project`或 UUID 格式，则重命名会因`400`而被拒绝，因为`--account`会优先解析这些内容，而非标签。如果同一所有者的另一个账户已有该标签（比较时不区分大小写），则重命名会因`409`而被拒绝。

<a id="connect-a-different-account"></a>

### 连接其他账户

如果某个槽位已有活动账户，指向该槽位的连接链接不会重新授权。服务提供商会重复使用该账户，连接页面会显示 **Already connected**。如需使用其他登录账号，请在连接器上添加新账户，将其设为默认账户，然后断开旧账户。

未指定账户的调用会先解析为调用者自己的默认私人账户，然后解析为项目的默认共享账户。每个调用结果都会回显其使用的账户（`account: { connection_id, label, owner_type }`），因此对话记录始终会显示工具调用所用的身份。使用`GET /connectors/projects/{projectId}/connectors/{slug}/accounts`列出某个连接器可调用的账户，默认账户排在首位。

> **Warning**
>
> `authorization_strategy`（`project` | `user`）曾是连接器级 MODE，用于让项目所有和成员所有的账户互斥——采用`user`策略的连接器无法提供共享账户，因此服务账户可用的任何位置都无法使用其连接流程。该模式现已弃用：清单和`PUT .../authorization-strategy`仍接受该模式并正常响应，但现在已没有任何功能会读取它。现在，每个连接上的`owner_type`就是完整的访问规则。

<a id="providers"></a>

## 服务提供商

连接器使用一种服务提供商类型：

- **composio** — 为受支持的 SaaS 应用提供托管式 OAuth
- **pipedream** — 旧版托管式 OAuth，仅保留用于回滚
- **openapi**、**postman**、**graphql**、**http** — 直接 API 连接器
- **mcp** — 通过 HTTP 或 SSE 连接的远程 MCP 服务器
- **channel** — 聊天平台连接
- **computer** — 每位成员连接的计算机均作为一个账户

托管式服务提供商流程请参阅 [Slack 和频道](/docs/zh/connect/slack) 及
[计算机](/docs/zh/connect/computers)。

<a id="apps-that-need-your-own-oauth-app"></a>

### 应用可用性

连接器目录展示当前可连接的应用。部分应用需要额外的身份验证配置，配置完成后才会显示。

## 身份验证与策略

连接通过以下方式进行身份验证：

- 通过托管 OAuth、频道安装或原生 OAuth2 授权流程进行身份验证
- 通过信息中心输入 API 密钥或令牌

Beeuni 会加密连接数据，并在每次工具调用时于服务器端解析这些数据。代理会请求执行操作。Beeuni 附加凭据，检查代理授权和连接器连接策略，调用外部 API，然后返回结果。

连接器策略归连接器所有。连接无法覆盖这些策略。项目护栏的优先级高于连接器连接策略。

默认情况下，不匹配任何规则的连接器操作无需审批。设置`policy.default_mode: risk`，即可要求审批不匹配规则的写入和破坏性操作。设置`sensitive: true`，可将`require_approval`设为连接器对不匹配操作的默认处理方式，包括读取操作。项目或连接器连接的显式规则仍会优先应用。

<a id="approve-one-governed-call"></a>

### 审批一项受策略管控的调用

`require_approval`会为一次连接器调用创建一项决策。连接器会返回`202 pending_approval`，其中包含`approval_url`、`approval_summary`和`execution_id`。它不会让 HTTP 请求保持打开状态。

你可以将`approval_url`分享给任何队友。该 URL 用于标识请求，但不会授予权限。该页面要求用户登录 Beeuni 账户，随后 Beeuni 会验证该账户是否有权访问项目并审批操作。

审批页面会显示连接器将收到的已脱敏参数。调用方可以添加`approval_context`（CLI：`--reason`）来说明操作效果。当参数只是引用时，请使用此功能：`gmail.send_draft`接收草稿 ID，因此代理需要说明收件人、主题和正文。页面会将此文本显示为代理的描述，而非经验证的信息。参数仍是判断依据。Beeuni 绝不会将`approval_context`发送给服务提供商。

对调用进行一次批准或拒绝。你可以为决策添加可选消息。该消息会随决策一并返回给代理，因此拒绝时可以说明应改做什么。

每个待处理调用也会显示在项目的 **Review → Needs you** 中。从侧边栏打开 **Review**；旁边的数量会显示待你处理的项目数。打开调用后，会显示与审批链接相同的审批视图，因此你可以在做出决定前查看连接器、参数和代理的描述。

当调用来自 Slack 或 Teams 会话时，Beeuni 会在该线程中发布审批卡片。卡片会显示智能体的描述、参数以及 **Approve** / **Deny**。在 Slack 中，**Reply…** 会打开一个输入框，供你向智能体发送消息；在 Teams 中，卡片内含消息输入框。只有项目经理或会话发起人可以作出决定。无论在哪儿（线程、Beeuni 或审批页面）作出的决定，都会将卡片按钮替换为处理结果，智能体则会在同一线程中继续工作。Beeuni 会通过持久化回调将决定发送回会话。

审批仅适用于完全匹配的请求摘要。收件人、主题、正文、频道、URL 或其他参数发生变化，都需要重新审批。

打开会话的 **Audit** 面板，可查看相同的参数视图。历史记录为只读。没有适用于整个会话的审批选项。只有在连接器操作必须无人值守地运行时，才使用明确的 `always_run` 策略。

<a id="connect-with-oauth"></a>

## 使用 OAuth 连接

1. **打开项目的 Connectors 页面**

    打开项目。选择 **Connectors**，然后选择应用。

2. **选择账户范围**

    共享账户请选择 **Project**，获授该连接器的任何人都可以访问。私人账户请选择 **User**，此账户归你所有，仅你可以访问，而且只能在私人会话中访问。

3. **完成授权**

    完成 OAuth 流程。Beeuni 会将已连接的账户保存为连接。

<a id="connect-an-mcp-server-that-uses-oauth-21"></a>

## 连接使用 OAuth 2.1 的 MCP 服务器

Beeuni 实现了 MCP 授权规范。打开连接器，选择 **Add credential**，然后选择 **OAuth 2.0** 选项卡。Beeuni 会探测服务器并读取其元数据：

1. 未经身份验证的探测会返回 `401`，其中包含
   `WWW-Authenticate: Bearer resource_metadata="…"`。
2. Beeuni 会从该 URL 读取受保护资源元数据（RFC 9728），或从
   `/.well-known/oauth-protected-resource` 读取。
3. Beeuni 会读取资源列出的第一个授权服务器的授权服务器元数据（RFC 8414 或 OpenID Connect 发现文档）。

当该服务器声明支持 `registration_endpoint` 时，Beeuni 会注册为 OAuth 客户端（RFC 7591），并显示一个按钮：**Connect &lt;server&gt;**。你无需创建应用，也无需复制客户端 ID 或密钥。随后，Beeuni 会运行带 PKCE（S256）的授权码流程，并通过 `resource` 参数（RFC 8707）将令牌绑定到服务器。

如果服务器需要预先配置重定向 URI，请注册以下 URI：

```text
https://api.beeuni.ai/v1/connectors/oauth2/callback
```

如果服务器发布了端点但没有 `registration_endpoint`，Beeuni 会预填授权 URL、令牌 URL 和作用域。请输入你在该提供商处创建的应用的客户端 ID。如果服务器没有发布任何元数据，请填写所有字段。

Beeuni 会保留 MCP 会话：按需运行 `initialize` 和
`notifications/initialized`，之后的调用则发送 `Mcp-Session-Id`。如果服务器在没有会话的情况下响应，就不会收到握手信息。

Beeuni 会将每个连接绑定到签发该连接的授权服务器。如果回调携带 `iss` 参数（RFC 9207），Beeuni 会在其与记录的签发者不匹配时拒绝请求——在兑换前就会拒绝由其他服务器签发的代码。

<a id="authorize-from-the-cli"></a>

### 从 CLI 授权

控制面板只是运行此流程的一种方式，并非唯一方式。在 `beeuni.yaml` 中声明连接器，然后从终端或智能体会话为其授权：

```yaml
connectors:
  - slug: read-ai
    name: Read AI
    provider: mcp
    url: 'https://api.read.ai/mcp'
    auth:
      type: bearer
```

```text
beeuni connectors authorize read-ai --json
```

该命令会创建连接、运行发现链，并在服务器支持 RFC 7591 时将 Beeuni 注册为客户端，然后返回待批准的 URL：

```json
{
  "connection_id": "7b1a16b2-...",
  "registered": true,
  "scopes": ["openid", "offline_access", "mcp:execute", "meeting:read"],
  "authorization_url": "https://authn.read.ai/oauth2/auth?response_type=code&...",
  "expires_at": "2026-08-19T14:48:26.345Z"
}
```

智能体会将 `authorization_url` 返回给它正在协助的人。对方批准后，智能体会确认：

```text
beeuni connectors authorize read-ai --status
```

状态为 `error` 时，命令会以非零状态码退出。使用 `--scope` 缩小请求范围。对于不支持动态客户端注册的服务器，请使用 `--client-id` 和 `--client-secret`。

授权完成后，Beeuni 会立即重新获取连接器的工具目录，因此连接器无需手动同步即可退出 `error` 状态。

<a id="connect-a-direct-api-with-oauth2"></a>

## 使用 OAuth2 连接直接 API

直接连接器支持：

- 客户端凭据
- 带 PKCE 的授权码
- 设备授权
- 动态客户端注册（RFC 7591）

使用客户端凭据时，请输入令牌 URL、客户端 ID、作用域和客户端密钥。你可以使用 `client_secret_basic`、`client_secret_post`、
`client_secret_jwt` 或 `private_key_jwt` 令牌端点身份验证方式。

使用授权码时，请输入授权 URL 和令牌 URL。使用设备授权时，请输入设备授权 URL 和令牌 URL。RFC 8414 发现 URL 可以提供这些端点。

Beeuni 会加密 OAuth2 配置和令牌。它会在访问令牌过期前刷新令牌，并保存每个轮换后的刷新令牌。撤销连接后，下一次连接器调用将被阻止。

对于 Microsoft Graph，请使用：

```text
https://login.microsoftonline.com/{tenant_id}/oauth2/v2.0/token
https://graph.microsoft.com/.default
```

对于直接调用 SharePoint REST API，请使用 SharePoint 资源作用域：

```text
https://{tenant}.sharepoint.com/.default
```

<a id="connect-with-an-api-key"></a>

## 使用 API 密钥连接

1. **声明连接器**

    ```yaml
    connectors:
      - slug: stripe-read
        name: Stripe read access
        provider: openapi
        spec: 'https://raw.githubusercontent.com/stripe/openapi/master/openapi/spec3.json'
        auth:
          type: bearer
        policies:
          - match: 'get_*'
            action: always_run
          - match: '*'
            action: block
    ```

    为保持向后兼容，清单仍接受 `authorization_strategy` 键。Beeuni 会忽略该键：添加凭据时，应针对每个连接设置所有者（共享或私人）。

2. **合并变更请求**

    Beeuni 会从默认分支读取清单。变更请求合并后，连接器便会启用。

3. **添加连接**

    打开连接器并设置其凭据。Beeuni 会对该值进行加密存储，不会将其写入清单。

<a id="grant-an-agent-access"></a>

## 授予智能体访问权限

将连接器 slug 添加到智能体的 `connectors` 字段：

```yaml
agents:
  release-bot:
    connectors: [stripe-read]
```

省略 `connectors` 后，智能体将获得 `none`。合并变更后才会生效。

`connectors_required` 已弃用。清单仍接受此字段，但没有任何代码会读取它：如果连接器没有可用账户，创建会话时也不会拒绝请求。实际上，缺少连接器而拒绝创建会话的情况并不会发生——仅限私人使用的连接器没有可供提供的共享账户，因此调用方无计可施。现在，访问检查是在连接器 **调用** 时进行：获准使用 `stripe-read` 的智能体可以立即开始执行，而只有实际需要该连接器的调用才会被拒绝（参见[调用被拒绝时](#when-a-call-is-denied)）——并附带解决方法。

<a id="select-an-account"></a>

## 选择账户

有两种相互独立的方式可选择调用所用的账户——为整个会话固定一个账户，或在单次调用中指定账户。

<a id="pin-a-session-wide-default"></a>

### 固定整个会话的默认账户

会话可以覆盖连接器的默认账户，供该会话发出的每次调用使用：

```json
{
  "connector_bindings": {
    "stripe-read": {
      "connection_id": "00000000-0000-4000-8000-000000000000"
    }
  }
}
```

绑定键是连接器 slug。其值是此会话的调用方有权访问的账户（连接）。

使用 `GET /projects/{projectId}/sessions/{sessionId}/scope` 读取生效的绑定。使用同一路径上的 `PUT` 替换绑定。替换后无需重启会话，即可在下一次工具调用时生效。

<a id="name-an-account-on-one-call"></a>

### 在单次调用中指定账户

按默认账户优先的顺序，列出连接器可使用的账户：

```text
GET /connectors/projects/{projectId}/connectors/{slug}/accounts
```

```json
{
  "connector": "stripe-read",
  "accounts": [
    { "connection_id": "...", "label": "Shared workspace", "owner_type": "project", "is_default": true, "connected_as": "billing@example.com" },
    { "connection_id": "...", "label": "My Stripe", "owner_type": "member", "is_default": false, "connected_as": null }
  ]
}
```

然后在调用中指定一个账户，可以使用连接 ID 或标签（不区分大小写），也可以使用两个选择器词 `me`（你自己的默认私人账户）和 `project`（项目的默认共享账户）：

```text
beeuni connectors call stripe-read get_balance '{}' --account me
beeuni connectors call stripe-read get_balance '{}' --account "Shared workspace"
```

未指定账户的调用会先解析为调用者自己的默认私人账户，然后是项目的默认共享账户——这与连接器能够持有多个账户之前的行为一致。指定的账户如果无法解析，则会被**拒绝**，绝不会悄悄替换为其他账户（参见[调用被拒绝时](#when-a-call-is-denied)）。每次成功调用的结果都会回显其使用的账户：`account: { connection_id, label,
owner_type }`。

<a id="use-a-connector-in-a-session"></a>

## 在会话中使用连接器

在会话中，使用 Connector CLI：

```text
beeuni connectors ls
beeuni connectors accounts stripe-read
beeuni connectors call stripe-read <action> '<json-args>' [--account <id|label|me|project>]
```

`connectors`列出当前范围内的连接器。`accounts`列出此调用者可以使用哪些账户调用 `stripe-read`——默认账户优先；每组中共享账户排在私人账户之前。`call`运行一个操作，可以使用指定的 `--account`；如果省略，则使用连接器能够持有多个账户之前相同的默认解析方式。

<a id="large-results"></a>

### 大型结果

列表或搜索调用返回的内容可能超出模型的读取能力：超过约 50 KB 的工具输出会被截断。将结果保存到文件，然后用代码处理：

```text
beeuni connectors call linear graphql.issues '{"first":250}' --out /workspace/output/issues.json
# → { "ok": true, "account": …, "saved_to": "/workspace/output/issues.json",
#     "bytes": 93037, "shape": { "issues": { "nodes": "array(250) of {id,title,…}",
#     "pageInfo": { "hasNextPage": true, "endCursor": "…" } } } }
jq '.data.issues.nodes[] | {id, title}' /workspace/output/issues.json
```

`--out`会写入完整的 JSON 结果，并创建父目录。标准输出会显示不含 `data` 的调用信封，以及 `saved_to`、`bytes` 和 `shape`：`data`的概要（键、数组长度和每个 `pageInfo`）。退出代码保持不变。

<a id="attach-files"></a>

### 附加文件

调用参数中不会携带文件字节。先暂存文件，再将其引用放入参数中。网关会在服务器端读取文件字节，并将其写入请求。

```text
beeuni connectors call microsoft-graph.sendmail @mail.json \
  --attach /workspace/artifacts/report.pdf
```

`--attach`会暂存文件，并将引用追加到该操作的 attachments 数组中。可在操作的输入架构中找到该数组：Email 通道使用 `attachments`，Microsoft Graph 使用 `body.message.attachments` `sendMail`。如果架构不常见，请使用 `--attach-path body.message.attachments`为其命名。
大型 JSON 参数应放在文件中（`@mail.json`）或通过 stdin 传入（`-`）。

要自行放置文件，请先暂存：

```text
beeuni connectors upload /workspace/artifacts/report.pdf --connector microsoft-graph
# → { "attachment_id": "…", "ref": { "$beeuni_attachment": "…" }, … }
```

将 `ref`放在参数中的任意位置。网关会按位置替换它：

| `ref`的位置 | 替换为 |
| --- | --- |
| attachments 数组中一个对象类型的元素 | 提供商的附件项。项架构决定具体格式：Microsoft Graph `fileAttachment`（`name` + `contentBytes` 或 `@odata.type`）、Postmark（`Name` + `Content`）、Mailjet（`Filename` + `Base64Content`）、SendGrid 和 Resend（`filename` + `content`）、Brevo（`name` + `content`）。其他任何项格式都会因 `attachment_item_shape_unknown` 而被拒绝。 |
| 字符串字段，例如 `contentBytes` 或 GitHub 文件 `content` | 文件的 base64。 |
| Email 通道的 `attachments[]` | Email 提供商的签名下载 URL。 |

规则：

- 文件必须来自 `/workspace/output`、`/workspace/artifacts`、
  `/workspace/reports` 或 `/workspace/deliverables`。符号链接和硬链接会被拒绝。请先将文件复制到这些位置。
- 一次调用最多可携带 20 个文件，总大小为 25 MiB。上游 API 可能设定更低的限制：Microsoft Graph 文档说明，一个 `sendMail` 请求最多可附加 3 MB 的文件。
- 暂存文件仅对暂存它的会话可见，只能使用一次，并会在 24 小时后删除。调用失败时，文件会释放以供重试。
- 审计记录、审批预览和模型只能看到引用，看不到文件字节。如果上游错误回显了文件内容，其中的 base64 会被移除。
- 托管连接器不接受暂存文件（`connector_attachments_unsupported`）。

MCP 服务器提供相同的流程：`call`接收 `attachment_files`（以及可选的 `attachment_path`），`upload_attachment`返回一个 `ref`。SDK 中的对应方法是 `connectors.uploadAttachment(bytes, { filename, contentType,
connector })`, which returns `ref`。

<a id="call-results"></a>

### 调用结果

`POST /connectors/projects/{projectId}/call`返回一个 JSON 对象。所有绑定（OpenAPI、HTTP、MCP、GraphQL、`composio`、`pipedream`、computers）中的字段都相同：

| 字段 | 状态码 | 值 |
| --- | --- | --- |
| `data` | 200 | 原始上游响应，保持不变。 |
| `output` | 200 | 不含绑定信封的有效负载：`composio` `data.result`、MCP `result.structuredContent ?? result.content`、GraphQL `data.data`。当它与 `data` 相同（OpenAPI、HTTP、Postman、channel、`pipedream` 和 computer 调用）时会省略，因此正文只包含一次有效负载：读取 `output ?? data`。SDK 会填入此字段。 |
| `binding` | 200、500、429、503 | 执行调用的绑定：`openapi`、`http`、`mcp`、`graphql`、`composio`、`pipedream`、`pipedream_proxy`、`postman`、`tunnel`。如果操作解析前就发生错误，则为 `null`。 |
| `upstream_status` | 200、500、429、503 | 上游 HTTP 状态码。没有上游响应时（超时、请求被拒绝、computer）为 `null`。`composio` 和 `pipedream` 会在 200 响应中报告提供商故障，因此 Beeuni 会为此设置 `502`（Linear 查询被拒绝时为 `400`，速率限制时为 `429`）。 |
| `upstream_error` | 200 | 当上游在 2xx 响应中报告故障时，此字段会出现：MCP 工具结果包含 `isError: true` 或 JSON-RPC 错误，或者 GraphQL `errors` 不含数据。调用仍计为已执行：`ok`保持为 `true`。 |
| `retry_after_seconds` | 429、503 | 上游发送的 `Retry-After`，单位为秒。响应还会包含 `Retry-After`标头。 |

调用失败时的 HTTP 状态码：

- 上游 `429`会返回 `429`。上游 `503`会返回 `503`。等待
  `retry_after_seconds`（或延长退避时间）后再调用。
- 其他上游故障返回 `500`，并在 `reason` 中说明原因。

- 上游响应超过 64 MiB 时会返回 `500`，并附带 `reason: "upstream_response_too_large: …"`。Beeuni 会在达到限制时停止读取。请请求较小的分页或使用筛选条件。
- 上游未在截止时间内响应时会返回 `500`，并附带 `reason: "upstream_timeout: …"`。默认截止时间为 60 秒。请将客户端超时设置得更长，以确保网关始终先行响应。

连接器调用不是幂等的。Beeuni 不会对重复调用进行去重。遇到 `upstream_timeout`或客户端中止后，调用可能已在上游执行：重复执行写入操作（例如发送邮件、创建记录）前，请先检查其效果。

<a id="typed-calls-from-code"></a>

### 从代码发起类型化调用

`beeuni connectors types`会为每个可调用的操作生成一个 TypeScript 声明文件。每个操作的参数类型取自其输入架构，结果类型取自其输出架构。结果类型描述调用的 `output`字段。

```sh
beeuni connectors types --out src/beeuni-connectors.d.ts
beeuni connectors types --connector linear,github --out src/beeuni-connectors.d.ts
```

将该文件包含在你的 `tsconfig.json`中。然后通过 `callAction`调用：

```ts
import { createBeeuni } from '@beeuni/sdk';

const connectors = createBeeuni({ backendUrl, getToken }).project(projectId).connectors;
const result = await connectors.callAction('linear', 'list_issues', { team: 'CORE' });
if (result.ok) console.log(result.output?.issues.length);
```

连接器句柄会读取同一个文件，`run`会直接返回类型化输出，或抛出类型化错误（参见“从代码使用连接器”）：

```ts
const issues = await createBeeuni({ backendUrl, getToken })
  .project(projectId)
  .connector('linear')
  .run('list_issues', { team: 'CORE' });
```

`callAction(slug, action, args, options)`发送与 `call('<slug>.<action>', args, options)`相同的请求，并返回相同的结果。两者只有类型不同：缺少或未知的参数会导致编译错误，`output`具有生成的类型。文件中未包含的操作接受任意对象，并返回 `output: unknown`。

- OpenAPI、HTTP、MCP 和 GraphQL 连接器会在其来源声明了输出架构时发布该架构。GraphQL 结果的类型为对象。
- 托管连接器不会发布输出架构。它们的结果是 `unknown`。CLI 会在写入文件后为它们命名。
- 每个 `$defs` 条目都会成为一个命名类型（`BeeuniConnectorDef<n>_<name>`），因此递归架构仍然是有限的。无法编译的架构（包含自身的联合类型），或单个操作的类型超过 256 KB 时，会变成 `unknown`。
- 添加连接器或连接器同步了新版本后，请重新运行此命令。
- 目录仅在请求时返回输出架构：
  `GET /connectors/projects/{projectId}/catalog?include_output_schemas=true`
  会为每个操作添加 `outputSchema`（如果没有存储架构，则为 `null`）。

<a id="when-a-call-is-denied"></a>

### 调用被拒绝时

被拒绝的调用会返回 `{ ok: false, status: "denied", reason, hint }`。`reason` 是稳定代码；`hint` 说明了该怎么做。

| `reason` | HTTP | 含义 | 正文中还包含 |
| --- | --- | --- | --- |
| `connector_not_assigned` | 403 | 运行中的代理在 `beeuni.yaml` 中获得的 `connectors:` 授权不包含此连接器。 | `agent`、`granted`（列表或 `"all"`）、`manifest_revision` 和 `manifest_commit`（授权所依据的清单）。 |
| `connector_not_connected` | 403 | 此调用没有可用的账户。可能是此调用者完全没有该连接器的账户（没有存储凭据，或从未授权该应用——`beeuni connectors ls` 会将其显示为 `needs_auth`）；也可能是 `--account` 指定的账户无法解析。 | 没有任何连接时，如果调用者可以创建授权链接，则会提供 `connect_url`——用于授权账户的托管链接。`--account` 指定了无法解析的名称时，会提供 `requested_account` 和 `available_accounts`——分别为无法解析的名称和已解析的名称。 |
| `connector_disabled` | 403 | 已声明此连接器，但处于禁用状态。 | — |
| `connector_not_found` | 404 | 项目中未声明此 slug 对应的连接器。 | — |
| `action_not_found` | 404 | 此连接器没有此操作。 | — |

对于未连接的连接器，`connect_url` 就是完整的解决办法：会话不会仅仅因为连接器未连接而在开始时被拒绝（参见[授予代理访问权限](#grant-an-agent-access)），因此拒绝本身就会给出解决办法。代理会原样显示链接；网页转录会将其显示为一键式“连接”按钮。默认情况下，该链接会为打开它的人类用户授权一个私人账户——连接共享账户则需要明确选择（`beeuni connectors connect stripe-read --owner project`，须满足 `project.connector.write`）。

创建会话的渠道连接器（Slack、Teams、电子邮件）始终可以从该会话中调用，无论代理的授权如何，因此代理总能在收到请求的线程中进行回复。

会话的授权遵循 `beeuni.yaml`：对运行中代理的 `connectors:` 所做的更改（包括 `beeuni connectors add --apply`）会在下一次调用时生效。授权会记录其来源清单 blob 和提交；如果某次读取将从同一个 blob 替换授权，系统会先通过第二次读取进行确认；来自较早提交的读取则绝不会替换它。

Slack 和 Microsoft Teams 与 OAuth 应用一样，都是从仪表板连接。连接 Slack 会为你写入一个 `channel` 连接器到 `beeuni.yaml`。参见 [Slack 和渠道](/docs/zh/connect/slack)。
