内容
# magnolia-author-mcp
[](./LICENSE)
[](https://github.com/noicework/magnolia-author-mcp/actions/workflows/ci.yml)
[](https://www.python.org/downloads/)
[](https://modelcontextprotocol.io)
**一个 [MCP](https://modelcontextprotocol.io) 服务器,允许 AI 代理在 [Magnolia CMS](https://www.magnolia-cms.com/) 上编写页面 - 运行时发现模板、组件和对话框字段,然后创建、更新、验证和发布真实内容。**
将 Claude Desktop、Claude Code、Cursor 或任何 MCP 客户端指向您的 Magnolia 作者实例,并使用自然语言驱动内容创作: *"使用英雄横幅和三卡网格创建活动登陆页面,然后发布到 UAT。"* 服务器将其转换为针对 Magnolia REST API 的正确、类型化的 JCR 编写 - 内置发现、验证和防护措施。
它封装了 Magnolia 的 Nodes API (`/.rest/nodes/v1`)、Definitions API (`/.rest/registry-definitions`) 和模板注册表 (`/.rest/template-definitions/v1`)。通用 - 没有关于特定组件库、页面模板或站点约定的假设;一切都从实时实例中发现。多实例 - 一个服务器可以通过 `instances.json` 与多个项目中的 dev/UAT/prod 层进行通信。
> 由 [Noice](https://noice.net.au) 构建和维护,Noice 是一家 Magnolia 白金合作伙伴。参见 [项目页面](https://noice.net.au/products/magnolia-author-mcp)。
---
## 为什么存在
在无头/混合 CMS 中以编程方式创作内容比看起来更难:无法看到组件库的代理将编写渲染器忽略的属性,发明 404 的 DAM 路径,将日期存储为破坏排序的字符串,或破坏可切换字段并写入错误的子节点形状。成功的 JCR 编写仍然可以渲染损坏的页面。
`magnolia-author-mcp` 弥补了这一差距:
- 它 **发现模板、组件和对话框的真实形状**,从运行实例中获取,因此代理根据真实情况创作,而不是猜测。
- 它 **正确序列化结构化字段** (可切换/复合/多字段/类型化 JCR 值)并告诉您它做了什么。
- 它 **验证渲染的 HTML**,而不仅仅是 JCR 编写。
- 它 **保护破坏性和生产操作**,具有干预运行、快照和审计日志,以及显式生产确认。
## 亮点
- **运行时发现,无需访问源代码。** 页面模板、组件定义(区域 + 允许的子组件)和对话框字段 **及其允许的值** 均来自实时实例。`scan_components` 爬取站点并按可创作性分类每个正在使用的组件;`learn_component_shape` 从实时用法中读取组件的实际磁盘形状。
- **结构化字段是纯 JSON。** 可切换字段/复合字段值是字典,jcrMultiField 值是字典列表,JCR 类型值使用显式 `$type` 形式 - 服务器序列化每个字段为 Magnolia 期望的子节点布局,并通过 `notices` 报告它做了什么。
- **批量写入。** `build_page` 在一次调用中创建页面并填充嵌套的组件树(网格 → 列 → 卡片);`add_components_bulk` / `set_properties_bulk` 将 N 次往返减少到一次。
- **真实验证。** `verify_render` 获取渲染的 `.html` 并报告空 `<img>`、损坏的 `/dam/` 引用、过小的正文和缺少 `mgnl:template` 的页面 - JCR 清洁写入不保证渲染清洁。
- **防护措施。** 干预删除具有预删除快照和 `restore_snapshot`,每个变异调用的审计日志,以及对 `is_prod` 实例的显式确认。
## 工作原理
```
┌──────────────┐ MCP(标准输入输出) ┌───────────────────────┐ HTTPS + 基本身份验证 ┌──────────────────┐
│ MCP 客户端 │ ───────────────▶│ magnolia-author-mcp │ ──────────────────▶│ Magnolia 作者 │
│ (Claude 等)│ ◀─────────────── │ (此服务器) │ ◀────────────────── │ 实例(s) │
└──────────────┘ 工具调用 └───────────────────────┘ 节点/定义 API └──────────────────┘
│
instances.json(注册表)
凭证:环境变量 / 1Password / 字面值
```
服务器是一个单一的 Python 文件。它通过标准 MCP 标准输入输出传输暴露了约 45 种工具。每个工具从 `instances.json`(每次调用重新读取,以便凭证编辑立即生效)解析其目标实例,使用 HTTP 基本身份验证进行身份验证,并与 Magnolia 的 REST 端点进行通信。
## 需求
- **Python 3.10+**
- 一个可访问的 **Magnolia 作者实例** (6.x),启用 REST **Nodes API**。 **Definitions API** 是可选的,但强烈建议使用 - 它支持模板/组件/对话框发现。没有它,发现工具会优雅地降级(服务器会通知您)。
- 一个具有适当 REST 角色的 Magnolia 用户(用于发现的读取角色;用于写入和激活的编辑/发布角色)。
## 快速入门
### 1. 安装
```bash
git clone https://github.com/noicework/magnolia-author-mcp.git
cd magnolia-author-mcp
python3 -m venv mcp/.venv
mcp/.venv/bin/pip install -r mcp/requirements.txt
```
### 2. 配置实例
```bash
cp instances.example.json instances.json
$EDITOR instances.json # 设置 base_url + 凭证源
```
`instances.json` 是 **gitignored** - 它保存您的基本 URL 和(可选)凭证。参见下方 [实例和凭证](#实例和凭证) 和 [`references/instances.md`](references/instances.md) 以获取完整模式。
### 3. 将服务器注册到 MCP 客户端
服务器使用标准输入输出,因此任何 MCP 客户端都可以使用。 使用 **绝对路径**。
**Claude Desktop** - 添加到 `claude_desktop_config.json`(macOS 上的 `~/Library/Application Support/Claude/`,Windows 上的 `%APPDATA%\Claude\`):
```json
{
"mcpServers": {
"magnolia-author": {
"command": "/abs/path/to/magnolia-author-mcp/mcp/.venv/bin/python",
"args": ["/abs/path/to/magnolia-author-mcp/mcp/magnolia_author_mcp.py"],
"env": {
"MAGNOLIA_AUTHOR_INSTANCES": "/abs/path/to/magnolia-author-mcp/instances.json",
"MGNL_LOCAL_USER": "superuser",
"MGNL_LOCAL_PASS": "superuser"
}
}
}
}
```
**Claude Code** - 从存储库根目录:
```bash
claude mcp add magnolia-author \
-e MAGNOLIA_AUTHOR_INSTANCES="$PWD/instances.json" \
-- "$PWD/mcp/.venv/bin/python" "$PWD/mcp/magnolia_author_mcp.py"
```
**Cursor** - 将相同的 `mcpServers` 块添加到 `.cursor/mcp.json`。
### 4. 试用
重启客户端并询问: *"使用 magnolia-author:测试与 `local-dev` 的连接,然后列出页面模板。"* 代理应该调用 `test_connection` 和 `list_templates`。
## 实例和凭证
`instances.json` 将实例键映射到连接 + 策略设置:
```jsonc
{
"instances": {
"local-dev": {
"label": "本地 Magnolia 作者",
"base_url": "http://localhost:8090", // 没有尾部斜杠
"site_root": null, // 或 "/mysite"
"is_prod": false, // true => 写入需要确认
"username_env": "MGNL_LOCAL_USER",
"password_env": "MGNL_LOCAL_PASS"
}
},
"aliases": { "local": "local-dev" }
}
```
实例的 **凭证解析** (按顺序):
1. **`username_env` / `password_env` 指定的环境变量**。
2. **1Password** 通过 `op_user_ref` / `op_pass_ref` (需要 `op` CLI 和 `OP_SERVICE_ACCOUNT_TOKEN`)。
3. **字面值** `username` / `password` 在条目中 - **开发环境专用,绝不提交**。
`instances.json` 在每次调用时重新读取,并且 auth/session 缓存会在其修改时间更改时失效,因此凭证编辑立即生效。(代码更改需要重启。)凭证永远不会被记录 - `test_connection` 仅报告 **哪个源** 解析,而不报告秘密。
当身份验证失败时,`test_connection` 返回 `auth_detail`:401 带基本质询意味着拒绝凭证;403 表示已身份验证但缺少 REST 角色;**没有** 质询的 401 通常意味着网关/SSO 层在 Magnolia 看到它之前拦截身份验证(Magnolia Cloud 作者常见)。
## 工具目录
**发现/内省**
| 工具 | 它回答的问题 |
|---|---|
| `list_instances` | 配置了哪些作者 + 别名 |
| `test_connection` | REST / 节点 API / 定义 API 是否启动;401/403 上的身份验证诊断 |
| `list_workspaces` | 可达的 JCR 工作空间 |
| `list_templates` | 页面模板 ID,可选作用于某个站点根目录 |
| `get_template` | 页面模板的区域 + 允许的组件(注册表备用 Dangling 引用) |
| `get_component_definition` | 组件的区域、`max_components`、允许的子组件和形状对话框字段 |
| `get_dialog` / `get_dialogs` | 对话框字段模式;选择字段包括 `options`,可切换字段包括子字段 |
| `list_components_for_area` | 一个区域的过滤视图 |
| `scan_components` | 爬取站点以获取实际组件库存 + 使用计数,按安全/字典作者/危险分类 |
| `learn_component_shape` | 合并实时用法到组件的实际磁盘形状 |
**读取**
| 工具 | 它执行的操作 |
|---|---|
| `read_node` | 读取 JCR 节点(`properties_only` / `structure_only` 瘦模式,`depth`) |
| `list_pages` | 路径下的页面(`verbose` 添加发布状态) |
| `browse_workspace` / `search_workspace` | 导航/搜索任意工作空间(如果可用,则索引查询,BFS 回退) |
| `query_jcr` | 在端点存在时运行 JCR 查询 |
| `get_publication_status` | 已发布 / 修改 / 未发布 |
| `list_dam_folder` / `search_dam` / `get_asset` | 查找 DAM 资产 + UUID |
| `download_dam_asset` | 将 DAM 二进制文件获取到本地缓存中(用于视觉工具) |
| `verify_render` | 获取渲染的 `.html` 并进行身份验证;报告空 `<img>`、损坏的 `/dam/` 引用、薄主体 |
**写入**
| 工具 | 它执行的操作 |
|---|---|
| `create_page` | 从模板创建页面 |
| `build_page` | 单次调用中的页面 + 嵌套组件树 |
| `add_component` / `add_components_bulk` | 添加组件(s_bulk);结构化字典值自动序列化 |
| `insert_component` | 位置插入(尾部拼接) |
| `reorder_components` | 在区域内重新排序 |
| `add_child_node` | 创建任意 JCR 节点 |
| `set_properties` / `set_properties_bulk` | 写入属性(相同的结构化值支持) |
| `set_component_image` / `set_page_image` | 将 DAM 图像(+ alt)连接到组件 / 页面英雄 |
| `copy_node` / `move_node` | 深度克隆 / 重新定位(移动默认干预运行,快照) |
| `upload_asset` / `upload_asset_from_url` / `make_dam_image_ref` | 添加 DAM 资产 / 构建图像引用 |
| `delete_node` | 删除(默认干预运行;确认删除快照) |
| `restore_snapshot` | 撤销删除 / 移动 / 拼接快照 |
| `publish_page` / `unpublish_page` | 激活 / 停用(命令路由自动发现) |
每个工具的签名位于 [`mcp/README.md`](mcp/README.md)。
## 作者约定(特殊属性值)
这些值形式在每个属性写入工具中都有效(`create_page`、`build_page`、`add_component(s_bulk)`、`add_child_node`、`set_properties(_bulk)`):
**DAM 图像引用** - `damLinkFields` 需要 `jcr:<UUID>`;裸 UUID 自动前缀(带有通知)。`image_alt` 是一个单独的文本字段;`set_component_image` 原子写入两者。
```json
{"image": "jcr:5ca6e45d-3c4b-4b3d-8f3b-f2488e939391", "image_alt": "People collaborating"}
```
**可切换字段 / 复合字段** - 传递字典;它存储为 Magnolia 期望的子节点。可切换字典需要 `field` 设置为选项名称(通过 `get_component_definition` / `get_dialog` 发现选项 + 子字段):
```json
{"iconLibrary": {"field": "enabled", "icon": "heart"},
"animation": {"field": "enabled", "animationStyle": "fade-up", "animationTiming": "stagger"}}
```
**jcrMultiField** - 传递字典列表;它存储为名为的属性容器 `<prop>0`、`<prop>1`、… 重置属性会替换整个容器(过时项目已删除):
```json
{"images": [
{"image": "jcr:<uuid-1>", "image_alt": "one", "caption": "First"},
{"image": "jcr:<uuid-2>", "image_alt": "two", "caption": "Second"}
]}
```
**类型化 JCR 属性** - `{"$type": ..., "value": ...}` 字典写入真正的类型化属性(`Date | Long | Double | Boolean | String`)。使用此内容应用工作空间的模型声明类型字段(例如,事件工作空间 `date`)- 字符串看起来正确,但在 AdminCentral 中会破坏日期排序和过滤:
```json
{"date": {"$type": "Date", "value": "2026-07-15T09:30:00+10:00"}}
```
ISO-8600 输入为您规范化:Magnolia 的节点 API 仅解析 `yyyy-MM-dd` 和 `yyyy-MM-ddTHH:mm:ss`(读取为 UTC)并且 **静默替换** 任何其他内容为当前时间戳 - 偏移感知值转换为 UTC 并剥离毫秒,因此实例存活。
**标量列表** 保留普通的多值属性。
## 推荐的工作流
1. `test_connection(instance)` 每个不熟悉的实例。
2. **发现:`list_templates` → `get_template` 用于页面模板的区域 → `get_component_definition` 每个容器您将嵌套到 → `scan_components` / `learn_component_shape` 进入不熟悉的组件库。
3. **作者:`build_page` 用于新页面,`add_components_bulk` 用于添加(条目按顺序处理,因此早期创建的网格可以是 `parent_path` 后续条目的父路径)。从规范克隆现有页面时使用 `copy_node`。
4. **验证:`verify_render(page_path)` - 检查 `ok`、`warnings`、`empty_img_tags`、`broken_dam_refs`。干净的 JCR 读取 **不** 保证干净的渲染。
5. **发布:`publish_page` 仅在实例的 REST 命令白名单允许的情况下进行;人类在 AdminCentral 中发布。## 安全模型
* `delete_node`。 Confirmed deletes / moves / splices snapshot the subtree to `snapshots/` first; every mutating call is appended to `audit.jsonl`. Both paths configurable via `MAGNOLIA_AUTHOR_SNAPSHOT_DIR` / `MAGNOLIA_AUTHOR_AUDIT_LOG`.
* 生产实例(`is_prod: true`)需要显式用户确认后才能进行任何写入操作。
* 如果您的 MCP 客户端支持每个工具的门控,请在深度防御中排除 `delete_node` / `publish_page`。
## 环境变量
| 变量 | 默认值 | 用途 |
|---|---|---|
| `MAGNOLIA_AUTHOR_INSTANCES` | `../instances.json` | 实例注册表路径 |
| `MAGNOLIA_AUTHOR_CACHE_DIR` | `~/.cache/magnolia-author-mcp` | DAM 下载缓存 + 允许本地读取根目录 |
| `MAGNOLIA_AUTHOR_TIMEOUT` | `30` | 每请求 HTTP 超时(秒) |
| `MAGNOLIA_AUTHOR_DEF_CACHE_TTL` | `1800` | 定义缓存 TTL(秒) |
| `MAGNOLIA_AUTHOR_SNAPSHOT_DIR` | `./snapshots` | 预删除/移动子树快照 |
| `MAGNOLIA_AUTHOR_AUDIT_LOG` | `./audit.jsonl` | JSONL 审计日志变异调用 |
| `OP_SERVICE_ACCOUNT_TOKEN` | — | 启用 1Password 凭证引用 |
| 实例特定的 `_env` 名称 | — | 您选择的用户名/密码环境变量 |
## 使用代理技能
[`SKILL.md`](SKILL.md) 是一个可移植的 [agent skill](https://modelcontextprotocol.io),教导模型 **如何** 使用这些工具做得好 - 发现前写入,DAM 处理,生产门控,回复语气 - 没有内置任何站点特定信息。支持技能的客户端可以直接加载它。站点特定的一切(实际组件库存,页面模板 ID,英雄组件,约定)都存在于实例指南中,位于 `references/<instance-key>.md`,从 `scan_components` 和实时读取的几次调用开始。
## 已知限制
* **定义 API 丢失**。对话框端点将 `jcrMultiField` / 复合条目从其 `properties` 映射中删除,JSON 模式模板端点在 Dangling 组件引用时返回 404。服务器进行补偿(带有提示的注册表备用;结构化值免于未知属性警告),但多字段名称无法针对对话框进行验证。
* **排序是一种拼接,而不是移动**。JCR `orderBefore` 未通过 REST 公开,因此 `insert_component` / `reorder_components` 会重新创建尾部兄弟节点(新 UUID)。响应包括恢复的快照文件。
* **渲染规则对内省不可见**。对话框可以声明一个字段,只有 FTL 才会在某些变体中渲染。 `verify_render` + 读取 HTML 是基本事实。
* **激活需要权限**。 `publish_page` 可以返回成功,而用户缺乏激活者角色 - 请检查 Magnolia 的激活监视器。
* `query_jcr` 需要可选节点 `@query` 端点;如果它不存在,则工具会降级为结构化错误,并使用 BFS 搜索。
## 开发
```bash
python3 -m venv mcp/.venv
mcp/.venv/bin/pip install -r mcp/requirements-dev.txt
mcp/.venv/bin/python -m pytest tests/test_unit.py -q # 离线,模拟 HTTP
mcp/.venv/bin/python tests/live_smoke.py # 端到端与本地作者
```
单元测试套件完全模拟,可以在任何地方运行。实时烟雾测试需要一个在 `instances.json` 的 `local-dev` 下配置的正在运行的作者;它会创建/清理自己的 `mcp-e2e-*` 页面。可以通过 `SMOKE_*` 环境变量覆盖其目标站点根目录和模板 ID(请参阅 [`tests/live_smoke.py`](tests/live_smoke.py))。
使用 MCP 开发运行器交互式检查服务器:
```bash
cd mcp && MAGNOLIA_AUTHOR_INSTANCES=$PWD/../instances.json .venv/bin/mcp dev magnolia_author_mcp.py
```
## 仓库布局
```
.
├── SKILL.md # 便携式代理技能(使用规则)
├── instances.example.json # 注册表模式 — 复制到 instances.json
├── mcp/
│ ├── magnolia_author_mcp.py # MCP 服务器(单个文件)
│ ├── requirements.txt # mcp[cli],requests
│ ├── requirements-dev.txt # + pytest
│ └── README.md # 每工具签名 + 注释
├── tests/
│ ├── test_unit.py # 离线,模拟 HTTP 单元测试
│ └── live_smoke.py # 端到端与本地作者
└── references/ # 技能参考文档,按需加载
├── components.md imagery.md instances.md moving-pages.md
├── project-document-layout.md troubleshooting.md
└── _instance-guide-template.md
```
## 贡献
欢迎提出问题和 PR。请参阅 [CONTRIBUTING.md](CONTRIBUTING.md) 以了解设置、测试工作流程和 PR 期望。请查看我们的 [行为准则](CODE_OF_CONDUCT.md)。
## 安全
发现漏洞?请按照 [SECURITY.md](SECURITY.md) 中的流程进行 - 不要为安全报告打开公共问题。
## 许可证
Apache License 2.0 — 请参阅 [LICENSE](LICENSE) 和 [NOTICE](NOTICE)。
由 [**Noice**](https://noice.net.au) 构建和维护 — 一家专门从事 CMS 迁移和 AI 辅助内容运营的 Magnolia 白金合作伙伴。
> *Magnolia* 是 Magnolia International Ltd. 的商标。本项目是独立的、社区维护的,不隶属于或得到 Magnolia International Ltd. 的认可或支持。
MCP 配置
以下是此 MCP Server 的配置信息,可直接复制到 Cursor 或其他 MCP 客户端中使用。
mcp.json
连接信息
猜你喜欢
暂无推荐服务器