Skip to main content
Glama
README.md
# Astro Publisher MCP

通过 AI 客户端管理 Markdown 草稿,将 Astro 静态博客发布到自己的服务器。

这是我的个人工程项目:把“写文章 → 本地检查 → 构建 → 上传 → 切换版本 → 验证”封装成标准 MCP 工具。它不依赖网站后台,不在公网新增管理 API,也不需要在低配置服务器上安装 Node.js 或执行 Astro 构建。

**当前版本:0.1.0 MVP。** 支持本地 stdio MCP、Markdown、SSH/SFTP 和 Linux Nginx 静态站。代码不包含真实私钥或个人服务器配置。真实博客使用的四分类 schema 是当前内容适配器的约定,适配其他站点时需对应调整。

## 工作方式

```mermaid
flowchart LR
    A[MCP 客户端] --> B[本地 MCP 服务]
    B --> C[草稿与摘要校验]
    C --> D[隔离目录 Astro 构建]
    D --> E[SSH 指纹校验与 SFTP 上传]
    E --> F[远端 SHA-256 验证]
    F --> G[原子切换 current 链接]
    G --> H[HTTPS 验证文章]
    H -->|失败| I[回退上一版本]
```

## 已实现能力

| 工具               | 用途                                     |
| ------------------ | ---------------------------------------- |
| `site_status`      | 检查 SSH 连接及当前版本                  |
| `list_posts`       | 列出本地文章与摘要                       |
| `read_post`        | 读取文章或草稿(`draft: true`)          |
| `save_draft`       | 校验并保存 Markdown,不自动上线          |
| `check_draft`      | 在临时副本中运行 Astro 检查与构建        |
| `publish_post`     | 构建、持久保存本地文章、上传、切换并验证 |
| `list_releases`    | 查看远端历史版本                         |
| `rollback_release` | 按预期当前版本回退,防止覆盖别人的新发布 |

已有草稿更新需要 `expectedDigest`;已有正式文章更新需要 `expectedPublishedDigest`。摘要是读取结果中的 SHA-256,用来避免旧内容覆盖新修改。

发布使用本地跨进程锁;远端启用文件锁和版本比较,只有当前版本仍符合预期时才切换。历史版本不会自动删除。

## 快速开始

需要 Node.js 22+、已安装依赖的 Astro 站点、Linux 服务器上的 Python 3.10+、可用的 SSH 密钥和静态 Web 服务。

```bash
npm ci
npm run build
npm test
```

复制 `config.example.json` 为 `config.local.json`,填写自己的站点目录、域名、SSH 身份与发布目录。配置中的私钥是**本地文件路径**,不要粘贴私钥内容。服务器公钥指纹需要通过已可信的服务器控制台核对,例如在服务器上执行:

```bash
ssh-keygen -lf /etc/ssh/ssh_host_ed25519_key.pub -E sha256
```

`hostKeySha256` 填写输出中的 Base64 指纹,不含 `SHA256:` 前缀。实现会对 SSH 实际选择的主机密钥进行严格比对;若服务器提供多种主机密钥,应确认协商使用的那一把,不能通过关闭校验解决。

测试 MCP 进程:

```bash
node dist/server.js /absolute/path/to/config.local.json
```

正常启动后等待 MCP 客户端输入,不会打印普通欢迎信息到 stdout。通用客户端配置见 [examples/mcp.json](examples/mcp.json)。Codex 可用本地 stdio 注册命令(替换为自己的绝对路径):

```bash
codex mcp add astroPublisher -- node /absolute/path/to/astro-publisher-mcp/dist/server.js /absolute/path/to/config.local.json
```

