# Channels 参考 [Beta]

> **Beta 功能**：Channels 目前处于 Beta 阶段，协议和接口可能会根据反馈进行调整。

Channel 是一种特殊的 MCP 服务器，可以将外部事件（Webhook、聊天消息、监控告警等）推送到 CodeBuddy Code 会话中，让 CodeBuddy Code 对终端外部发生的事情做出反应。

## 概述

Channel 运行在与 CodeBuddy Code 相同的机器上，通过 stdio 与 CodeBuddy Code 通信。CodeBuddy Code 将其作为子进程启动。

典型的使用场景：

- **聊天平台**（Telegram、Discord）：插件本地运行，轮询平台 API 获取新消息，转发给 CodeBuddy Code
- **Webhook**（CI、监控）：服务器监听本地 HTTP 端口，接收外部系统的 POST 请求，推送给 CodeBuddy Code

Channel 分为两种模式：

| 模式 | 说明 |
|------|------|
| 单向 | 只转发事件给 CodeBuddy Code（告警、Webhook），在本地会话中处理 |
| 双向 | 额外暴露 reply 工具，让 CodeBuddy Code 可以回复消息 |

## 使用 Channel

### 启动

使用 `--channels` 参数指定要加载的 channel：

```bash
# 加载插件类型的 channel
codebuddy --channels plugin:fakechat@claude-plugins-official

# 加载 .mcp.json 中配置的 channel server
codebuddy --channels server:webhook

# 加载多个 channel（逗号分隔）
codebuddy --channels plugin:telegram@claude-plugins-official,plugin:discord@claude-plugins-official
```

### 开发模式

自定义 channel 在初始阶段使用 `--dangerously-load-development-channels` 标志来测试。此标志允许任何 channel 运行，无需在允许列表中：

```bash
codebuddy --dangerously-load-development-channels server:my-webhook
```

此标志仅绕过允许列表检查，`channelsEnabled` 组织策略仍然生效。一旦 channel 提交到官方市场并通过安全审查，就会被添加到允许列表，之后可以直接使用 `--channels` 加载。

### 会话中的 Channel 消息

Channel 消息以 `<channel>` 标签的形式注入到 CodeBuddy Code 的上下文中：

```xml
<channel source="fakechat" sender="web" chat_id="1">你好，请帮我看看这个问题</channel>
```

在 TUI 中，channel 消息以友好格式显示：

```
#fakechat · web: 你好，请帮我看看这个问题
```

### 设置

在 `settings.json` 中可以控制 channel 功能：

```json
{
  "channelsEnabled": true
}
```

设为 `false` 可完全禁用 channel 功能。

## 构建 Channel

### 基本要求

一个 channel server 需要：

1. 声明 `claude/channel` capability，让 CodeBuddy Code 注册通知监听器
2. 发送 `notifications/claude/channel` 事件
3. 通过 stdio transport 连接（CodeBuddy Code 将其作为子进程启动）

### 最小示例：Webhook 接收器

```ts
#!/usr/bin/env bun
import { Server } from '@modelcontextprotocol/sdk/server/index.js'
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'

const mcp = new Server(
  { name: 'webhook', version: '0.0.1' },
  {
    capabilities: { experimental: { 'claude/channel': {} } },
    instructions: '来自 webhook 的事件以 <channel source="webhook" ...> 标签到达。单向通道，只需阅读并处理。',
  },
)

await mcp.connect(new StdioServerTransport())

Bun.serve({
  port: 8788,
  hostname: '127.0.0.1',
  async fetch(req) {
    const body = await req.text()
    await mcp.notification({
      method: 'notifications/claude/channel',
      params: {
        content: body,
        meta: { path: new URL(req.url).pathname, method: req.method },
      },
    })
    return new Response('ok')
  },
})
```

注册到 `.mcp.json`：

```json
{
  "mcpServers": {
    "webhook": { "command": "bun", "args": ["./webhook.ts"] }
  }
}
```

## Server 配置选项

| 字段 | 类型 | 说明 |
|------|------|------|
| `capabilities.experimental['claude/channel']` | `object` | **必填**。始终为 `{}`。声明后 CodeBuddy Code 注册通知监听器 |
| `capabilities.experimental['claude/channel/permission']` | `object` | 可选。始终为 `{}`。声明此 channel 可以接收权限中继请求 |
| `capabilities.tools` | `object` | 双向 channel 需要。始终为 `{}`。标准 MCP 工具能力 |
| `instructions` | `string` | 推荐。注入到 system prompt，告诉 CodeBuddy Code 如何处理此 channel 的事件 |

## 通知格式

发送 `notifications/claude/channel` 通知时，params 包含两个字段：

| 字段 | 类型 | 说明 |
|------|------|------|
| `content` | `string` | 事件内容，成为 `<channel>` 标签的正文 |
| `meta` | `Record<string, string>` | 可选。每个键值对成为 `<channel>` 标签的属性。键名仅允许字母、数字和下划线，包含连字符或其他字符的键会被静默丢弃 |

