一核四面:桌面应用如何同时服务 UI、CLI、HTTP 和 MCP
这篇文章解释为什么一个桌面应用值得同时提供 UI、CLI、HTTP、MCP 四个入口,以及它们如何共享同一内核来降低维护成本、稳定 AI agent 工作流并保留人工可见控制面。
先说结论:如果你的产品既要给人直接用,又要给脚本、现有系统和 AI agent 调用,最稳的做法通常不是做四套产品,而是做“一套内核,四个入口”。 对 OmniGoAI 的 OmniPost 来说,这四个入口就是桌面 UI、CLI、HTTP API 和 MCP。它们看起来像四种形态,本质上调用的却应是同一套发布能力、账号状态和结果记录。
很多团队一提“多入口”,第一反应是功能会不会越做越散。真正的问题其实不是入口多,而是入口背后是否还是同一内核。如果 UI 有一套逻辑、CLI 再抄一套、HTTP 又拼一套、MCP 再包一层不同实现,系统很快就会出现行为不一致:桌面上能发,CLI 发不了;HTTP 能查到状态,MCP 查不到;日志记录格式各不相同。真正可维护的做法,是让外层只负责“怎么调用”,内层只负责“真正做事”。
如果你正在设计一款 agent 友好的桌面工具,这篇文章会回答四个问题:为什么“一核四面”比“四套并行实现”更稳;UI、CLI、HTTP、MCP 分别解决什么问题;共享内核到底共享哪些东西;以及设计时最容易踩的坑是什么。
为什么桌面工具越来越需要不止一个入口
现在很多桌面工具已经不只是“人点按钮”的软件了。
它们往往同时面对四类调用者:
- 人类用户:需要一个看得见、能检查状态、能手动介入的界面;
- 本地脚本或计划任务:需要 CLI,方便批处理和定时运行;
- 已有业务系统:需要 HTTP API,方便和 CMS、队列、后台服务对接;
- AI agent:需要 MCP 或其它结构化工具接口,便于逐步调用、逐步决策。
如果只做其中一个入口,其它三类需求迟早会反过来敲门。也正因为这样,OmniGoAI 的 OmniPost 从一开始就不是只做“会点按钮的桌面应用”,而是把桌面能力继续暴露给 CLI、HTTP 和 MCP,让一篇内容能从人手操作一路延展到自动化流水线。
这和站内另一篇 任何 agent 接入 OmniPost 的三条路径 讲的是同一件事:上游可以变化,但发布层边界最好稳定。
“一核四面”到底指什么
“一核四面”不是 marketing 口号,它对应一个很具体的工程结构:
- 一核:真正负责业务规则、状态管理、校验、记录和执行的核心能力;
- 四面:UI、CLI、HTTP、MCP 四种面向不同调用者的入口。
判断你是不是在做“一核四面”,可以看一个简单标准:同一个动作换入口调用时,结果是否应该一致。
比如 OmniPost 里的“正式发布一篇文章到掘金”这件事,无论你是:
- 在桌面 UI 里点“发布”;
- 在 CLI 里执行
publish --mode publish; - 通过 HTTP
POST /api/publish; - 让 agent 通过 MCP 调
publish_post;
它们最终都应该走同一套:
- 读取账号状态;
- 校验平台能力;
- 检查 category、tags、summary 等必填字段;
- 执行发布;
- 记录结果、返回 stage 和文章链接。
如果这五步在不同入口里各自重写,那就不是“一核四面”,只是“四套看起来相似的实现”。
共享内核真正该共享什么
很多团队会说“我们也做了统一内核”,但实际上只共用了一个数据结构。真正有价值的共享,至少应该覆盖下面几层。
1. 业务规则必须共享
最该收进内核的,不是按钮样式,而是业务规则。
以多平台内容分发为例,真正应该统一的是:
- 哪个平台支持草稿,哪个支持自动正式发布;
- 正式发布缺哪些字段应该报错;
- 掘金为什么必须带分类、标签和摘要;
- 平台返回 NEED_LOGIN、VALIDATION_FAILED、MANUAL_PUBLISH 时怎么解释;
- 哪些结果算 published,哪些只是 draft。
这些规则不应该在 UI 提示里写一遍、CLI 解析里再写一遍、HTTP handler 再写一遍。规则必须只存在一处,否则迟早漂移。
2. 状态模型必须共享
“一核四面”的第二个关键,是状态不能分裂。
对于桌面工具来说,至少这些状态应该是共享的:
- 应用是否运行;
- 平台和账号是否已登录;
- 最近一次执行的结果;
- 某条内容当前是草稿、已发布、审核中还是失败;
- 日志、错误码和公开链接。
如果 UI 自己维护一份“本地状态”,CLI 临时现查一份,HTTP 又查数据库另一份,最后 agent 看见的世界和人类看见的世界就不会一样。内容工具最怕的不是失败,而是不同入口对同一事实说不同的话。
3. 执行动作必须共享
共享内核不是只共享判断逻辑,还要共享真正做事的执行路径。
例如“发布到知乎”这一步,正确做法应该是四个入口都调用同一执行函数;区别只在外层怎么把参数送进来、怎么把结果送出去。
这样做的直接好处是:
- 修一次 bug,四个入口一起受益;
- 新增一个错误码,不需要改四套分支逻辑;
- 发布结果和日志格式天然一致;
- 你更容易保证 preview、draft、publish 的行为不漂移。
4. 结果记录必须共享
这也是最容易被忽略的一层。很多团队会统一“执行”,却没有统一“记录”。
真正 agent 友好的桌面工具,应该保证无论入口是什么,结果都落到同一套记录里。至少要包括:
- 时间;
- 平台;
- 账号;
- 入参摘要;
- stage;
- error code;
- postUrl 或 editorUrl;
- 可供下次跳过重复发布的 recordId。
这就是为什么 OmniPost 的 CLI、HTTP、MCP 最后都应该回到同一套 posts 记录上:否则你没法在下一轮里正确判断“这篇到底发过没有”。
四个入口分别解决什么问题
共享内核,并不代表四个入口没有区别。恰恰相反,它们各自的职责应该非常明确。
UI:给人类一个可见控制面
桌面 UI 的核心价值,不是“也能点发布”,而是它给了人类一个可见、可检查、可介入的控制面。
它特别适合做这些事:
- 查看当前账号和登录态;
- 手动补分类、标签、摘要;
- 观察预览和发布结果;
- 在风控、验证码、人工确认场景下接管。
很多 agent 工具最后还是要回到桌面 UI,因为真正稳定的系统必须允许人在关键节点接手,而不是假设所有动作都能全自动完成。
CLI:给文件和脚本一个稳定入口
CLI 的价值在于它天然适合文件、shell 和批处理。
它最擅长的不是“替代 UI”,而是:
- 直接读取 Markdown 文件;
- 嵌进计划任务或本地脚本;
- 在 CI 前后做探活、发布和回查;
- 避免长正文在 JSON 和 shell 转义之间来回折腾。
如果你的内容先落盘,再经过 check、build、deploy,最后才发布,CLI 往往是最顺手的那一面。想进一步比较三种机器入口,也可以看 OmniPost 的 CLI、MCP、HTTP 三种接入方式怎么选。
HTTP:给系统与系统之间的对接面
HTTP 的作用不是“更高级”,而是更适合系统集成。
它适合这些情况:
- 你的内容来自 CMS 或后台服务;
- 多个上游系统要共用一套发布层;
- 你需要统一权限、审计或调度;
- 上游不方便直接碰本地文件和桌面进程。
HTTP 把桌面应用从“本地给人点的工具”扩展成“局域可编排的服务节点”。这对内容流水线、运营后台和自动化系统都很关键。
MCP:给 AI agent 一个结构化工具面
如果说 HTTP 是给系统看的,MCP 更像是给 agent 看的。
agent 的工作方式不是“拿到一条命令就结束”,而是:
- 先看状态;
- 再查账号;
- 根据返回结果补字段;
- preview;
- 决定草稿还是正式发布;
- 再读回结果继续后续动作。
这种逐步决策的节奏,最适合结构化工具接口,而不是大段命令输出。MCP 的价值,本质上是把 HTTP/CLI 背后的能力重新包装成 agent 更容易推理的工具形态。
为什么“一核四面”比“四套实现”更稳
真正的好处不是“听起来更优雅”,而是长期维护成本会低很多。
第一,行为一致性更容易守住
只要入口共用同一内核,就更容易保证这些判断一致:
- 哪个平台支持 publish:auto;
- 哪些字段是硬门槛;
- 哪个错误应提示重新登录;
- 哪些结果要记成 draft、published 或 reviewing。
这种一致性对 agent 特别重要,因为 agent 非常依赖工具返回的稳定语义。
第二,新增能力只需要补一处
如果你后来新增“发布状态回查”“账号健康检查”或“定时发布”,最理想的情况是:
- 先把能力加进内核;
- 再按需要暴露到 UI、CLI、HTTP、MCP。
这样新能力的核心逻辑只有一份,外层只是选择呈现形式。
第三,更容易做人工兜底
很多现实世界动作都不可能 100% 自动。
比如内容分发里常见的:
- 登录过期;
- 验证码或滑块;
- 平台风控;
- 需要人工确认分类或标题。
如果四个入口共享同一状态和记录,那么 agent 遇到问题时,人类就能直接在 UI 里看到同一条任务、同一份结果、同一条错误,而不是在不同通道之间来回对账。
设计“一核四面”时最容易踩的坑
说起来简单,真正做时最容易踩这几个坑。
坑一:只有传输层统一,没有业务层统一
很多项目会说“CLI 和 HTTP 都调用同一服务”,但 UI 仍然是另一套逻辑。这会导致人类看到的结果和自动化看到的结果不一致。
坑二:错误码没有统一语义
如果 UI 弹中文提示,CLI 打一串自由文本,HTTP 返回另一套字段名,MCP 再自己翻译一次,agent 很难稳定处理失败分支。
更好的做法是:内核先定义统一错误语义,外层再决定如何展示。
坑三:记录分散在不同入口
如果 UI 有自己的历史列表,CLI 输出不落库,HTTP 单独记日志,MCP 只返回给会话,不写统一记录,那么“已发布过则跳过”这种规则几乎不可能长期可靠。
坑四:把 MCP 当成一套新业务逻辑
MCP 不应该是“再做一套 agent 专用系统”,而应该是对既有内核的结构化暴露。否则你会同时维护“给人用的产品”和“给 agent 用的影子产品”。
哪些产品最适合一开始就按“一核四面”设计
这类结构尤其适合三种产品。
1. 本地优先、但又要自动化的桌面工具
比如 OmniPost 这种既要保护账号本地化,又要接自动发布流水线的工具。只做 UI 不够,只做 API 也不够。
2. 有明显人工兜底节点的 agent 工具
如果系统里注定会有“人最后确认一下”的步骤,那么 UI 和 agent 接口最好从一开始就共享内核,而不是后补。
3. 要长期演化能力边界的产品
今天你可能只需要 UI+CLI,明天就会有人要 HTTP,后天又会有人要求接 MCP。提前把核心能力和外层入口拆开,后面扩展会省很多力气。
常见问题
为什么不能只做 HTTP,再让 UI 和 MCP 都包它?
可以,但前提是 HTTP 后面那层仍然是统一内核,而不是把 HTTP handler 本身写成业务中心。真正重要的是业务逻辑不要长在入口层上。
MCP 和 HTTP 有什么本质区别?
HTTP 更适合系统对系统调用,MCP 更适合 agent 逐步调工具、逐步消费结果。两者背后最好还是同一套核心能力。
UI 在 agent 时代还重要吗?
很重要。只要系统里存在登录、风控、人工确认和结果复盘,UI 就仍然是必要的人类控制面,而不是历史包袱。
“一核四面”会不会把产品做得太重?
真正让产品变重的不是入口数量,而是每个入口都各做一遍。共享内核反而是在给多入口降复杂度。
如果你现在也在做一个既服务人、又服务脚本和 agent 的桌面工具,最值得先想清楚的通常不是“先上哪种协议”,而是“哪些规则、状态和记录必须只存在一处”。想看这种结构在内容分发产品里的落地形态,可以从 OmniPost 下载页开始:https://omnigoai.com/zh/download/omnipost/