Claude Code扩展工具——Channels

cuixiaogang

背景

项目进入维护阶段后,很多需求其实只是小改动——文案调整、配置修改、局部逻辑优化。这类改动如果仍走”产品提需求、开发实现、测试验证、上线”的完整流程,会让简单问题的处理周期被拉长,增加沟通与排期成本。

Claude 这类工具已经具备一定的代码理解和修改能力,能够辅助完成相对简单的开发工作。于是有一个值得尝试的方向:对于维护阶段的大量小需求,能否借助 AI 缩短中间链路,让需求更快落地。

但这件事不能简单理解为”把 Claude 交给产品去用”。一方面 Claude 的使用偏代码层面,产品同学通常不具备直接操作代码的能力;另一方面让非研发角色直接进入开发环境,本身存在管理与安全风险。所以关键不在于工具能力够不够,而在于能否围绕现有协作方式,设计一套适合维护场景的流程机制,让 AI 在可控前提下真正参与到小需求处理中。

这篇笔记记录整套方案的设计,以及其中最核心的一环——用 Claude Channels 打通 Web 平台与开发机上 Claude 的实现。

整体方案

整套流程分为五步,把产品的需求一路送到 Claude,再把改动经测试、提交、流水线送上线。

1. 搭建 ClaudeWeb 平台

开发一个 ClaudeWeb 平台,统一管理多个处于维护阶段的项目。产品经理在平台中选择对应项目,直接与该项目绑定的 Claude 交互,描述需求和修改内容。

2. 打通 ClaudeWeb 与 Claude 的通信

ClaudeWeb 负责在产品经理和 Claude 之间建立通信,把需求准确传递给 Claude,同时接收 Claude 的执行结果和反馈,打通产品与项目代码之间的使用壁垒。

3. 由 Claude 在项目内完成代码修改

Claude 在对应项目的运行环境中工作,根据需求完成代码更新。它的操作要受项目规则和权限范围限制:提前配置好 skills、开发规范、rules、hooks 等机制,用于代码检查、测试执行和开发约束,尽量避免异常修改和不规范代码进入项目。

4. 产品经理在测试环境验收改动

代码改完后,产品经理通过测试地址查看效果,确认是否符合预期。验证通过后,再由产品经理在页面上发起提交 Git 的操作。

5. 通过流水线完成发布上线

流水线持续监听 Git 仓库变更,检测到新的版本提交后自动执行后续发布流程,完成上线。

其中第 2 步”打通 ClaudeWeb 与 Claude 的通信”是最关键也最难的一环——只有这条链路建立起来,产品经理在 Web 端提出的需求才能真正传递到具体项目里的 Claude 并由它执行。这一步的解决方案就是下面要讲的 Claude Channels

Claude Channels 简介

这是什么

Claude Channels 本质上是一套基于 MCP(Model Context Protocol) 的插件机制。它通过 MCP 协议,在 Claude Code 和外部消息平台之间建立一条通信通道,让原本只在本地开发环境中工作的 Claude,具备远程接收消息、理解指令并返回结果的能力。

可以把它理解成一座桥:一边连接开发机上的 Claude,另一边连接 ClaudeWeb 这样的上层平台。平台侧发出的需求和指令通过这座桥传给 Claude,Claude 的执行结果、状态反馈和处理过程再返回到平台侧。

也就是说,Claude Channels 解决的不是”让 Claude 更聪明”,而是”让 Claude 能接入现有流程”,从而成为整个维护链路中一个可用的节点。

消息流转过程

1
2
3
4
5
6
7
8
9
10
11
消息平台(Telegram / Discord / iMessage / 自建 Web)

MCP Server(插件)

<channel> 事件包装

Claude Code 会话

本地环境处理(FS / Git / MCP)

通过暴露的工具回复

上游消息进入 MCP Server 后,被包装成 <channel> 事件推给 Claude Code 会话;Claude 在本地环境完成文件、Git、MCP 等操作后,再通过插件暴露的 reply 工具把结果回传。整条链路的两个方向——“消息进”和”结果出”——就是后面代码要实现的核心。

代码实践