参考 [官方 MCP 配置文档](https://developers.openai.com/codex/mcp)。长时间构建建议在客户端中将该 MCP 的工具调用超时设为至少 300 秒。

## 首次配置服务器

发布器不会自动改动 Nginx、创建账号或安装公钥。管理员需先创建初始版本并让 Nginx 使用 `current` 链接。下面用 `/var/www/astro-releases` 举例,实际目录必须与配置一致:

```bash
# 上传 remote/worker.py 和 remote/bootstrap.py 到服务器的同一目录
python3 bootstrap.py /var/www/astro-releases /path/to/current-static-site
```

将 Nginx 站点 `root` 设为 `/var/www/astro-releases/current`,保留原来的 TLS 证书、ACME 续期目录和其他路由;检查配置后重载。发布账号需要 release 根目录的读写权限与 SSH 命令、SFTP 能力。推荐使用专用发布账号;MCP 不需要 root 权限。

初始版本必须包含 `index.html`。bootstrap 会复制旧站,并生成清单与校验标记。回滚只接受已完成验证的版本,并重新验证每个文件摘要;未上传完成的目录不列为可回滚版本。站点如有 CDN,版本标记文件和 HTML 应按请求绕过缓存,否则验证可能触发回退。

## 使用示例

先调用 `save_draft`:

```json
{
  "slug": "understanding-mcp",
  "title": "理解 MCP 的工具调用边界",
  "description": "从一个本地博客发布工具理解 MCP。",
  "pubDate": "2026-11-11",
  "category": "agent",
  "tags": ["MCP", "Astro"],
  "body": "## 为什么做这个工具\n\n把重复的发布步骤变成可检查的工作流。"
}
```

取得返回的 `digest`,调用 `check_draft`;用户要求上线时再调用 `publish_post`:

```json
{ "slug": "understanding-mcp", "digest": "从工具返回中取得的64位摘要" }
```

分类为 `agent`、`interview`、`bug`、`english`;站点可将 `interview` 显示为“基础知识”。草稿位于站点的 `.publisher/drafts/`,发布日志为 `.publisher/history.jsonl`。应在站点自身的 `.gitignore` 中加入 `.publisher/`。

## 失败状态

| 结果                | 含义与处理                                                                               |
| ------------------- | ---------------------------------------------------------------------------------------- |
| `published`         | HTTPS 版本标记和目标文章字节摘要均通过                                                   |
| `not_published`     | 上传未完成、切换被拒绝,或验证失败后已回退;本地 Markdown 可能已保存,重试需读取最新摘要 |
| `unknown`           | 切换阶段失去明确结果,先查 `site_status`,不要盲目重试                                   |
| `recovery_required` | 验证与回退均失败,需要人工核对当前链接与历史版本                                         |

构建失败不会上传或修改正式文章。上传失败保留本地文章便于重试。失败上传产生的未激活目录可能留在服务器上,当前版本不自动清理。进程崩溃留下的本地锁需要确认相关进程已退出后手动移除。

## 安全与边界

- 只处理限制长度的 Markdown,不支持工具输入执行任意命令、MDX 导入、脚本或任意路径写入。
- slug、日期、分类严格校验;危险 HTML 标签、事件属性及协议会被拒绝,支持 details 等常用教学排版标签。
- SSH 目的地来自可信本地配置;工具参数不能切换服务器。私钥仅用于内存中的 SSH 握手,不写入日志。
- 本地 Astro 源码及其依赖属于**可信代码**,构建不是沙箱。不要对陌生仓库运行该服务。
- 临时构建重用站点现有 node_modules,调用前应在站点执行 `npm ci`;不在每篇文章发布时重新安装。站点的 .env 不复制到临时构建;当前版本需将公开构建配置放在可信源码中。
- 输出日志与读回的文章都属于数据,不能视为新的指令。MCP 工具标注不取代客户端权限控制。
- 回滚工具校验远端当前版本并切换已有目录;自动发布额外执行 HTTPS 验证。不能声称提供容器级隔离、完整审计系统或数据库事务。

## 验证与后续计划

本地 15 项测试覆盖协议握手、草稿冲突、路径与符号链接、发布锁、构建/上传失败、SSH 指纹和 HTTPS 验证。Linux Python 6 项测试覆盖清单损坏拒绝切换、版本切换及回退、旧版本冲突和路径边界。实际 Astro 站点已完成临时副本构建,并对既有页面完成真实 SFTP 发布、HTTPS 验证和回退,未向公网发布测试文章。详见 [验证记录](docs/verification.md)。

GitHub Actions 定义了 Windows/Linux Node 检查及 Linux 远端 worker 测试;CI 的实际运行状态以仓库 Actions 为准。

后续计划:更多 Astro schema 适配器、图片资源管理、版本清理策略与更完整的发布状态恢复。当前不支持远程 HTTP MCP、定时发文和文章删除。

## 项目结构

```text
src/content.ts       草稿、校验、摘要与锁
src/build.ts         临时构建与资源清理
src/remote.ts        SSH/SFTP、版本切换与 HTTPS 验证
src/publisher.ts     发布流程与结果日志
src/mcp.ts           MCP 工具注册
src/server.ts        stdio 启动入口
remote/worker.py     Linux 远端发布操作
tests/              Node / MCP 测试
remote/test_worker.py Linux 文件系统测试
```

MIT License.