构建一个 MCP 服务器作为 Channel,可以把外部事件(Webhook、聊天消息、CI 告警)推送到 Claude Code 会话,让 Claude 响应终端外发生的事情。单向 Channel 只推送事件,双向 Channel 还需暴露回复工具;权限中继(permission relay)让你可以从手机远程批准工具调用,但需要先做发送方白名单验证防止提示注入。诊断连接问题用 /mcp 命令查看服务器状态,或检查 ~/.claude/debug/<session-id>.txt 的 stderr 日志。

Channels 技术参考

Channels 处于研究预览阶段,需要 Claude Code v2.1.80 或更高版本。Team 和 Enterprise 组织需管理员明确启用

Channel 是一个将外部事件推送到 Claude Code 会话的 MCP 服务器,使 Claude 能够响应终端之外发生的事情。

  • 单向 Channel:转发告警、Webhook 或监控事件供 Claude 响应。
  • 双向 Channel:类似聊天桥接,还暴露一个回复工具,让 Claude 可以发送消息。
  • 具有可信发送方路径的 Channel 还可选择加入权限中继,让你可以远程批准或拒绝工具使用。

概览

Channel 是运行在与 Claude Code 同一台机器上的 MCP 服务器。Claude Code 将其作为子进程启动,通过 stdio 通信。Channel 服务器是外部系统与 Claude Code 会话之间的桥梁:

  • 聊天平台(Telegram、Discord):插件在本地运行,轮询平台 API 获取新消息。无需暴露 URL。
  • Webhook(CI、监控):服务器监听本地 HTTP 端口,外部系统 POST 到该端口,服务器将负载推送给 Claude。

Channels 架构图


前提条件

只需要 @modelcontextprotocol/sdk 包和兼容 Node.js 的运行时(Bun、Node 或 Deno 均可)。研究预览内的预构建插件用 Bun,你的 Channel 不必跟随。

服务器需要:

  1. 声明 claude/channel 能力,以便 Claude Code 注册通知监听器
  2. 在事件发生时发送 notifications/claude/channel 通知
  3. 通过 stdio transport 连接(Claude Code 将你的服务器作为子进程启动)

研究预览期间,自定义 Channel 不在官方批准名单上。使用 --dangerously-load-development-channels 在本地测试。


示例:构建 Webhook 接收器

这个演示构建一个单文件服务器,监听 HTTP 请求并将其转发到 Claude Code 会话。任何能发 HTTP POST 的系统(CI 流水线、监控告警、curl 命令)都可以向 Claude 推送事件。

最小实现

#!/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 的事件以 <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 服务器并声明 claude/channel 能力,instructions 字符串会被加到 Claude 的系统提示中,告知事件格式和处理方式。
  • Stdio 连接:通过 stdin/stdout 连接到 Claude Code。
  • HTTP 监听器:启动本地 Web 服务器,监听 8788 端口。每个 POST 请求的正文通过 mcp.notification() 转发给 Claude。

.mcp.json 中注册(项目级用相对路径,用户级用绝对路径):

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

测试(研究预览期间需要开发标志):

claude --dangerously-load-development-channels server:webhook

另一个终端模拟 Webhook:

curl -X POST localhost:8788 -d "main 分支构建失败:https://ci.example.com/run/1234"

Claude 会收到 <channel source="webhook" path="/" method="POST">main 分支构建失败:https://ci.example.com/run/1234</channel>

如果事件没到达,按以下排查:

  • curl 成功但 Claude 没反应:运行 /mcp 检查服务器状态。“Failed to connect” 通常表示依赖或导入错误,查看调试日志 ~/.claude/debug/<session-id>.txt 中的 stderr 输出。
  • curlconnection refused:端口未绑定或旧进程占用。lsof -i :<port> 查看监听进程,kill 掉后再重启会话。

研究预览期间测试

研究预览期间,每个 Channel 必须在 Anthropic 管理的批准名单上才能注册。使用 --dangerously-load-development-channels 可以为特定条目绕过名单(需确认提示):

# 测试开发中的插件
claude --dangerously-load-development-channels plugin:yourplugin@yourmarketplace

# 测试裸 .mcp.json 服务器(尚无插件包装)
claude --dangerously-load-development-channels server:webhook

