EmDash 在 /_emdash/api/mcp 提供内置 Model Context Protocol(MCP)服务器。MCP 客户端通过它读取和管理内容、署名、架构、媒体、分类法、菜单、修订和设置,并导出或导入整个站点。
认证
MCP 端点需要 Bearer 令牌。EmDash 支持以下令牌流程:
| 方式 | 用途 |
|---|---|
| OAuth 2.1 Authorization Code with Proof Key for Code Exchange (PKCE) | 交互式 MCP 客户端。用户在浏览器中批准请求的 scope。 |
| Personal access token | 面向客户端或自动化的长期访问。令牌使用 ec_pat_ 前缀,在管理后台创建。 |
| OAuth 2.0 Device Authorization Grant | 命令行客户端,要求用户在浏览器中批准代码。emdash login 使用此流程。 |
会话 Cookie 不能用于认证 MCP 端点。
Scopes
OAuth 和个人访问令牌限制客户端可调用的工具。用户角色单独检查,因此 scope 不会授予用户没有的权限。
| Scope | 访问 |
|---|---|
content:read | 读取和搜索内容、署名、分类法、术语、菜单和修订。类草稿内容还需要用户的 content:read_drafts 权限。 |
content:write | 创建和修改内容、署名和修订。为兼容现有令牌,还授予 taxonomies:manage 和 menus:manage。 |
media:read | 读取媒体记录。 |
media:write | 上传、注册、更新和删除媒体。 |
schema:read | 读取集合和字段。 |
schema:write | 创建、更新和删除集合和字段。 |
taxonomies:manage | 创建、更新和删除分类法定义和术语。 |
menus:manage | 创建、更新和删除菜单和菜单项。 |
settings:read | 读取站点设置。 |
settings:manage | 更新站点设置。 |
mcp:tools | 调用任何已启用插件暴露的 MCP 工具。 |
mcp:tools:<pluginId> | 调用某一已启用插件暴露的 MCP 工具。 |
transfer:export | 将整个站点导出为站点包并下载。 |
transfer:analyze | 上传站点包并分析以准备导入。 |
transfer:execute | 启动、推进、取消和放弃站点导入。 |
admin | 调用所有核心工具,包括站点转移工具。插件工具仍需要 mcp:tools 或插件专用 scope。 |
admin scope 包含 transfer:export、transfer:analyze 和 transfer:execute。每个转移 scope 仅授予自身操作,且需要管理员角色。若希望客户端(如智能体)在不导出或导入的情况下分析站点包,应授予 transfer:analyze 而非 admin。
授权码同意页允许用户移除请求的 scope。EmDash 还会将请求与客户端注册的 scope 和用户角色取交集,并拒绝空授权。
角色要求
下表列出各类能力的最低角色要求。当用户操作他人内容时,所有权检查可能要求更高角色。
| 能力 | 最低角色 |
|---|---|
| 读取已发布内容、媒体、分类法、术语和菜单 | Subscriber |
| 读取草稿、定时内容、回收站、对比和修订 | Contributor |
| 创建内容或上传媒体 | Contributor |
| 编辑或发布自己的内容并注册媒体 | Author |
| 管理署名、分类法、菜单或所有用户的内容 | Editor |
| 读取架构或设置 | Editor |
| 修改架构或设置、永久删除内容或修复媒体使用 | Admin |
| 导出或导入整个站点 | Admin |
完整角色定义见用户角色。
传输
服务器使用无状态 Streamable HTTP。每个请求独立;服务器不保持 MCP 会话或 Server-Sent Events 连接。
| 方法 | 端点 | 行为 |
|---|---|---|
POST | /_emdash/api/mcp | 接受 JSON-RPC 初始化、工具列表和工具调用。 |
GET | /_emdash/api/mcp | 返回 405 Method Not Allowed。 |
DELETE | /_emdash/api/mcp | 返回 405 Method Not Allowed。 |
响应使用 JSON-RPC 2.0。构造工具请求前请调用 tools/list 获取当前输入 schema 和 MCP 注解。
工具清单
以下清单与 tools/list 返回的静态工具一致。包含注册标题,因为客户端可能显示标题而非工具名。
内容工具
| Tool | Registered title | Required scope |
|---|---|---|
content_list | List Content | content:read |
content_get | Get Content | content:read |
content_create | Create Content | content:write |
content_update | Update Content | content:write |
content_delete | Delete Content (Trash) | content:write |
content_restore | Restore Content | content:write |
content_permanent_delete | Permanently Delete Content | content:write |
content_publish | Publish Content | content:write |
content_unpublish | Unpublish Content | content:write |
content_schedule | Schedule Content | content:write |
content_unschedule | Cancel Scheduled Publication | content:write |
content_compare | Compare Live vs Draft | content:read |
content_discard_draft | Discard Draft | content:write |
content_list_trashed | List Trashed Content | content:read |
content_duplicate | Duplicate Content | content:write |
content_translations | Get Content Translations | content:read |
署名工具
| Tool | Registered title | Required scope |
|---|---|---|
byline_list | List Bylines | content:read |
byline_get | Get Byline | content:read |
byline_create | Create Byline | content:write |
byline_update | Update Byline | content:write |
byline_delete | Delete Byline | content:write |
byline_translations | List Byline Translations | content:read |
架构工具
| Tool | Registered title | Required scope |
|---|---|---|
schema_list_collections | List Collections | schema:read |
schema_get_collection | Get Collection Schema | schema:read |
schema_list_block_types | List Block Types | schema:read |
schema_get_block_type | Get Block Type | schema:read |
schema_create_block_type | Create Block Type | schema:write |
schema_update_block_type | Update Block Type | schema:write |
schema_activate_block_type_version | Activate Block Type Version | schema:write |
schema_create_collection | Create Collection | schema:write |
schema_delete_collection | Delete Collection | schema:write |
schema_update_collection | Update Collection | schema:write |
schema_create_field | Add Field to Collection | schema:write |
schema_delete_field | Remove Field from Collection | schema:write |
schema_update_field | Update Field | schema:write |
媒体工具
| Tool | Registered title | Required scope |
|---|---|---|
media_list | List Media | media:read |
media_create | Confirm Signed Media Upload | media:write |
media_upload | Upload Media | media:write |
media_get | Get Media Item | media:read |
media_update | Update Media Metadata | media:write |
media_delete | Delete Media | media:write |
media_usage_repair | Repair Media Usage Index | admin |
搜索工具
| Tool | Registered title | Required scope |
|---|---|---|
search | Search Content | content:read |
分类法工具
| Tool | Registered title | Required scope |
|---|---|---|
taxonomy_list | List Taxonomies | content:read |
taxonomy_get | Get Taxonomy Definition | content:read |
taxonomy_create | Create Taxonomy Definition | taxonomies:manage |
taxonomy_update | Update Taxonomy Definition | taxonomies:manage |
taxonomy_delete | Delete Taxonomy Definition | taxonomies:manage |
taxonomy_list_terms | List Taxonomy Terms | content:read |
taxonomy_create_term | Create Taxonomy Term | taxonomies:manage |
taxonomy_update_term | Update Taxonomy Term | taxonomies:manage |
taxonomy_delete_term | Delete Taxonomy Term | taxonomies:manage |
taxonomy_term_translations | List Term Translations | content:read |
菜单工具
| Tool | Registered title | Required scope |
|---|---|---|
menu_list | List Menus | content:read |
menu_get | Get Menu with Items | content:read |
menu_translations | List Menu Translations | content:read |
menu_create | Create Menu | menus:manage |
menu_update | Update Menu | menus:manage |
menu_delete | Delete Menu | menus:manage |
menu_set_items | Set Menu Items | menus:manage |
修订工具
| Tool | Registered title | Required scope |
|---|---|---|
revision_list | List Revisions | content:read |
revision_restore | Restore Revision | content:write |
设置工具
| Tool | Registered title | Required scope |
|---|---|---|
settings_get | Get Site Settings | settings:read |
settings_update | Update Site Settings | settings:manage |
站点转移工具
transfer:* 表示 transfer:export、transfer:analyze 或 transfer:execute 中的任意一个。admin scope 满足本表所有要求。
| Tool | Registered title | Required scope |
|---|---|---|
site_transfer_capabilities | Get Site Transfer Capabilities | transfer:* |
site_export_start | Start Site Export | transfer:export |
site_export_status | Get Site Export Status | transfer:export |
site_import_analyze | Analyze Site Import | transfer:analyze |
site_import_start | Start Site Import | transfer:execute |
site_import_status | Get Site Import Status | transfer:* |
site_import_resume | Resume Site Import | transfer:execute |
site_import_receipt | Get Site Import Receipt | transfer:* |
管理员批准请求时,site_export_start 和 site_import_start 也可接受无对应 scope 的令牌。对于已批准请求所启动的操作,site_export_status、site_import_status、site_import_resume 和 site_import_receipt 也可接受同一令牌且无需 scope。
使用工具 schema
tools/list 返回每个工具的描述、JSON 输入 schema 和注解。构造调用前请阅读这些元数据,以便客户端使用已安装 EmDash 版本支持的字段、允许值和限制。
例如,更新文章的客户端先调用 content_get 并保留返回的 _rev,然后可发送如下 JSON-RPC 请求:
{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/call",
"params": {
"name": "content_update",
"arguments": {
"collection": "articles",
"id": "01JARTICLE0000000000000000",
"data": { "title": "Updated title" },
"_rev": "opaque-revision-token"
}
}
}
结果以 JSON 文本形式返回在第一个内容块中。带输出 schema 的工具还可能在 structuredContent 中返回相同值。
内容生命周期与署名
内容生命周期参考定义 MCP、REST、CLI 和管理后台共享的状态、修订、 权限、冲突和 hook 行为。
content_get 返回不透明的 _rev 值。将其传给 content_update、content_publish、content_unpublish、content_schedule 或 content_discard_draft。过期值会返回冲突,重试前请重新读取条目。
content_update 为部分更新:未提供的字段保持当前值。更新已发布条目会暂存草稿,线上版本不变。使用 content_compare 对比线上与草稿,再调用 content_publish 发布草稿或 content_discard_draft 丢弃。content_delete 将条目移入回收站;仅 content_permanent_delete 永久删除回收站中的条目。
署名是可复用的作者或贡献者信息。byline_create 可创建访客署名或关联 CMS 用户。将返回的署名 ID 传入 content_create 和 content_update 接受的 bylines 输入。删除署名会从内容中移除该信息并清除主署名。
MCP 写入不参与管理后台的条目编辑锁。_rev 检查保护接受它的操作,但其他写入工具可能在编辑者打开条目时修改条目。
翻译
内容、署名、分类术语和菜单的翻译工具返回相关翻译组中的各语言变体。若创建工具的 schema 提供 translationOf 输入则使用它;必填字段以 tools/list 为准。
content_translations 接受集合及内容 ID 或 slug。署名、术语和菜单翻译工具接受单条记录 ID 或共享翻译组 ID。无草稿访问权限的用户只能看到已发布内容的翻译。
架构、媒体、分类法与菜单
架构工具会改变数据库结构。创建内容或修改字段前请使用 schema_get_collection;它返回可用字段名、类型、约束和验证规则。删除集合和字段会移除已存内容或字段值且无法撤销。
使用 media_upload 发送 base64 编码的字节。上传受配置的大小和 MIME 类型限制;相同字节可能返回已有媒体项并带 deduplicated: true。
media_create 确认通过 POST /_emdash/api/media/upload-url 创建的待处理上传。用返回的签名 URL 上传文件,再以同一用户账号和返回的 storageKey 调用 media_create。工具在媒体库可用前会验证存储文件存在且大小与请求上传 URL 时一致。
分类法定义描述分类及其适用的集合;术语是分配给内容的单个值。层级术语可使用 parentId,但父项须属同一分类法且不能形成环。在非层级分类法中用 parentId 创建或更新术语会返回 VALIDATION_ERROR。有子项的术语须先移除或移动子项才能删除。
menu_set_items 在一次原子操作中替换菜单的完整项列表。数组顺序即菜单顺序。嵌套项的 parentIndex 指向同数组中更早的项,因此每个父项应放在其子项之前。
media_usage_repair 可处理单个或全部集合,在大型站点上可能运行较久。其 complete、partial、failed 和 stale 状态均为成功的工具响应。请检查返回的状态和计数,而非依赖 isError;认证、验证和意外执行失败会设置 isError: true。
站点转移
site_* 工具将整个站点导出为站点包并将包导入空站点。它们启动并驱动操作并返回有限摘要,从不承载包字节、媒体、记录内容、主体邮箱或下载 URL。请用 CLI 或 REST API 下载导出并上传导入包,再按 ID 引用操作。
每个转移工具都需要 Admin 角色。角色在 scope 之前检查;非管理员调用方收到 INSUFFICIENT_PERMISSIONS 且不会创建批准请求。
导出
site_export_start 接受 comments(默认 true)并返回新操作。site_export_status 每次调用执行一个有限导出步骤并报告操作和 nextRequestInMs。在该延迟后重复调用直至 nextRequestInMs 为 null。传入 advance: false 可不执行步骤仅读状态。导出完成后,结果还包含 totals:按类型的记录数、媒体数量与字节、包文件数量与字节。
导入
请先上传包。CLI 的 emdash site import <file> --analyze 会上传、分析并打印操作 ID。
site_import_analyze 每次调用执行一个有限分析步骤。重复直至 nextRequestInMs 为 null;结果将包含计划摘要,含 packageDigest、planDigest、executable、计数、大小、主体、决策、转换、警告和阻塞项。每项转换列出其 code、记录的 kind(如有)和 count,不含所应用的 ID 或值。主体、警告和阻塞项各最多 50 条,完整计数在 total。主体列表不含邮箱,含建议与当前映射的目标用户 ID。传入 decisions 可将主体映射到目标用户 ID(或 null)并选择包或目标的标题与副标题。每次变更产生新的 planDigest。
site_import_start 接受操作 ID 及最新计划的 packageDigest 和 planDigest。计划不得有阻塞项。该工具有 destructiveHint: true:启动后导入会写入站点并阻塞其他写入,直至完成或管理员放弃。调用前向用户展示计划并确认。
site_import_resume 执行一个有限导入步骤并报告操作和 nextRequestInMs。重复直至 nextRequestInMs 为 null;断线后重复安全。site_import_status 报告操作和已上传文件数且不推进导入。site_import_receipt 在导入完成后返回完整回执,含 receiptDigest。
导入执行期间,以及失败或取消后直至放弃前,所有其他可写工具均因 TRANSFER_IMPORT_IN_PROGRESS 失败,包括插件工具。标注 readOnlyHint: true 的工具和八个 site_* 工具仍可用,initialize 和 tools/list 从不阻塞。媒体使用激活期间,写入工具同样因 MEDIA_USAGE_ACTIVATION_IN_PROGRESS 失败。
操作摘要包含 id、kind、state、stage、progress、packageDigest、planDigest、error({ code } 或 null)和时间戳。progress 为 { done, total } 步骤,外加导出已写入的 records,以及已知时的 bytesDone 和 bytesTotal。MCP 工具不能取消或放弃导入;请使用 REST API。
批准
带有 admin 或所需转移 scope 的令牌从不请求批准。对两者皆无的令牌(如仅授予 transfer:analyze 的智能体),管理员批准后 site_export_start 和 site_import_start 才会执行:
- 无 scope 的首次调用创建待批准请求并以
TRANSFER_APPROVAL_REQUIRED失败。消息文本和_meta.details携带approvalId及其expiresAt。相同参数且无approvalId再次调用返回同一未决请求。 - 管理员在 设置 → Transfer 的 Approval requests 中批准,或通过 REST API 的仅会话批准端点。API 令牌不能批准请求。
- 客户端以相同参数和
approvalId重复调用。该调用启动操作时消耗批准。若操作未能启动,客户端可用同一approvalId重试直至过期。
请求绑定用户、令牌、操作和精确参数:导出选项,或导入操作 ID 与两个摘要。待决请求在创建后 15 分钟过期,已批准的在批准后 15 分钟过期。参数或令牌不同,或批准被拒绝、过期或已用时,调用因 TRANSFER_APPROVAL_INVALID 失败。
site_import_start 在创建请求前检查摘要、操作状态和计划阻塞项,因此管理员只需批准可执行的导入。批准需要令牌 ID;无令牌 ID 的调用方收到 INSUFFICIENT_SCOPE。
已批准调用启动操作后,同一用户和令牌可无 scope 调用该操作的 site_export_status,或 site_import_status、site_import_resume 和 site_import_receipt。
插件工具
管理员须启用各插件的 MCP 界面。已启用工具在 tools/list 中显示为 <pluginId>__<localName>,令牌认证调用需要 mcp:tools 或 mcp:tools:<pluginId>。EmDash 还会检查插件路由声明的权限,并在审计日志中记录插件、工具、路由和执行者。
插件工具因安装而异,不在上述静态清单中。
OAuth 发现
MCP 客户端从受保护资源元数据发现授权服务器:
GET /.well-known/oauth-protected-resource
响应将 /_emdash/api/mcp 标识为受保护资源并链接到授权服务器。客户端随后读取:
GET /.well-known/oauth-authorization-server/_emdash
该文档提供当前授权、令牌、注册和设备授权端点、支持的 scope、grant 类型及 PKCE 方法 S256。请使用发现值,勿硬编码 OAuth 协议路由。
未认证的 MCP 请求返回带发现 URL 的 401:
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer resource_metadata="https://example.com/.well-known/oauth-protected-resource"
错误
工具失败时 isError: true。第一个文本块以稳定代码开头,_meta.code 为读取结构化元数据的客户端重复该代码:
{
"content": [{ "type": "text", "text": "[NOT_FOUND] Collection 'articles' not found" }],
"isError": true,
"_meta": { "code": "NOT_FOUND" }
}
认证失败使用如 INSUFFICIENT_SCOPE 和 INSUFFICIENT_PERMISSIONS 等代码。传输失败使用 JSON-RPC 内部错误码 -32603,不暴露底层异常。