Typecho MCP Workbench 开发手记 05:图床、文件床和 v1 之前的安全边界

前四篇基本把项目的骨架讲完了。

第一篇讲为什么做:不是再造一个 Typecho 后台,而是把远程博客变成一个本地可控、AI Agent 可操作、远端尽量轻量的发布目标。

第二篇讲怎么够到远端:SSH zero-touch,自动探测 Docker 里的 Typecho,部署一个很小的 PHP agent,用 stdin/stdout 做受控调用。

第三篇讲 AI 被允许怎样操作:MCP 工具不是裸数据库函数,而是一组有策略、有快照、有审计的本地工作流。

第四篇讲人类入口:localhost Web UI 只是原型,长期目标应该是一个类似 Obsidian 或 Typora 的本地桌面工作台。

这一篇想把素材和安全模型收一下:图片、附件、外部 URL、图床、文件床、对象存储、桌面打包和 v1 路线,最后都要回到同一个问题:AI 可以帮我把内容准备到什么程度,人类又必须在哪些地方保留边界。

本地工作台通过资产 provider 管理图片文件,并用策略保护发布流程

为什么第 5 篇要讲素材

博客文章不只有文字。

一篇真正要发布的文章,里面会有截图、架构图、流程图、下载文件、外部链接、以后可能还有视频或大型附件。它们不一定都适合放在 Typecho 服务器上。

Typecho 自己的上传目录很稳定,默认会把文件放到类似这样的路径:

usr/uploads/YYYYMM/

对于小图、普通截图、兼容性测试,这很好。第一版工作台也已经支持 typecho.media.upload:把文件上传到 Typecho 本地媒体目录,注册附件记录,再返回 Markdown。

但如果所有图片和文件都塞进博客服务器,就会遇到几个现实问题:

  • 博客服务器磁盘不一定大。
  • 大文件会拖慢备份和迁移。
  • 热门图片会吃掉站点带宽。
  • 有些用户本来就有图床、文件床、对象存储或 CDN 工作流。
  • AI Agent 插入素材时,需要知道这个链接来自哪里、是否可访问、是否应该被允许。

所以这里不能只做一个“上传按钮”。更合适的抽象,是 asset provider。

local media 和 external URL 是第一组边界

当前项目里已经有两种最基础的素材路径。

第一种是 typecho-local

它把文件上传到 Typecho 自己的上传目录,并注册成 Typecho 附件。优点是兼容、直观、不需要第三方配置。缺点也很明显:它消耗博客服务器存储和带宽,不适合大文件,也不适合把 Typecho 当成通用文件仓库。

第二种是 external-url

它不上传文件,只注册一个已经存在的外部 URL,然后把它作为图片或文件链接插入 Markdown。这个 URL 可以来自图床、文件床、CDN、OpenList/AList、R2/S3,也可以是用户手工上传到别处后复制过来的链接。

这两种能力看起来简单,但它们给后续 provider 系统定了一个重要形状:文章正文里插入的是 Markdown,工作台内部要保留 provider、URL、类型、来源和可检查状态。

未来无论资源来自哪里,返回结果都应该长得差不多:

{
  "provider": "external-url",
  "media": {
    "filename": "diagram.png",
    "url": "https://cdn.example.com/diagram.png",
    "mime": "image/png",
    "external": true,
    "kind": "image"
  },
  "markdown": "![diagram.png](https://cdn.example.com/diagram.png)"
}

这样 MCP Agent 和人类工作台不需要关心底层是 Typecho 上传、R2、S3、WebDAV,还是 OpenList。它们只需要知道:这是一个可插入文章的资产,它属于哪个 provider,它是否是外部链接,它应该怎样被审计和检查。

不同素材来源归一成同一种可插入文章的资产对象

asset provider 不是图床配置页

如果把这个功能做窄一点,它很容易变成一个普通的“图床配置页”:填 endpoint、填 token、点上传、复制 URL。

但在 Typecho MCP Workbench 里,asset provider 应该是发布工作流的一部分。

因为 AI Agent 会参与写作,它不只是帮你复制图片链接。它可能会做这些事:

  • 根据文章内容建议需要哪几张图。
  • 把本地截图上传到默认 provider。
  • 把已有 CDN 图片注册到文章资产列表。
  • 检查 Markdown 里所有图片链接是否可访问。
  • 在发布前报告哪些资源是 Typecho 本地媒体,哪些是外部 URL。
  • 发现某个外链图片返回 404 后提醒人类替换。