该标志只绕过批准名单。组织策略 channelsEnabled 仍然生效。不要用它在不可信来源上运行 Channel。


服务器选项

Channel 在 Server 构造器中设置这些选项:

字段 类型 说明
capabilities.experimental['claude/channel'] object 必填,始终 {}。注册通知监听器。
capabilities.experimental['claude/channel/permission'] object 可选,始终 {}。声明此 Channel 可接收权限中继请求。
capabilities.tools object 双向 Channel 需要,始终 {}。标准 MCP 工具能力声明。
instructions string 推荐填写。添加到 Claude 的系统提示,告知事件格式、属性含义、是否回复以及使用哪个工具。

一个双向 Channel 的构造器示例:

import { Server } from '@modelcontextprotocol/sdk/server/index.js'

const mcp = new Server(
  { name: 'your-channel', version: '0.0.1' },
  {
    capabilities: {
      experimental: { 'claude/channel': {} },
      tools: {}, // 单向 Channel 省略这行
    },
    instructions: '消息以 <channel source="your-channel" ...> 到达。用 reply 工具回复。',
  },
)

通知格式

通过 mcp.notification() 推送事件,method 为 notifications/claude/channel

字段 类型 说明
content string 事件正文,作为 <channel> 标签的内容。
meta Record<string, string> 可选。每个键值对变为 <channel> 标签的属性。键只能包含字母、数字、下划线,含连字符等其他字符会被静默丢弃。
await mcp.notification({
  method: 'notifications/claude/channel',
  params: {
    content: 'build failed on main: https://ci.example.com/run/1234',
    meta: { severity: 'high', run_id: '1234' },
  },
})

Claude 收到的事件(source 属性由服务器名称自动设置):

<channel source="your-channel" severity="high" run_id="1234">
build failed on main: https://ci.example.com/run/1234
</channel>

通知不保证送达。await mcp.notification() 在消息写入传输层时就返回,不等 Claude 处理。如果会话未加载你的服务器作为 Channel,或组织策略禁止,事件会被静默丢弃。如果需要确认送达,在服务器内跟踪事件状态,并暴露一个回复工具让 Claude 报告状态。

事件排队进入会话,按顺序处理。多个通知同时到达时,Claude 在下一轮一起处理它们。要并发处理独立事件流,运行多个会话。


暴露回复工具(双向 Channel)

双向 Channel 需要暴露一个标准 MCP 工具让 Claude 可以发送回复,包含三个组成部分:

  1. 服务器构造器 capabilities 中的 tools: {} 条目
  2. 定义工具 schema 和实现发送逻辑的工具处理器
  3. 服务器构造器中的 instructions 字符串,告知 Claude 何时调用工具以及传入哪些参数

将以下代码添加到 webhook.ts(在 Server 构造器和 mcp.connect() 之间):

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 }
    // send() 你的发送逻辑(POST 到聊天平台 API,或 SSE 广播等)
    send(`Reply to ${chat_id}: ${text}`)
    return { content: [{ type: 'text', text: 'sent' }] }
  }
  throw new Error(`unknown tool: ${req.params.name}`)
})

更新 instructions

instructions: '消息以 <channel source="webhook" chat_id="..."> 到达。用 reply 工具回复,传入标签中的 chat_id。'

完整双向示例见上游文档中的 Full webhook.ts with reply tool(可展开的代码块),我们会在末尾提供链接。


验证入站消息(防止提示注入)

未验证的 Channel 是提示注入的入口:任何能访问你端点的人都能在 Claude 面前插入文本。聊天平台或公开端点的 Channel 必须在发送通知前验证发送方。

const allowed = new Set(loadAllowlist()) // 从 access.json 或配置加载

// 在消息处理器中,发送前检查:
if (!allowed.has(message.from.id)) { // 基于发送方身份
  return // 静默丢弃
}
await mcp.notification({ ... })

重要:基于发送方身份(message.from.id)而非聊天室身份(message.chat.id)验证。在群聊中,两者不同——仅验证聊天室会让允许名单中群组的任何成员都能向 Claude 注入消息。

Telegram 和 Discord 插件以相同方式基于发送方白名单验证,通过配对流程自举列表。iMessage 则检测用户自己的地址自动放行。


中继权限提示

权限中继需要 Claude Code v2.1.81 或更高版本。早期版本忽略 claude/channel/permission 能力。