示例：

```ts
await mcp.notification({
  method: 'notifications/claude/channel',
  params: {
    content: 'build failed on main',
    meta: { severity: 'high', run_id: '1234' },
  },
})
```

到达 CodeBuddy Code 时的格式：

```xml
<channel source="webhook" severity="high" run_id="1234">
build failed on main
</channel>
```

## 暴露 Reply 工具

双向 channel 需要暴露标准 MCP 工具让 CodeBuddy Code 回复消息：

```ts
import { ListToolsRequestSchema, CallToolRequestSchema } from '@modelcontextprotocol/sdk/types.js'

mcp.setRequestHandler(ListToolsRequestSchema, async () => ({
  tools: [{
    name: 'reply',
    description: '通过此 channel 发送回复消息',
    inputSchema: {
      type: 'object',
      properties: {
        chat_id: { type: 'string', description: '要回复的对话 ID' },
        text: { type: 'string', description: '要发送的消息' },
      },
      required: ['chat_id', 'text'],
    },
  }],
}))

mcp.setRequestHandler(CallToolRequestSchema, async req => {
  if (req.params.name === 'reply') {
    const { chat_id, text } = req.params.arguments as { chat_id: string; text: string }
    // 调用你的聊天平台 API 发送消息
    await sendToPlatform(chat_id, text)
    return { content: [{ type: 'text', text: 'sent' }] }
  }
  throw new Error(`unknown tool: ${req.params.name}`)
})
```

## 发送者安全管控

未设防的 channel 是 prompt 注入攻击向量。任何能访问你端点的人都可以向 CodeBuddy Code 注入文本。

在调用 `mcp.notification()` 之前，必须检查发送者身份：

```ts
const allowed = new Set(loadAllowlist())

// 在消息处理器中，发送通知之前：
if (!allowed.has(message.from.id)) {  // 检查发送者 ID，而非群组 ID
  return  // 静默丢弃
}
await mcp.notification({ ... })
```

**重要**：始终基于发送者身份（`message.from.id`）而非聊天室身份（`message.chat.id`）进行验证。在群聊中，这两个值不同，按群组验证会让群内任何人都能向会话注入消息。

## 权限中继

当 CodeBuddy Code 调用需要审批的工具（如 `Bash`、`Write`、`Edit`）时，本地终端会打开权限对话框。双向 channel 可以选择同时接收这个提示，让你在远程设备上审批或拒绝。

本地终端和远程 channel 的对话框同时打开，**先到的回答生效**，另一个自动关闭。

### 启用权限中继

在 Server 构造器中添加 `claude/channel/permission`：

```ts
capabilities: {
  experimental: {
    'claude/channel': {},
    'claude/channel/permission': {},  // 启用权限中继
  },
  tools: {},
},
```

### 权限请求字段

CodeBuddy Code 发送 `notifications/claude/channel/permission_request`，包含以下字段：

| 字段 | 说明 |
|------|------|
| `request_id` | 5 个小写字母（排除 `l`），用于匹配回复 |
| `tool_name` | CodeBuddy Code 要使用的工具名，如 `Bash`、`Write` |
| `description` | 工具调用的可读描述 |
| `input_preview` | 工具参数的 JSON 字符串，截断到 200 字符 |

### 发送裁决

你的 channel 需要发送 `notifications/claude/channel/permission` 通知：

```ts
await mcp.notification({
  method: 'notifications/claude/channel/permission',
  params: {
    request_id: '<收到的 request_id>',
    behavior: 'allow',  // 或 'deny'
  },
})
```

### 处理入站裁决

在入站消息处理器中识别 `yes <id>` 或 `no <id>` 格式的回复：

```ts
// 匹配 "y abcde", "yes abcde", "n abcde", "no abcde"
// [a-km-z] 是 CodeBuddy Code 使用的 ID 字母表（小写，跳过 'l'）
const PERMISSION_REPLY_RE = /^\s*(y|yes|n|no)\s+([a-km-z]{5})\s*$/i

const m = PERMISSION_REPLY_RE.exec(message.text)
if (m) {
  await mcp.notification({
    method: 'notifications/claude/channel/permission',
    params: {
      request_id: m[2].toLowerCase(),
      behavior: m[1].toLowerCase().startsWith('y') ? 'allow' : 'deny',
    },
  })
  return  // 作为裁决处理，不转发为聊天
}
```

## 打包为插件

将 channel 包装为插件可以方便分享和安装。用户通过 `/plugin install` 安装，然后用 `--channels plugin:<name>@<marketplace>` 启用。

## 参考

- [fakechat 示例](https://github.com/anthropics/claude-plugins-official/tree/main/external_plugins/fakechat)：完整的双向 channel 实现，包含 Web UI、文件附件和 reply 工具
- [MCP 协议](https://modelcontextprotocol.io)：channel 基于 MCP 协议实现
