做一个自动发布掘金文章的 MCP 工具时,我们遇到过一个尴尬结果:发布请求返回了文章 ID,创作者后台也能找到文章,但工具读取公开页正文时失败了。此时如果直接报告"发布成功",读者看到的文章还没核实;如果把核验失败当成发布失败再提交一次,又可能制造重复文章。
这次测试稿最后通过只读核验确认已发布。它给写入型自动化划出一条判断边界:写入响应、后台记录和外部可见结果,是不同层次的证据。结果不明时应保留未知状态,继续读取同一个目标。
先定义"完成"指什么
这个工具的输入是一份本地文章,输出是掘金上的草稿或公开文章。测试时,我们需要依次确认:操作的是指定账号;远端草稿与本地稿的标题、正文一致;发布后创作者后台存在目标文章;匿名打开的公开页也能读到目标标题和正文。
这几层不能互相代替。接口返回文章 ID,说明请求得到了一个标识,但还不足以证明公开页的实际内容。后台列表能证明目标文章出现在账号里,仍不能替代匿名视角的正文核对。反过来,核验器没找到公开页正文,也不能直接证明文章没有发布。
两次"失败",暴露的是同一个判断问题
创建草稿时,请求已经返回草稿 ID,后续详情解析却失败了。代码原先按顶层 data 解析草稿,实际响应把草稿放在 data.article_draft。这时远端可能已有草稿;再调用一次创建接口,有产生重复稿件的风险。我们保留"保存结果不明",按已返回的草稿 ID 读详情和列表,修正解析后确认标题与正文一致。
发布测试又出现了更典型的情况。发布请求只调用了一次,返回文章 ID;创作者文章列表按 ID、标题和作者找到了目标。但首次匿名公开页核验没有读到正文,工具把状态留在 publish_unknown。排查发现,问题在本地核验器的旧 CSS 选择器:当时页面的正文容器实际位于 article[data-entry-id] .article-viewer。修正定位后,我们没有重发文章,只对同一个 ID 做只读恢复;匿名页面上的标题和正文与测试稿一致,才将状态改为 published_verified。
当时的证据链可以压缩成四个观察,顺序也决定了状态何时能从"未知"变为"已核验":
| 观察 | 能说明什么 | 还不能说明什么 |
|---|---|---|
| 发布接口返回文章 ID | 请求得到了目标标识 | 公开页能否读到正确正文 |
| 创作者列表匹配 ID、标题和作者 | 指定账号下存在目标文章 | 匿名读者看到的内容是否正确 |
| 首次匿名核验找不到正文容器 | 当时的核验未通过 | 文章一定没有发布 |
| 修正定位后,匿名页路径、标题、正文匹配 | 这篇测试稿满足本次发布验收条件 | 未来页面变化或复杂内容也会通过 |
这里能确认的直接原因是本地解析和页面定位假设与当时的真实响应不一致。不能据此说平台曾经发布失败,也不能从一次测试推出接口以后都稳定。
把"不知道"做成状态
实现里,发布请求前先核对账号、草稿 ID、标题、正文和发布元数据。请求发出后,无论是否拿到文章 ID,都先进入 publish_unknown。只有账号文章列表与匿名公开页都匹配,才进入 published_verified。发布结果不明时,恢复函数只读取目标,不再次调用发布。
核心判定在本地代码中是这样的(为突出判定条件,省略了身份检查和返回对象中的其他字段):
ts
const creatorMatches = await inspectAccountArticleListing(
page, uuid, identity.profileId, articleId, record.title
);
const publicPage = await inspectPublicArticle(articleId, record);
if (!creatorMatches || publicPage.pageState !== 'article') {
return { verified: false, state: 'publish_unknown' };
}
await updateDraftRecord(draftRef, { stage: 'published_verified' });
这里的 article 判定并非只看页面能打开:核验器还比对文章路径中的 ID、标题和正文。若发布响应没有文章 ID,实现会按来源草稿 ID、标题和作者在账号文章列表中找目标;找不到就继续保持未知。它不会把一次查询失败改写成"未发布",也不会自动重新提交。
我认为这比简单的成功/失败二值状态更贴近写入操作的现实:写入请求与后续核验之间,确实可能出现"远端已变化,但本地还没拿到足够证据"的时间段。unknown 保留了这种不确定性,也给安全恢复留出了入口。
这次到底验证到了哪一步
2026 年 9 月 24 日的测试中,一篇明确标记的测试稿只发布了一次。返回文章 ID 后,创作者列表找到了精确目标;修正公开页定位后,独立匿名浏览器读到匹配的标题和正文,状态才转为已核验发布。测试稿后来被删除,没有留作公开文章。
当时本地回归测试记录为 24/24 通过;本地测试不能替代真实平台核验。创作者列表和匿名公开页的读回,验证了那次测试稿的发布结果。本文整理的是已有验收记录,没有在写稿时重新发布或复测平台。不同账号切换、复杂图片排版,以及平台接口或页面变化后的表现,都不在这次结论范围内。
给写入型自动化留一条恢复路径
下次给 MCP 或 Agent 接入外部写操作,我会在实现前写清四件事:
- 确定唯一目标:绑定账号、来源稿件和远端 ID;标题、正文版本也要可比对。
- 区分响应与完成:写入返回值只作为一层证据,按任务目标选择详情、列表或外部可见页面继续核验。
- 保存未知状态:请求可能已到达远端而核验失败时,保留已有 ID 和上下文,先读回,不直接重试写入。
- 按证据更新状态:这类公开发布任务要核对目标身份、内容和公开页;满足预先定义的验收条件才报告完成,缺一项就说明缺哪一项。
这套做法不能保证每个平台都有相同的详情接口或公开页,却能避免一种常见误判:把工具调用的返回值,当成真实系统中已经完成的工作。