前期准备

  • Claude 客户端:参考 Claude Code 基础概念及使用手册,版本需为最新版(v2.1.80+)。
  • Go 代码库:用于开发 MCP Server 及 HTTP Server。
  • vue-element-admin:用于开发前端。
  • android_analysis_platform:维护阶段的一个真实项目,用作测试对象。
  • 通信方式:这里 MCP 采用 stdio 方式,比较方便,也可以换成其他方式。

消息流转流程
消息流转流程

Channel MCP Server 开发

MCP Server 是整座桥的中枢,它同时干两件事:作为 MCP 插件挂在 Claude Code 上(stdio),又作为 WebSocket 服务端连着前端。下面只保留最能说明桥接原理的核心片段。

注册工具与通知(mcp.go)

关键在两处:注册一个 reply 工具供 Claude 回话,以及在收到前端消息时用 notifications/claude/channel 通知把消息推给 Claude。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
type ChannelServer struct {
mcpServer *server.MCPServer
wsManager *WSManager
session *Session
port int
}

func NewChannelServer(port int) *ChannelServer {
sess := NewSession()
cs := &ChannelServer{wsManager: NewWSManager(sess), session: sess, port: port}

mcpSrv := server.NewMCPServer(
"claude-web", "0.0.1",
server.WithToolCapabilities(false),
// 告诉 Claude:消息以 <channel .../> 形式到达,用 reply 工具带上 chat_id 回复
server.WithInstructions("Messages arrive as <channel source=\"claude-web\" ...>. Reply with the reply tool, passing the chat_id from the tag."),
server.WithExperimental(map[string]any{
"claude/channel": map[string]any{},
"claude/channel/permission": map[string]any{},
}),
)

// 暴露给 Claude 的 reply 工具:Claude → 前端
replyTool := mcp.NewTool("reply",
mcp.WithDescription("Send a message back to the web UI"),
mcp.WithString("chat_id", mcp.Required(), mcp.Description("The conversation to reply in")),
mcp.WithString("text", mcp.Required(), mcp.Description("The message to send")),
)
mcpSrv.AddTool(replyTool, cs.handleReply)

// 前端把消息、权限审批结果转发进来
cs.mcpServer = mcpSrv
cs.wsManager.SetOnChat(cs.handleChatFromBrowser)
// ...权限审批等回调注册省略...
return cs
}

// Claude 调用 reply 工具 → 广播给前端
func (cs *ChannelServer) handleReply(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
args := req.GetArguments()
cs.wsManager.Broadcast(WSMessage{
Type: "chat_reply",
Payload: map[string]string{
"message": args["text"].(string),
"from": "claude",
"chat_id": args["chat_id"].(string),
},
})
return mcp.NewToolResultText("sent"), nil
}

// 前端发消息 → 用 channel 通知推给 Claude
func (cs *ChannelServer) handleChatFromBrowser(message, sender, chatID string) {
cs.mcpServer.SendNotificationToAllClients(
"notifications/claude/channel",
map[string]any{
"content": message,
"meta": map[string]string{"sender": sender, "chat_id": chatID},
},
)
}

服务启动时同时跑两个监听:一个 HTTP/WebSocket 服务(ListenAndServe)对接前端,一个 stdio MCP 服务(stdioSrv.Listen)对接 Claude,这部分是样板代码,此处省略。

会话归属控制(session.go)

多个用户可能同时连上同一个项目的 Claude,需要一个”谁在用”的归属锁,避免指令互相干扰。核心是 Claim / Release 两个方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
type Session struct {
mu sync.RWMutex
owner string
users map[string]bool
}

// 抢占:无人占用或本人已占用才成功
func (s *Session) Claim(user string) bool {
s.mu.Lock()
defer s.mu.Unlock()
if s.owner != "" && s.owner != user {
return false
}
s.owner = user
return true
}

// 释放:只有占用者本人能释放
func (s *Session) Release(user string) bool {
s.mu.Lock()
defer s.mu.Unlock()
if s.owner != user {
return false
}
s.owner = ""
return true
}

// AddUser / RemoveUser / GetState 等在线用户维护方法省略

前端通信(websocket.go)

WebSocket 侧按消息 type 分发,核心是 register(注册在线用户)、chat(发消息,带归属校验)、claim(抢占会话)三类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
type WSMessage struct {
Type string `json:"type"`
Payload map[string]string `json:"payload"`
}

