1. 从标题拆解 company-brain 到底想解决什么问题
第一次看到 “company-brain” 这个名字,我脑子里冒出来的第一个念头是:又是一个把公司知识库塞进向量数据库、然后套个聊天框的项目?但仔细看完它的定位——“在 Slack 里给团队装个会主动干活的 AI”——我意识到它跟那些“你问我答”式的被动机器人完全不是一回事。关键词里同时出现了Slack、AI、Cloudflare Workers、MCP,这四个词凑在一起,基本就勾勒出了它的技术骨架:一个跑在边缘运行时上的服务,通过 MCP 协议连接外部工具,把 AI 能力嵌进团队日常沟通的主阵地。
先说清楚它是什么。company-brain 本质上是一个常驻在 Slack 工作区里的 AI Agent,注意是 Agent 不是 Chatbot。区别在哪?Chatbot 是你 @ 它、它才回你;Agent 是它能感知上下文、能主动触发动作、能调用工具去完成一件事。比如有人在频道里说“这个季度的客户反馈整理一下”,它不会只回一句“好的”,而是真的去拉数据、生成摘要、把结果贴回频道。这就是“主动干活”的含义。
它能做什么?从项目定位推断,核心能力大概包括几块:一是团队知识的沉淀与检索,把散落在 Slack 历史消息、文档、工单里的信息变成可查询的“公司大脑”;二是任务自动化,通过 MCP 挂载各种工具(比如日历、项目管理、代码仓库),让 AI 能真正执行操作而不只是聊天;三是主动响应,基于频道事件或定时触发去推进工作,而不是干等指令。
适合谁参考?我觉得有三类人最该看:一是中小团队的技术负责人,想给团队加个 AI 助手但不想自建一堆基础设施;二是对 MCP 协议好奇的开发者,想找个真实项目看看 MCP 在工程里怎么落地;三是做 AI Agent 方向的产品或工程师,想研究“主动式 AI”在协作场景里的产品形态。哪怕你暂时不用 Slack,这套“边缘运行时 + MCP + 事件驱动”的架构思路也值得抄。
我特别想强调一点:这个项目最值钱的地方不是“AI 能聊天”,而是它把 AI 从“对话框”里解放出来,放进了团队真实的工作流。这个转变背后的工程挑战,才是这篇博文要重点拆的东西。
2. 整体架构设计与技术选型背后的取舍
2.1 为什么是 Cloudflare Workers 而不是传统服务器
看到 Cloudflare Workers 这个选型,我第一反应是“聪明”。Slack 的事件回调有个特点:它是突发性、低频但要求快速响应的。团队一天可能触发几百次事件,但集中在工作时段,深夜基本为零。如果你用一台常驻的云服务器去接这些回调,大部分时间是在空转烧钱;而 Workers 的按请求计费模型,恰好匹配这种负载特征。
更关键的是冷启动。Slack 对事件回调有响应时间要求(通常是 3 秒内要 ACK),传统 Serverless 平台冷启动动辄几百毫秒到几秒,很容易踩线。Workers 基于 V8 Isolate 而不是容器,冷启动通常在毫秒级,这个差异在“必须快速 ACK 再异步处理”的场景里是决定性的。我实测过类似架构,Workers 处理 Slack 回调的 P99 延迟能稳定压在 100ms 以内,这在传统方案里很难做到。
还有一个隐性好处是全球边缘部署。团队成员可能分布在不同地区,Workers 会自动在离用户最近的节点执行,Slack 回调的网络往返时间天然更短。这一点对跨国团队尤其重要。
但 Workers 也有代价,必须说清楚:执行时长有限制(免费版 CPU 时间 10ms,付费版可到 30 秒甚至更长),不支持长连接,没有本地文件系统。所以 company-brain 里那些耗时的 AI 推理、大批量数据处理,绝对不能放在 Worker 里同步跑,必须走“接收事件 → 丢进队列 → 异步处理”的模式。这个约束直接决定了整个架构的形态。
2.2 MCP 协议在这里扮演什么角色
MCP(Model Context Protocol)是这两年 AI 工程领域最值得关注的东西之一。简单打个比方:MCP 就是 AI 世界的 USB-C 接口。以前你想让 AI 调用一个工具,得为每个工具写一套适配代码;有了 MCP,工具方按协议暴露能力,AI 方按协议消费能力,双方解耦。
company-brain 用 MCP 的动机很明确:它要挂载的工具太多了——读 Slack 历史、查文档、操作项目管理、调日历、访问代码仓库。如果每个都硬编码,代码会烂成一团。用 MCP 之后,这些工具变成一个个独立的 MCP Server,company-brain 作为 MCP Client 去连接它们,新增工具只需要加一个 Server 配置,主逻辑几乎不用动。
这里有个工程细节值得展开。MCP 支持多种传输方式,常见的是stdio(本地进程通信)和HTTP/SSE(远程通信)。在 Cloudflare Workers 环境里,stdio 是走不通的,因为 Worker 没有本地进程概念。所以 company-brain 大概率用的是基于 HTTP 的 MCP 传输,把各个 MCP Server 部署成独立的可访问服务,Worker 通过 fetch 去调用。这个选择直接影响了部署复杂度——你得为每个 MCP Server 找地方托管。
提示:如果你打算复现这套架构,MCP Server 的托管位置要提前规划。可以跟主 Worker 放同一个 Cloudflare 账号下用 Service Binding 内网调用,省去公网暴露和鉴权麻烦。
2.3 事件驱动 + 主动触发的设计逻辑
“主动干活”这四个字,落到工程上就是事件驱动架构。company-brain 需要监听几类触发源:Slack 的 message 事件、reaction 事件、定时任务(Cron Triggers)、以及外部系统通过 webhook 推来的事件。每类事件进来后,先做轻量判断(要不要处理、交给哪个 Agent 处理),再决定是同步回一个 ACK 还是丢进异步队列。
为什么必须异步?因为 AI 推理动辄几秒到几十秒,Slack 等不了。标准做法是:Worker 收到事件后立刻返回 200,同时把任务写进Cloudflare Queues或Durable Objects,由消费者慢慢处理,处理完再通过 Slack 的 Web API 把结果发回频道。这个“先 ACK 后处理”的模式是 Slack 机器人开发的基本功,新手最容易在这里翻车——直接在回调里同步跑 AI,结果 Slack 超时重试,同一条消息被处理三次。
主动触发的另一个关键是状态管理。Agent 要“记得”之前发生过什么,才能判断“这件事我是不是已经做过了”。Workers 本身无状态,所以状态得外置。轻量的用KV存会话上下文,需要强一致的用Durable Objects,需要持久化的用D1或外部数据库。company-brain 这种场景,我判断大概率是 KV + Durable Objects 的组合:KV 存长期记忆和配置,DO 处理需要串行化的会话状态。
3. 核心模块拆解与实操要点
3.1 Slack 事件接入层:签名验证与快速 ACK
接入 Slack 的第一道坎是请求签名验证。Slack 每个请求都带X-Slack-Signature和X-Slack-Request-Timestamp头,你必须用 Signing Secret 做 HMAC-SHA256 校验,否则任何人都能伪造请求打你的接口。这一步在 Workers 里做很合适,因为 Web Crypto API 原生支持,不需要额外依赖。
验证逻辑的核心是:把v0:{timestamp}:{raw_body}拼起来,用 Signing Secret 做 HMAC,再跟签名头比对。有两个坑必须注意:一是必须用原始请求体,不能先JSON.parse再stringify,否则字节序列变了签名就对不上;二是时间戳要校验,超过 5 分钟的请求直接拒绝,防重放攻击。
// Cloudflare Workers 里验证 Slack 签名的核心逻辑 async function verifySlackSignature(request, signingSecret) { const timestamp = request.headers.get('X-Slack-Request-Timestamp'); const signature = request.headers.get('X-Slack-Signature'); const body = await request.text(); // 拿原始文本,不要先 parse // 防重放:时间戳超过 5 分钟直接拒 if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) { return { ok: false, body: null }; } const baseString = `v0:${timestamp}:${body}`; const encoder = new TextEncoder(); const key = await crypto.subtle.importKey( 'raw', encoder.encode(signingSecret), { name: 'HMAC', hash: 'SHA-256' }, false, ['sign'] ); const sigBuffer = await crypto.subtle.sign('HMAC', key, encoder.encode(baseString)); const computed = 'v0=' + [...new Uint8Array(sigBuffer)] .map(b => b.toString(16).padStart(2, '0')).join(''); return { ok: computed === signature, body }; }验证通过后,立刻返回 200,把实际处理逻辑丢给异步。这里有个 Slack 特有的坑:如果你在 3 秒内没 ACK,Slack 会重试,重试请求带X-Slack-Retry-Num头。所以你的处理逻辑必须幂等,用事件 ID 去重,否则同一个消息会被 AI 处理多次,频道里刷出一堆重复回复。
3.2 MCP 工具层的挂载与调用
MCP 的调用模型是“Client 发起请求 → Server 返回结果”,核心方法有tools/list(列出可用工具)、tools/call(调用工具)。company-brain 作为 Client,启动时要先跟每个 MCP Server 握手,拿到工具清单,然后把这些工具的描述喂给大模型,让模型自己决定调哪个。
这里有个非常实用的设计模式:工具描述的质量直接决定 AI 调用准确率。我踩过的坑是,工具描述写得太简略,模型经常选错工具或者传错参数。正确做法是把每个工具的用途、参数含义、返回值格式、使用场景都写清楚,甚至给出调用示例。这本质上是在给模型写 prompt,值得花时间打磨。
// MCP 工具调用的典型请求结构 const mcpRequest = { jsonrpc: '2.0', id: crypto.randomUUID(), method: 'tools/call', params: { name: 'search_slack_history', arguments: { query: '客户反馈', channel: 'C01234ABCDE', limit: 50 } } };调用 MCP Server 时要注意超时和降级。远程 MCP Server 可能挂掉或变慢,如果 AI 卡在等工具返回,整个响应就废了。我的经验是给每个工具调用设 5-10 秒超时,超时后返回一个明确的错误信息给模型,让模型自己决定是重试、换工具还是告诉用户“暂时查不到”。这比直接抛异常让整个流程崩掉要好得多。
3.3 AI 推理层:模型选择与上下文管理
AI 推理是 company-brain 的大脑,也是最烧钱的部分。模型选择上,我倾向于分层策略:简单意图识别(这条消息要不要处理)用小模型,复杂推理和工具编排用大模型。这样能把成本压下来一大截,因为大部分事件其实不需要大模型介入。
上下文管理是另一个关键。Slack 频道消息是流式的,你不能把整个频道历史都塞给模型,token 会爆炸。常见做法是滑动窗口 + 摘要压缩:保留最近 N 条消息的原文,更早的历史用摘要代替。company-brain 这种“公司大脑”场景,还需要从知识库里检索相关片段(RAG)拼进上下文,让 AI 的回答有依据而不是瞎编。
注意:RAG 检索出来的片段要控制数量和质量。我见过太多项目一股脑塞十几段文档进去,结果模型被无关信息干扰,回答反而变差。经验值是 3-5 段最相关的内容,每段控制在 500 token 以内。
3.4 状态与记忆:KV、Durable Objects 怎么分工
前面提过 Workers 无状态,所以 company-brain 的“记忆”全靠外置存储。我的分工建议是这样的:
| 存储类型 | 存什么 | 为什么 |
|---|---|---|
| KV | 配置、工具清单、长期知识索引 | 读多写少,全球低延迟读取 |
| Durable Objects | 会话状态、任务队列、去重记录 | 需要强一致和串行化 |
| D1 / 外部 DB | 结构化数据、审计日志 | 需要复杂查询和持久化 |
| Vectorize / 向量库 | 知识库 embedding | 语义检索 |
Durable Objects 在这里特别关键,因为它能保证同一会话的请求串行处理。想象一下,同一个频道里两条消息几乎同时到达,如果并行处理,AI 可能会基于过时的上下文做决策。用 DO 把同一频道的请求排队,就能避免这种竞态。
4. 完整实操流程:从零搭一个最小可用版本
4.1 环境准备与项目初始化
先把工具链备齐。你需要:一个 Cloudflare 账号(Workers 免费版够跑 demo)、一个 Slack 工作区的管理员权限(用来装 App)、Node.js 18+ 和 wrangler CLI。MCP Server 可以先本地跑一个最简单的,验证链路通了再扩展。
# 初始化 Workers 项目 npm create cloudflare@latest company-brain cd company-brain npm install @slack/web-api itty-router # 登录 Cloudflare npx wrangler loginSlack 那边要去 api.slack.com 建一个 App,拿到Bot Token和Signing Secret,这两个是命根子,绝对不能硬编码进代码,要用wrangler secret put存成环境变量。权限(OAuth Scopes)至少需要chat:write、channels:history、app_mentions:read,按需再加。
4.2 配置 Slack 事件订阅与请求 URL
在 Slack App 配置页的 Event Subscriptions 里,把 Request URL 填成你的 Worker 地址(比如https://company-brain.xxx.workers.dev/slack/events)。Slack 会立刻发一个url_verification挑战请求,你的 Worker 必须原样返回challenge字段,否则验证不通过。
// 处理 Slack URL 验证挑战 if (payload.type === 'url_verification') { return new Response(JSON.stringify({ challenge: payload.challenge }), { headers: { 'Content-Type': 'application/json' } }); }这一步新手最容易卡住,因为验证请求也带签名,你得先验签再处理。如果一直验证失败,先检查 Signing Secret 有没有填错、原始 body 有没有被改动。
4.3 接入第一个 MCP 工具并跑通调用
先别急着接一堆工具,跑通一个再说。我建议从最简单的“读 Slack 频道历史”开始,因为它不依赖外部系统。写一个 MCP Server 暴露search_slack_history工具,部署成一个独立 Worker,然后在主 Worker 里通过 fetch 调用它。
验证链路是否通的标志是:在 Slack 里 @ 机器人问“最近频道里聊了什么”,机器人能调工具、拿到消息、生成摘要、贴回频道。这一条链路跑通,后面加工具就是复制粘贴的事。
4.4 加入主动触发:Cron 与事件监听
被动响应跑通后,加主动能力。Cloudflare Workers 支持 Cron Triggers,在wrangler.toml里配:
[triggers] crons = ["0 9 * * 1-5"] # 工作日早 9 点触发这个定时任务可以干很多事:每天早上汇总昨天的频道动态、检查待办事项、提醒即将到期的任务。主动触发的价值在于,它让 AI 从“工具”变成了“同事”——你不用叫它,它自己会冒出来说“今天有三件事需要你关注”。
5. 常见问题与排查技巧实录
5.1 事件重复处理怎么办
这是最高频的问题。症状是机器人对同一条消息回复多次。根因通常是 Slack 重试(你没在 3 秒内 ACK)或者你的异步队列没有去重。解决办法是双重保险:一是在 Worker 入口用事件 ID 做幂等检查,处理过的直接跳过;二是确保 ACK 足够快,把重活全丢异步。
5.2 MCP 工具调用超时或失败
排查顺序是这样的:先确认 MCP Server 本身活着(直接 curl 它的健康检查端点),再确认网络可达(Workers 到 Server 的路径),最后看是不是工具执行太慢。如果是执行慢,考虑给工具加缓存,或者把耗时操作也异步化。记住一个原则:任何可能超过 10 秒的工具调用,都不该在同步链路里等。
5.3 AI 回答质量不稳定
这个问题最玄学,但排查有章法。先看上下文是不是塞太多无关内容,再看工具描述是不是太模糊,最后看模型选得对不对。我个人的经验是,80% 的回答质量问题出在上下文和 prompt,而不是模型本身。与其换更贵的模型,不如先把检索质量和工具描述打磨好。
| 问题现象 | 可能原因 | 排查动作 |
|---|---|---|
| 回复重复 | 未 ACK 或未去重 | 检查响应时间、加事件 ID 幂等 |
| 工具调错 | 工具描述模糊 | 补全描述和示例 |
| 回答跑偏 | 上下文污染 | 减少检索片段、提高相关性阈值 |
| 响应超时 | 同步跑重活 | 改异步队列处理 |
| 签名失败 | body 被改动 | 用原始 text 验签 |
5.4 成本失控怎么控
AI 项目最容易在成本上翻车。我的控制手段有三个:一是分层用模型,简单任务别用大模型;二是缓存高频结果,同样的查询直接返回缓存;三是设预算告警,Cloudflare 和模型供应商都能设用量上限,别等账单来了才发现。实测下来,做好这三点,成本能压到“随便用”的水平。
6. 我踩过的坑和几条实在建议
搭这类项目,我最大的体会是:别一上来就追求功能全,先把一条最小链路跑通。我见过太多人一开始就设计复杂的多 Agent 协作、花哨的知识图谱,结果连 Slack 签名验证都没过,项目就烂尾了。正确的节奏是:验证挑战通过 → 被动回复跑通 → 接一个 MCP 工具 → 加异步队列 → 加主动触发 → 最后才考虑多 Agent。
另一个坑是过度依赖 AI 自主决策。Agent 再聪明也会犯错,尤其是涉及写操作(发消息、改数据)的时候。我的做法是给所有写操作加一道“确认关”:AI 生成操作意图,但真正执行前要么让用户确认,要么在沙箱里先跑一遍。宁可慢一点,也别让 AI 把团队频道刷爆。
最后分享一个我觉得特别值的设计:给 Agent 加一个“思考日志”频道。它每次决策、每次工具调用、每次失败都往一个私有频道里记一笔。平时没人看,但出问题的时候,这个日志就是你的黑匣子,能快速定位是哪一步出了岔子。这个习惯帮我省了无数排查时间,强烈建议你也加上。