这时 provider 就不能只是“能上传就行”。它至少要回答几个问题:

  • 这个资源会不会公开?
  • 这个资源会不会消耗博客服务器空间?
  • 这个 URL 是否适合直接出现在公开文章里?
  • 凭据存在哪里?
  • AI 是否有权上传这个大小和类型的文件?
  • 失败后能不能从 operation ID、audit log 和 debug bundle 里查到发生了什么?

换句话说,asset provider 不是一个孤立功能,而是 Content、Review、Media、Operations 四个工作区之间的一条线。

凭据必须留在本地

素材系统里最不能含糊的是凭据。

如果未来支持 S3、Cloudflare R2、WebDAV、OpenList/AList,用户会配置 access key、secret、token、endpoint、bucket、public base URL 这些信息。

这里的原则应该很硬:

Provider credentials stay local.
Never store object storage secrets on the Typecho server.
Do not put secrets in article Markdown, audit body, or debug bundle.

远端 Typecho 不应该知道 R2 secret,也不应该代替工作台去上传对象。远端 PHP agent 的职责仍然要窄:处理 Typecho 文章、附件记录和必要的站点读写。素材上传、provider 凭据、URL 检查和策略判断,应该尽量留在本地工作台。

这和前面几篇的架构是一致的:远端轻,本地重;远端执行,本地控制;远端不新增公网 API,本地保留策略、快照、审计和诊断。

对于外部 URL,默认也不应该让 Typecho 服务器去代理下载。文章里插入的是公开 URL,发布前由本地工作台检查它是否可访问。这样既减少远端复杂度,也避免把博客服务器变成一个不受控的资源中转站。

Operation Policy 也要管素材

前几篇一直在讲 Operation Policy,主要围绕 publish、rollback、delete、plugin/theme/settings 这些高风险动作。

但素材也需要策略,只是风险等级不一样。

上传一张小截图,通常是 medium risk:它创建了远端对象,但一般可控、可替换、可删除。

注册一个外部 URL,也可以是 medium risk:它会让公开文章依赖一个站外资源,需要记录来源和可访问性。

删除媒体就更危险。因为一个看起来没用的附件,可能还被旧文章引用;一个外部 URL 如果被批量替换,也可能破坏历史文章。到了这一步,它就不再是“媒体管理”,而是会影响公开内容的写操作。

所以我更希望 Operation Policy 以后能覆盖这些动作:

assets.upload              medium
assets.register_url        medium
assets.rewrite_urls        high
media.delete               high / critical
provider.configure         high
provider.delete            high
provider.credential.write  critical

这不是为了给每个小动作都加弹窗,而是为了让系统能区分:哪些操作可以由 AI 准备,哪些操作必须人类确认,哪些操作需要先出报告,哪些操作永远不能静默执行。

素材上传、外链登记、删除媒体和凭据写入被纳入同一套 Operation Policy

发布前检查应该看见资产

prepare_publish 现在已经是这个项目里非常关键的动作。它不是发布按钮的一部分,而是 AI 和人类之间的一次安全握手。

前面的版本里,它已经会检查标题、slug、正文、分类、标签、状态、图片链接等信息。到了 asset provider 这一层,它应该继续变得更具体:

  • 文章引用了多少张图片。
  • 图片分别来自 Typecho local 还是 external URL。
  • 外部图片是否返回 200。
  • 是否存在 http 明文链接。
  • 是否存在 localhost、内网地址、临时签名 URL。
  • 是否存在空 alt 或明显不合适的 alt。
  • 是否存在过大的文件或不适合公开的下载链接。
  • 是否存在 provider 凭据泄漏到正文的迹象。

这些检查不一定都要一开始完成,但方向很明确:发布前,人类应该看到“这篇文章会把哪些素材公开出去”。

这也是工作台区别于普通 Markdown 编辑器的地方。普通编辑器只关心 Markdown 能不能渲染;Typecho MCP Workbench 还要关心这段 Markdown 一旦发布,会对远端博客、外部存储、读者浏览和未来回滚造成什么影响。

桌面打包让素材工作流更自然

第四篇说过,长期入口应该是 Tauri 桌面应用,而不是永久停在 localhost 页面。

素材系统会让这个判断更明显。

浏览器页面当然可以选择文件、上传图片、插入 Markdown。但真正顺手的写作工作台,应该能做更多本地事情:

  • 记住每个站点的默认 asset provider。
  • 用系统文件选择器挑选截图和附件。
  • 把本地草稿目录、图片目录和远端文章 cid 绑定起来。
  • 把 debug bundle 导出到用户选择的位置。
  • 用本地安全存储保存 provider 凭据引用。
  • 在菜单、命令面板和快捷键里提供插入图片、注册外链、检查资产这些动作。