func (wm *WSManager) handleMessage(conn *websocket.Conn, msg WSMessage) {
switch msg.Type {
case "register": // 记录在线用户并广播状态
wm.clients[conn] = &client{conn: conn, user: msg.Payload["user"]}
wm.session.AddUser(msg.Payload["user"])
wm.BroadcastStatus()

case "chat": // 发消息前先校验会话归属
sender := msg.Payload["sender"]
if owner := wm.session.GetOwner(); owner != "" && owner != sender {
wm.sendTo(conn, WSMessage{Type: "error",
Payload: map[string]string{"message": fmt.Sprintf("当前被 %s 占用", owner)}})
return
}
chatID := fmt.Sprintf("%d", nextChatID())
wm.Broadcast(WSMessage{Type: "chat_echo", Payload: map[string]string{
"message": msg.Payload["message"], "sender": sender, "chat_id": chatID}})
wm.onChat(msg.Payload["message"], sender, chatID) // 回调进 handleChatFromBrowser

case "claim": // 抢占会话
if !wm.session.Claim(msg.Payload["user"]) {
wm.sendTo(conn, WSMessage{Type: "error",
Payload: map[string]string{"message": "占用失败"}})
return
}
wm.BroadcastStatus()

// case "release" / "permission_verdict" 等分支省略
}
}

BroadcastSendToOwnerBroadcastStatus 等发送方法与 nextChatID 等辅助函数是样板代码,此处省略。

注册自定义 MCP 插件

在测试项目 android_analysis_platform 下创建 .mcp.json,把上面编译出的 Server 作为 stdio 类型的 MCP 插件注册进去。

1
2
3
4
5
6
7
8
9
10
11
12
{
"mcpServers": {
"claude-web": {
"args": ["channel"],
"command": "/data/cuixiaogang/service/data/claude_web/server/claude-web-server",
"env": {
"CHANNEL_PORT": "15422"
},
"type": "stdio"
}
}
}

启动带该 channel 插件的 Claude:

1
claude --dangerously-load-development-channels server:claude-web

如果不希望 Claude CLI 一直占着当前终端不能停,可以用 tmux 把它放到后台会话里跑。

前端事件处理

前端就是一个聊天窗口,额外要能接收权限授权事件。核心是 _handleMessage:按后端推来的消息 type 分发到不同处理——Claude 回复、用户消息回显、会话状态、权限请求、错误。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
_handleMessage(msg) {
switch (msg.type) {
case WS_MSG_TYPE.CHAT_REPLY: // Claude 的回复
this.store.commit('claude/ADD_MESSAGE', {
role: 'claude', content: msg.payload.message, timestamp: Date.now()
})
break
case WS_MSG_TYPE.CHAT_ECHO: // 自己发的消息回显
this.store.commit('claude/ADD_MESSAGE', {
role: 'user', content: msg.payload.message,
sender: msg.payload.sender, timestamp: Date.now()
})
break
case WS_MSG_TYPE.STATUS: // 会话归属 / 在线状态
this.store.commit('claude/SET_SESSION_STATUS', msg.payload)
break
case WS_MSG_TYPE.PERMISSION_REQUEST: // Claude 请求权限,弹审批
this.store.commit('claude/ADD_PERMISSION_REQUEST', msg.payload)
break
case WS_MSG_TYPE.ERROR:
this.store.commit('claude/SET_ERROR', msg.payload.message)
break
}
}

建连与注册(_doConnectonopen 时发 registeronclose 时重连)是常规 WebSocket 样板,此处省略。

最终成果

小结

环节 要点
方案目标 维护阶段的小需求,借 AI 缩短”产品→开发→测试→上线”链路,且全程可控
核心难点 打通 Web 平台与开发机上 Claude 的通信,即第 2 步
Claude Channels 基于 MCP 的插件机制,在 Claude Code 与外部消息平台间架一条双向通道
MCP Server 中枢:一边 stdio 挂在 Claude 上,一边 WebSocket 连前端
两个方向 前端→Claude 用 channel 通知推入;Claude→前端用 reply 工具回传
安全控制 session 归属锁(Claim/Release)+ 权限审批中继 + 项目内 rules/hooks 约束

延伸方向:给 Claude 配置项目级 skills / hooks 做代码质量卡点,以及把提交 Git、触发流水线也包装成工具接入这条链路。