给 AI 编程留一条可审计的交付路径:用这个 Cloudflare Monorepo 模板拆开 HTTP、RPC、队列与 MCP
让 AI 编程助手直接在一个空仓库里生成前端、API、异步任务和 MCP 服务,最快的问题往往不是“能不能跑起来”,而是几周之后还能不能说清:哪个入口该暴露到公网,哪个服务拥有数据库,异步任务由谁消费,Agent 的工具调用究竟能触达什么。若这些边界只存在于某次对话的 prompt 中,代码越多,边界越容易被偶然改坏。
louisbrulenaudet/monorepo-template 是一个 Apache-2.0 许可的 TypeScript 起步仓库。它把 pnpm workspace、Turborepo、Cloudflare Workers、Hono、React/Vite、Tailwind CSS 和 TanStack Router/Query 组合在一起;更值得借鉴的并不是技术栈清单,而是它先为不同运行时角色划分目录、通信方式和端口区间。README 还提供 AI agent hook 与 skills 更新入口,目标是让 Agent 在已有约束内扩展工程,而不是每次都重新发明工程结构。
本文不把它当成“下载即用的万能脚手架”,而是从一个真实场景出发:为一个已有 SaaS 加入“用户上传 CSV、后台处理、前端查看结果,并允许内部 Agent 查询处理状态”的功能。重点是如何利用模板的边界减少 AI 生成代码时最危险的几类混淆。
先区分四种入口,而不是先新建一个大服务
许多小项目起步时只有一个 HTTP 应用:路由、业务逻辑、数据库访问、Webhook 和后台任务都在其中。AI 很容易沿着这个惯性继续添加文件,直到公开 API 与内部能力、同步请求与异步计算混杂在一起。
这个模板将 Cloudflare Workers 按职责命名:worker-api 是面向浏览器或外部客户端的 HTTP 网关;worker- 承担业务逻辑,并在生产环境通过 service binding RPC 被调用;queue- 只消费队列;webhook- 接收第三方回调;mcp- 则提供面向 MCP 客户端的 HTTP 传输层。前端 front-* 只通过 HTTP 调用网关,不直接接触 Worker binding。
这不是形式主义。拿 CSV 场景来说,上传请求需要立即回应,解析和写入可能耗时,内部 Agent 只需读取状态。按角色拆开后,风险点会更清晰:
worker-api校验上传请求、鉴权并投递任务,不把解析逻辑暴露为公共路由;queue-import消费消息,执行耗时解析;worker-import拥有导入记录及其数据库 schema,提供受限的 RPC;mcp-import只定义查询工具,再通过 RPC 调用业务 Worker;front-app只访问网关提供的状态 API。
其中“数据库只由一个 app 拥有”尤其重要。README 明确建议不要建立共享的 packages/db-* schema 包,也不要把同一个 DB binding 附加给多个 app;其他服务应走 service-binding RPC 或队列。这样做会多一层调用,却把写入权从“任何能 import 数据模型的模块”收缩为一个明确的边界。对 AI 来说,这也是可执行的约束:当它被要求新增读取功能,优先新增 RPC 契约或网关路由,而不是绕过拥有者直接连接数据库。
把目录和契约作为 Agent 的上下文,而不是口头约定
模板已给出一组放置规则:HTTP 的 Zod schema 放在 packages/dtos-common/src/api/;RPC、队列、Webhook 的 schema 分别放在对应层级目录;共享字符串值放进 enums-common;某个 Worker 私有的枚举留在该 app 内;数据库 schema 放在唯一拥有者的 src/db/。
它的价值在于把“该放哪里”的回答变成可检索的项目事实。给 Agent 提需求时,可以把验收条件写得具体一些,例如:“导入消息的 schema 必须位于 packages/dtos-common/src/queue/;解析逻辑不得在 worker-api 中直接写库;状态查询由 worker-import 的 RPC 暴露。”这比“保持架构整洁”更容易审查,也更适合让 lint、类型检查和代码评审共同验证。
一个简化的消息契约可以像这样。字段名称需要按业务调整,但边界不应因为实现方便而消失:
// packages/dtos-common/src/queue/import.ts
import { z } from "zod";
export const importJobSchema = z.object({
jobId: z.string().uuid(),
tenantId: z.string().min(1),
sourceKey: z.string().min(1),
});
export type ImportJob = z.infer;
网关负责把已验证的 ImportJob 写入队列;消费者负责处理;业务 Worker 负责状态与持久化。即使以后把 CSV 换成 PDF、把队列换成另一个实现,调用方向仍然容易解释。注意,Zod 只能约束数据形状,不能自动实现租户隔离、文件扫描或幂等性;这些仍要在业务边界中明确设计并测试。
先用本地验证证明边界真的生效
该仓库的 README 要求 Node.js 22+,并建议通过仓库根目录的 Make 目标安装和运行。它刻意推荐 make install 而非裸跑 pnpm install,目的是保持 workspace 链接一致。首次试用不必马上接入真实生产密钥:先复制 Worker 的 .dev.vars.example 为 .dev.vars,前端的 .env.example 为 .env.local,再启动开发环境。
make install make prepare make dev curl http://localhost:8700/api/v1/health
模板预留了角色化端口:HTTP 网关使用 8700–8709,业务 Worker 为 8710–8739,队列消费者为 8740–8759,Webhook 为 8760–8779,MCP 为 8780–8789;前端开发端口在 5170–5199。它并不替代安全策略,但能减少本地多人协作或 Agent 新增服务时随意抢占端口的摩擦。
需要验证 service binding 时,README 给出的方式是一次启动多个 Wrangler 配置,第一个配置作为 HTTP 主入口:
wrangler dev \ -c apps/worker-api/wrangler.jsonc \ -c apps/worker-import/wrangler.jsonc
在真正把 Agent 放进仓库前,建议先让它完成一个小任务,并把 make ci 设为最低交付门槛。README 将其定义为 lint、格式检查和 TypeScript 类型检查的本地 PR gate;它不能替代集成测试、授权测试或人工审查,但能阻止大量“生成后从未编译”的改动进入评审。
给 Agent 设定“改动半径”,再让它开始生成
模板中的 SCOPE、FILTER 与 AFFECTED 变量也适合成为 Agent 任务的执行边界。README 说明,make dev SCOPE=worker-api 可以只启动一个包,make build FILTER=...front-app... 能把构建限定到指定的 Turborepo 过滤表达式,make ci AFFECTED=1 则只检查相对于基线发生变化的包。它们解决不了跨服务回归,却能避免一个仅修改前端文案的任务无谓地触发整仓库开发服务。
更重要的是把范围控制写入交付指令:要求 Agent 先列出会触及的 app、DTO 与 binding,再执行受限的 build 或 CI;一旦需要跨越既定边界,例如让前端直接调用内部 Worker,就应停下来解释原因并请求架构确认。这样,速度来自缩小可验证的改动面,而不是默认给予一次性修改全仓库的权限。
MCP 层要薄:把工具当成受控入口,不是数据库直通车
MCP 很容易把“内部可用”误解为“什么都能调用”。这个模板的设计更适合采用薄工具层:mcp- 是公开 HTTP MCP 服务,但工具实现只通过 RPC 调用 worker-,而非自行持有数据库 binding 或复刻业务规则。
以导入查询工具为例,工具可以只接受 jobId,由业务 Worker 根据调用者身份与租户关系判断是否返回结果。不要因为 Agent 需要“方便地排障”,就提供按任意 tenant、任意日期导出所有原始数据的工具。MCP transport 的认证、输入限制、审计日志和业务授权应分别实现;把它们全寄托在模型“会遵守 prompt”上,等于没有边界。
模板提供了 mcp-* 的位置和通信方向,但不会替你完成威胁建模。特别是涉及上传文件、支付、删除、生产配置或客户数据时,仍应为工具增加最小权限、明确的参数白名单、速率限制以及人工确认点。
适用范围:它是工程约束的起点,不是平台承诺
这个仓库适合已经决定采用 Cloudflare Workers、并且项目至少包含前端、API 与多个运行时角色的团队。它对 AI 辅助开发尤其有帮助,因为目录、命名、端口和调用方向都能被写入仓库,减少了每位开发者或每个 Agent 自由发挥的空间。
但代价也很实际:对于单个页面、一次性脚本或无需异步任务的小服务,RPC、队列、多个配置文件和 Turborepo 可能比问题本身更重。即便采用该模板,也不应把 README 中的推荐结构当成不可违反的教条;应先确认每个 Worker 的所有权、公共面和故障恢复需求,再决定是否新增一个运行时。
最有价值的落地方式是渐进式的:先拿一个新功能建立“网关—业务 Worker—队列”的最小闭环,确认契约、日志、测试和部署路径都能被团队理解;随后才引入 MCP 服务或更多 Worker。AI 能加快编码,但只有当通信边界、数据所有权和交付门槛都留在仓库里时,这种加速才不会变成未来难以追溯的架构债务。
相关链接