这正是 Tauri 方向的价值:UI 可以继续复用现在的 Web renderer,但桌面壳逐步接管窗口状态、菜单、文件选择、凭据存储、日志目录、profile 和本地路径。

短期的 Node local-service 仍然很重要。它是 MCP、CLI 和当前 Web Workbench 的参考后端,也承载了已经跑通的 SSH、local-ops、policy、cache、snapshot、audit。项目不应该为了桌面化而大爆炸重写。

更稳的路线是:

Web Workbench renderer
  -> WorkbenchBackend adapter
  -> HTTP backend today
  -> Tauri invoke commands later
  -> same policy / snapshot / audit semantics

也就是说,桌面打包不是换一个壳就完事。它真正要完成的是:让人类入口变成长期可用的本地应用,同时不绕过已有的安全核心。

Tauri 桌面路线保留同一个工作台渲染层和安全核心

v1 之前,先把边界说清楚

这个项目已经能做很多事情:连接远端 Typecho,读文章,创建草稿,更新文章,上传媒体,注册外链,准备发布报告,显式确认后发布,写快照,回滚,记录审计,导出 debug bundle,还有第一版桌面壳方向。

但越是接近可发布版本,越不能把话说满。

当前真实链路优先跑通的是 SQLite Typecho。远端 agent 可以检测数据库类型,但不能因此宣称所有数据库都完整支持。MySQL/MariaDB 应该先做只读兼容和能力报告,再谨慎进入写支持。

当前核心写路径集中在 posts 和 media。Pages、taxonomy、comments、plugins、themes、settings、users、permalinks 都可以逐步进入工作台,但应该先 read-only inventory,再决定哪些操作值得开放写入。

当前 publish 默认需要人工确认。未来可以有 trusted agent mode,甚至 autopublish,但它们必须建立在更强的 readiness checks、客户端身份、限额、审计、回滚和策略配置之上,而不是把 confirm=true 偷偷藏起来。

所以 v1 之前的路线,我更愿意写成一组清晰的门槛:

1. SQLite-first release candidate
2. reliable SSH zero-touch setup
3. posts/media core workflow stable
4. publish and rollback review/confirm stable
5. asset provider baseline: local media + external URL
6. Operation Policy visible across Web / CLI / MCP
7. Debug Bundle and typed errors useful enough for real troubleshooting
8. Tauri desktop alpha can open the workbench and preserve the same safety model
9. database compatibility boundary stated honestly
10. broader Typecho operations stay read-only until policy/preview/audit/backup are ready

这听起来比“马上 v1”慢一点,但我喜欢这种慢。一个 AI 可以操作的发布工具,最危险的不是功能不够多,而是功能看起来够多、边界却没有讲清楚。

这个系列真正写下来的东西

写到第 5 篇,我发现这个系列其实不是在记录“我做了一个 Typecho 客户端”。

它记录的是一个 AI 时代的小型软件应该怎样长出来:

  • 先把远端系统变成可控工具目标。
  • 再给 AI 一组语义明确的 MCP 工具。
  • 再把危险动作收进策略、快照、审计和确认。
  • 再给人类一个能长期使用的本地工作台。
  • 最后再让素材、外部存储、桌面打包和 v1 路线全部服从同一条安全边界。

这条路的重点不是让 AI 替我发博客。

恰恰相反,是让 AI 能做越来越多准备工作时,我仍然知道它做了什么、要改什么、会发布什么、出了问题到哪里查、需要回滚时从哪里恢复。

这也是 Typecho MCP Workbench 到目前为止最核心的产品判断:

AI 负责加速。
本地工作台负责秩序。
人类负责边界。

Typecho MCP Workbench v1 之前需要完成的可靠性和安全边界门槛

下一步

五篇连载写完以后,项目就可以从“开发手记”进入更公开的 README、截图、release checklist 和桌面 alpha 打磨。

后面如果继续写,我可能会把它拆成两个方向:

  • 一个方向写产品化:Tauri 桌面 alpha、安装包、站点 profile、默认 provider、发布报告和 debug bundle。
  • 另一个方向写更硬的工程边界:数据库兼容、Typecho Hook、comments/plugins/settings 的只读盘点、trusted agent mode 和 autopublish policy。

如果说前五篇是在搭骨架,那后面就该开始回答一个更具体的问题了:这个工具怎样从“我自己的博客能用”,变成“别人也敢拿来接自己的 Typecho”。