做过内容运营的朋友估计都体会过这种崩溃:开完选题会,确认好方向,接下来就是漫长的复制粘贴时间。同一篇文章,在博客站要传封面、改摘要,在专栏要去水印、调排版,在公众号还得额外处理引语和标签。一套手动流程走完,半小时没了,而且这事每周要重复好几次。
所以我最近给自己搭了一条自动化内容发布流水线,核心就两个词:MCP和多 Agent。说白了,就是我有五个分工明确的 AI“员工”在跑一条产线——一个负责选题,一个负责写稿,一个负责审核,一个负责调度,还有一个专门通过标准协议把内容发到不同平台。我这边只需要明确“今天写什么”这个方向,剩下的从初稿到发布链接,基本不用我碰。
先说这条流水线能干什么:输入一个选题关键词,输出的是多个平台上已发布的内容链接;过程中每篇文章的标题、正文、标签、封面格式都会按平台规则自动适配;而且所有环节都有记录,哪一步卡住,一查日志就能定位。
适合谁看?我想大概是三类人。第一类是和我一样每天被多平台发布流程绑住手脚的内容运营或自媒体从业者;第二类是想搞明白 MCP 和多 Agent 到底能怎么落到实际业务里的人;第三类是本身就在做自动化工具、想从零搭一套属于自己的内容生产管线的开发者。下面这篇,我就把整体设计、关键代码和踩过的坑全部摊开来讲。
1. 为什么我最终选了 MCP + 多 Agent 这条路
1.1 之前靠脚本和单 Agent,痛点太明显
最早我的方案特别朴素:封装每个内容平台的接口,写一套发布脚本,然后在程序里调用大模型 API 生成文章再自动发出去。听上去很直接,但用起来问题一个接一个。
第一,每个平台的接口完全是“各家各话”。有的要客户端鉴权,有的走密钥签名,有的直接给一个令牌就能调;数据结构差异更是夸张,标题、摘要、正文、标签、封面比例全都对不上。我每接一个新平台,就要在脚本里补齐一套适配逻辑,平台侧一改字段,脚本立刻失灵。当时确实能跑通,但维护成本高到让人不想碰。
第二,把写作、审核、发布全塞给同一个模型会话,prompt 越写越长,模型越到后来越容易“精神分裂”。让一个模型既当策划又当小编,还要同时检查敏感词和排版格式,它的注意力必然分散。有一次它把一篇技术文章硬改成了客服话术,原因就是审核环节的指令权重太高,把写作风格全带偏了。
第三,发布动作全部写死在应用代码里。想给流水线增加“发布前自动截图预览”这种能力,就得回头改主流程逻辑,牵一发动全身。长此以往,这套东西离“流水线”越来越远,倒更像是“一次性脚本合集”。
1.2 MCP 真正解决了什么问题
MCP(Model Context Protocol,模型上下文协议)在我看来最大贡献不是某个具体功能,而是把“工具接入”和“模型使用工具”之间的边界彻底标准化了。
它简化后的组件结构是:
- Host:你的 Agent 应用,也就是流水线主程序。
- Client:Host 内部负责发起连接的客户端组件。
- Server:独立的 MCP Server 进程,对外暴露资源、工具、提示词。
打个比方,一个 MCP Server 就像是一个“统一规格的插座”。我写好一个发布 Server,它声明自己有三个工具:publish_article、upload_image、get_publish_status,那么任何支持 MCP 的 Host 都能动态发现并调用这些工具,不需要挨个为每个 Host 做定制适配。
这带来几个非常实际的变化:
- 平台适配代码全部沉淀在 Server 端,和业务逻辑解耦。
- Host 可以在运行时动态获取工具列表,不用把工具调用路径写死。
- 多个 Agent 共享同一个发布通道,不需要重复造轮子。
我最终放弃旧脚本体系,核心原因就在这:与其让发布逻辑散落在一堆脚本里,不如统一收敛到一个 MCP Server 中,让所有 Agent 都走这一条规范通道。
1.3 多 Agent 对比单 Agent 的优势
说句实在话,单 Agent 不是不能用,但它擅长的是“我把这一件事尽量做好”,而不是“一件复杂的事被可靠地拆解并执行”。内容生产恰好是后者。
我最后拆了五个角色:编排、选题、写作、审核、发布。每个 Agent 只负责一个环节,prompt 短、上下文干净、输出格式明确。这样做的好处很明显:
- 出问题定位快。谁输出不对,就是谁的 prompt 或者输入数据有问题。
- 每个 Agent 可以独立替换。比如写作 Agent 换成更强的模型,其他环节完全不动。
- 审核不通过就带着审核意见回到写作环节重写,这种反馈闭环,单 Agent 很难清晰实现。
我在第二版里把“审核 Agent 返回的修改意见”原样回传给写作 Agent,重写质量的提升非常明显。这种有明确身份边界的协作,单 Agent 模式下基本做不到。
2. 流水线整体设计:五个环节接力跑完全程
2.1 整体框架与工件流转
整条流水线,我把它想象成一条传送带,五个工位依次处理同一个“内容工件”。工件从一个 JSON 结构开始:
{ "task_id": "20240501-001", "topic": "MCP 协议入门", "target_platforms": ["blog", "column", "wechat"], "status": "todo" }经过每个工位后,这个 JSON 被不断补充,最终变成:
{ "task_id": "20240501-001", "topic": "MCP 协议入门", "final_title": "...", "draft": "...", "final_content": "...", "publish_results": { "blog": "https://...", "column": "https://...", "wechat": "https://..." } }这种“一个工件,多级加工”的模式,比每个 Agent 各搞各的要好维护得多。因为你随时可以知道某个任务在哪个环节,也可以把中间产物存下来做回溯。
2.2 五个 Agent 的职责边界
- 编排 Agent(Orchestrator):主控角色。接收任务,分发给选题 Agent,收集结果后根据状态决定下一步动作,相当于流水线上的“总调度”。
- 选题 Agent(Topic Agent):根据输入的关键词产出一组选题方向,包含标题建议、核心论点和目标读者画像。
- 写作 Agent(Writer Agent):拿到选题后生成完整初稿,输出 Markdown 格式正文,并附带摘要和标签建议。
- 审核 Agent(Reviewer Agent):检查事实准确性、敏感词、格式规范、结构完整性,输出“通过”或“需重写”,重写时附上具体修改意见。
- 发布 Agent(Publisher Agent):调 MCP Server 的
publish_article工具,把最终稿发布到各目标平台,返回链接和状态。
这五个角色放在一个系统里其实不复杂,关键在于每个角色的输入输出都被严格约束成结构化数据,而不是靠自然语言来回倒腾。后面会具体展开。
2.3 状态流转与任务调度设计
状态机管理我用得很简单,但很稳。每个任务经历这些状态:
todo -> topic_selected -> writing -> waiting_review -> reviewing -> approved / rejected -> publishing -> published / failed关键细节:审核被驳回时,任务状态回到writing,同时retry_count加 1,最多重写 3 次,超过就自动转人工。这一步必须要有,否则可能陷入“写作—审核—驳回”的死循环,白白烧掉大量模型调用费。
我用一个 Python 字典维护状态:
task_state = { "task_id": "20240501-001", "state": "reviewing", "retry_count": 0, "history": ["todo", "topic_selected", "writing", "reviewing"] }每个环节只关心两件事:我拿到的是什么状态,我要转成什么状态。这样整套逻辑走下来,代码量不大,边界又非常清楚。
3. 核心实操:自己写一个内容发布 MCP Server
3.1 MCP Server 的最小结构
按照 MCP 的规范,一个 Server 需要向 Host 暴露能力。我用的是官方 SDK,核心代码其实不多。以发布 Server 为例,它要做的事就三件:声明自己是干什么的、注册工具函数、启动并等待请求。
我用 TypeScript 写的最小版本长这样:
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { z } from "zod"; const server = new McpServer({ name: "content-publish-server", version: "1.0.0", }); server.tool( "publish_article", { title: z.string(), content: z.string(), platforms: z.array(z.string()), tags: z.array(z.string()).optional(), }, async ({ title, content, platforms, tags }) => { const results = await publishToPlatforms(title, content, platforms, tags ?? []); return { content: [{ type: "text", text: JSON.stringify(results) }], }; } ); const transport = new StdioServerTransport(); await server.connect(transport);这里的publishToPlatforms才是真正干活的地方:它负责调用各内容平台的上传接口,做格式适配、封面处理、异常重试,然后返回统一的发布结果结构。
3.2 平台适配层怎么写
这是整个 Server 里最实在的部分。每个平台我都会封装成独立的适配函数,对外暴露同一种签名:
interface PublishAdapter { publish(req: PublishRequest): Promise<PublishResponse>; }这样publishToPlatforms里的逻辑就很简单:拿到platforms列表,逐个找对应适配器执行,把结果收集起来:
async function publishToPlatforms(title: string, content: string, platforms: string[], tags: string[]) { const results: Record<string, any> = {}; for (const p of platforms) { const adapter = getAdapter(p); if (!adapter) { results[p] = { success: false, reason: `unknown platform: ${p}` }; continue; } try { const resp = await adapter.publish({ title, content, tags }); results[p] = { success: true, url: resp.url }; } catch (e) { results[p] = { success: false, reason: (e as Error).message }; } } return results; }这样每个平台自己的鉴权、签名、字段映射,全部封装在getAdapter里,后续接新平台只是新增一个适配器,完全不用改主流程。
3.3 本地启动与调试技巧
MCP Server 有两种常见传输方式:stdio和SSE(HTTP)。本地调试我强烈建议先用 stdio 模式,因为它不需要起端口,直接在终端就能跑。
跑起来之后,我用 MCP Inspector 这类调试工具连接本地 Server,它会以图形化界面列出 Server 声明的所有工具,我可以在里面直接测试publish_article,看看传参、返回格式是否正确。这一步非常关键:永远先单独验证 MCP 工具本身没问题,再去接 Agent。否则当 Agent 调用失败时,你很难分清是模型的问题还是工具的问题。
一个小提示:stdio 模式下,Host 启动子进程时的环境变量必须完整。我踩过一次坑,环境里没带上平台鉴权密钥,导致 Server 进程里读不到配置,发布的请求全部 401。后来我把密钥统一放到.env,在启动 MCP Server 的进程里显式加载,问题才消停。
4. 多 Agent 协作:编排、调用与上下文管理
4.1 编排器怎么调度各 Agent
编排层我写的是一段偏命令式的调度代码,不引入太重的工作流框架,减少学习成本。大致逻辑如下:
def run_pipeline(task: dict): # 1. 选题 topic_result = call_agent("topic_agent", task["topic"]) task["candidates"] = topic_result["items"] # 2. 写稿(先用第一个选题) writing_input = { "title": task["candidates"][0]["title"], "outline": task["candidates"][0]["outline"] } draft = call_agent("writer_agent", writing_input) task["draft"] = draft["content"] # 3. 审核 review = call_agent("reviewer_agent", task["draft"]) while review["verdict"] == "rejected" and task.get("retry_count", 0) < 3: task["retry_count"] += 1 draft = call_agent("writer_agent", { "previous_draft": task["draft"], "review_comment": review["comment"] }) task["draft"] = draft["content"] review = call_agent("reviewer_agent", task["draft"]) if review["verdict"] == "rejected": mark_for_human_review(task) return # 4. 发布 publish_result = call_mcp_tool( "content-publish-server", "publish_article", { "title": draft["title"], "content": draft["content"], "platforms": task["target_platforms"], "tags": draft.get("tags", []) } ) task["publish_results"] = publish_result return task这段代码有三个细节要注意:
- 每个
call_agent都要设置超时和重试。模型接口偶尔抽风,超时控制在 60 秒,重试 2 次比较合理,再多的重试也是在烧钱。 - 审核环节的 while 循环是防死循环的关键。没有这个限制,重写可能永远进行下去。
- 发布阶段我复用了同一个 MCP 工具,这也证明了第 3 节设计的价值:发布逻辑只写在 Server 端一份。
4.2 System Prompt 设计:关键是约束输出格式
多 Agent 协作最大的坑,是 Agent 返回的内容“不听话”。所以我的 System Prompt 里写死了输出格式,并且明确要求“只输出 JSON,不要多余解释”。
以审核 Agent 为例,它的 Prompt 核心是:
你是一名内容审核专员。你会收到一篇文章,请从以下维度审查: 1. 事实准确性(是否有明显错误或可疑表述) 2. 合规风险(是否有违规词、敏感表述) 3. 结构完整性(是否有充足引言、分节、结论) 4. 可读性(段落长度是否合理、有没有明显语病) 请严格输出 JSON,结构如下: {"verdict": "approved" 或 "rejected", "comment": "修改意见,仅当 rejected 时填写"} 不要输出任何多余内容。这个 Prompt 不长,但指令非常明确,尤其是“只输出 JSON”那句,省掉了大量解析错误。写作 Agent 的 Prompt 同理,我让它必须输出带title、content、tags三个字段的 JSON。
经验之谈:模型输出偶尔会夹带 Markdown 代码块标记(json),我的解析函数做了容错处理,先把首尾的代码块标记剥掉,再走 JSON 解析。别小看这个,实际跑流程时遇到的格式问题绝大多数都出自这里。
4.3 Agent 之间的内容传递与数据规范化
Agent 之间传数据,我统一用 JSON,而不是直接把一大段文本塞进对话。这样做的好处是:上下文更短、解析更稳、断点续跑也容易。
比如选题 Agent 给我的不是一个标题字符串,而是一个结构化候选列表:
{ "items": [ { "title": "MCP 协议入门:用统一接口连接 AI 与工具", "outline": ["什么是 MCP", "核心组件", "一个最小示例"], "audience": "技术入门者" } ] }我还会把所有中间产物落到本地磁盘,文件名按task_id和环节命名。比如20240501-001_draft.json、20240501-001_review.json。一旦后面哪个环节出了问题,我能直接把对应的 JSON 喂给模型继续处理,不用从零开始重新跑整个流程。
5. 全流程实测:从输入选题到内容上线的完整记录
5.1 环境准备清单
在跑通流水线之前,你需要准备好这几样东西:
| 项目 | 说明 |
|---|---|
| 大模型 API | 写作和审核环节用的主力模型,建议支持 JSON 输出模式 |
| MCP SDK | 用于搭建 MCP Server 的官方 SDK,按语言选 |
| 各平台发布凭据 | 博客、专栏、公众号等各平台的上传接口令牌 |
| 任务队列 | 我这里用最简单的一个 Redis 列表,也可以直接用 Python 的队列 |
| 运行环境 | Node.js 18+ 跑 MCP Server,Python 3.10+ 跑编排逻辑 |
这套组合不挑机器,我本地一台普通笔记本就能完整跑起来。
5.2 发起一次任务,看全流程流转
我拿“MCP 协议入门”这个选题做了一次完整测试。命令很简单:
python orchestrator.py --topic "MCP 协议入门" --platforms blog,column,wechat日志会逐行打出每个状态节点:
2024-05-01 10:00:01 [todo] 任务创建 2024-05-01 10:00:02 [topic_selected] 选题完成,3 个候选 2024-05-01 10:00:15 [writing] 初稿完成,3241 字 2024-05-01 10:00:18 [reviewing] 审核通过,未驳回 2024-05-01 10:00:22 [publishing] 开始发布 2024-05-01 10:00:41 [published] 3 个平台发布完成整个过程40 秒左右,其中大头是大模型生成和平台接口调用的等待时间。对比我之前手动操作:写稿 20 分钟,逐平台发布 30 分钟,总共至少五十分钟起步。现在从选题到上线基本一分钟内搞定,效率差距已经不是一个量级。
5.3 实测效果与质量观察
流水线做出来的内容质量,第一版其实一般般,主要问题集中在:
- 写作 Agent 偶尔会把同一论点换着说法重复几遍,段落显得臃肿。
- 审核 Agent 的判定标准偏松,有些结构差的文章也能“通过”。
- 不同平台的标题长度限制不同,一刀切的标题在公众号上被截断。
这些我都在后续迭代里修了。写作 Agent 加了“每节必须有新观点,禁止重复论述”的约束;审核 Agent 增加了“重复率检测”指令;发布阶段对每个平台执行独立的标题裁剪规则。
到目前稳定运行的状态,流水线产出的文章能达到能发布、能看、不用大改的水平,基础的内容结构和表达都没问题。但我要实话实说:它替代的是“从选题到上线的重复性劳动”,而不是“深度原创内容的生产”。需要独立观点和复杂逻辑的长文,我还是会把写作 Agent 当成扩写工具,自己把关核心框架。
6. 踩坑记录与排查技巧
6.1 常见问题速查表
| 现象 | 根因 | 解决方案 |
|---|---|---|
| Agent 调用 MCP 工具时找不到工具 | MCP Server 没启动,或 Host 连错了 Server | 先单独连 Server,确认工具列表存在 |
| 发布请求返回 401 | 环境变量没传给 MCP Server 子进程 | 检查启动 Server 时的环境变量,显式加载.env |
| 审核一直驳回,循环重写 | 驳回意见太笼统,写作 Agent 不知道怎么改 | 限制驳回时必须给具体修改点,超过 3 次转人工 |
| 内容在某个平台发布失败 | 该平台适配器字段映射错误 | 单独跑该平台适配器的测试函数,打印原始请求响应 |
| 模型输出无法用 JSON 解析 | Prompt 约束不够,或输出带了代码块标记 | 容错剥除代码块标记,并启用模型 JSON 模式 |
| 发布时间不固定 | 没有调度队列,任务即来即跑 | 加一个简单定时调度器,把时间窗口固定 |
6.2 几个容易忽略的细节
第一个是幂等性。发布接口一旦网络超时,而实际请求已经在平台侧成功,重试就会造成重复内容。我的解决方式:每次发布前生成一个client_msg_id,同一个任务的任务 ID 保持一致,平台侧按这个 ID 去重。这条如果你不处理,早晚会在某次日志里看到一模一样的文章被发了两次。
第二个是任务取消。流水线是异步的,运行中途如果你发现选题不对,想停掉任务,需要给状态机增加cancelling状态,并且每个 Agent 调用前都检查一次当前状态是否已取消。否则你只能拔网线,非常被动。
第三个是模型费用。写作 Agent 重写 3 次加上审核 2—3 轮,一次完整流程的大模型调用量相当于 5—7 次单轮生成。如果选题量大,这个费用很容易被忽略。我后来给流水线加了“预算上限”参数,任务累计消耗超过设定值时就转人工,避免模型在死循环里烧掉太多钱。
6.3 我最后想分享的一个经验
整条流水线从动手写第一版到稳定运行,我大概花了两个周末,前后改了三版。最大的教训是:不要第一步就追求全自动、多平台、高质量一步到位。我第一版只接了一个平台的发布,手动跑通了选题到发布的完整链路,确认状态机没问题之后,才开始加第二个、第三个平台,再逐步完善审核和重写逻辑。
MCP 和多 Agent 这套组合,真正值钱的不是“自动化”这三个字,而是它把内容生产这件事,从“靠人盯着的临时工作”变成了“可维护、可追溯、可替换的工程系统”。哪怕你现在手里的站点只有两个,我也建议先把发布 MCP Server 搭起来,哪怕只在本地跑通一次,那种“内容自己流过去”的感觉,和写脚本完全不一样。后面要加平台,加 Agent,都只是填适配器和调 Prompt 的事。