当 Claude 调用需要批准的工具(如 BashWriteEdit)时,本地终端会打开对话框,会话等待。双向 Channel 可以选择接收相同提示,并将其中继到另一台设备(如手机)。本地终端和远程端均保持有效,先到达的回复被采用

注意:项目信任和 MCP 服务器同意对话框中继,只在本地终端显示。

中继流程

  1. Claude Code 生成短请求 ID,通过 notifications/claude/channel/permission_request 通知你的服务器
  2. 你的服务器将提示和 ID 转发到聊天应用
  3. 远程用户用该 ID 回复“是”或“否”
  4. 你的入站处理器解析回复为裁决,通过 notifications/claude/channel/permission 发回给 Claude Code
  5. Claude Code 只接受 ID 匹配的裁决,先到的回复生效

权限请求字段

出站通知 notifications/claude/channel/permission_request 包含以下 params 字段:

字段 说明
request_id 5 个小写字母(不含 l),避免与 1 混淆。包含在提示中,用户回复时回传。
tool_name 工具名称(如 BashWrite
description 可读的操作摘要(与本地对话框相同的文本)
input_preview 工具参数的 JSON 字符串(截断到 200 字符),可选是否在提示中显示

回复裁决方法:notifications/claude/channel/permission,包含 request_idbehavior'allow''deny')。

添加权限中继到聊天桥接

需要同时做三件事:

  1. 声明权限能力:在 Server 构造器的 experimental 下添加 'claude/channel/permission': {}
  2. 处理权限请求通知:注册 setNotificationHandler,解析 PermissionRequestSchema,格式化提示发送给用户。
  3. 拦截入站裁决:在入站消息处理器中先匹配 yes <id> / no <id> 格式,匹配则发送裁决通知,不回传给 Claude。

只有你的 Channel 验证了发送方才应该声明 claude/channel/permission 能力,因为能回复的人就能批准工具调用。

匹配裁决的正则表达式:

const PERMISSION_REPLY_RE = /^\s*(y|yes|n|no)\s+([a-km-z]{5})\s*$/i

回复时用 m[2].toLowerCase() 作为 request_id,用 m[1] 是否以 y 开头确定 behavior

完整的带权限中继的 webhook.ts 示例见上游文档中的 Full webhook.ts with permission relay(可展开代码块)。


将 Channel 打包为插件

要让 Channel 可安装和共享,将其封装在插件中并发布到市场。用户通过 /plugin install 安装,然后用 --channels plugin:<name>@<marketplace> 启用。

发布到你自己市场的 Channel 仍需 --dangerously-load-development-channels 运行,因为它不在官方批准名单上。默认批准名单是 claude-plugins-official 中的 Channel 插件,由 Anthropic 管理。社区市场的插件不在 Channel 批准名单中。Team/Enterprise 管理员可以将你的插件加入组织自己的 allowedChannelPlugins 列表。


相关文档

  • Channels — 安装和使用 Telegram、Discord、iMessage 或 fakechat 演示,以及启用组织 Channel 设置
  • GitHub 上的参考实现 — 包含配对流程、回复工具和文件附件的完整代码
  • MCP — Channel 服务器实现的底层协议
  • 插件 — 将 Channel 打包为可安装插件

常见问题

Channel 和普通 MCP 服务器有什么区别?

普通 MCP 服务器是被动的——Claude 在任务中主动查询它们(如读取文件、搜索)。Channel 是主动的——外部系统可以向正在运行的 Claude Code 会话推送事件,Claude 会主动响应。技术上,Channel 就是声明了 claude/channel 能力的 MCP 服务器。

如何防止 Channel 成为提示注入入口?

在调用 mcp.notification() 之前,基于发送方身份(如 message.from.id)验证,而不是聊天室身份(message.chat.id)。群聊中只有允许名单中的发送方才能推送消息。Telegram 和 Discord 的官方插件通过发送方白名单 + 配对流程实现。iMessage 则自动检测用户自身地址。

权限中继的 request_id 为什么不含字母 l?

request_id 是 5 个小写字母(字母范围 a-z 排除 l),避免字母 l 和数字 1 在手机小屏幕上混淆,提高远程输入准确率。用户回复时需要原样回传该 ID。